当前位置: 首页 > news >正文

计算机网络基础之网络套接字socket编程(初步认识UDP、TCP协议)

绪论​
“宿命论是那些缺乏意志力的弱者的借口。 ——罗曼.罗兰”,本章是为应用层打基础,因为在写应用层时将直接通过文本和代码的形式来更加可视化的理解网络,本章主要写的是如何使用网络套接字和udp、tcp初步认识。
请添加图片描述
话不多说安全带系好,发车啦(建议电脑观看)。

在这里插入图片描述

1.理解套接字

套接字 = ip + 端口
我们所有的网络通信行为:本质就是进程间通信!!
在这里插入图片描述
对双方而言

  1. 先把数据能到达自己的主机(ip)
  2. 找到指定的进程(port:端口号)
  3. IP地址用来标识互联网中唯一的一台主机
  4. 端口号用来标识该指定的机器中进程的唯一性
  5. 所以{ip,port}(ip+port):互联网中唯一一台主机的进程(套接字,socket )
  6. 在互联网中通过套接字进行通信(也就类似于是网络中的进程通信)

2.认识端口号

端口是是一个16位的数字:uint16_t port,它用来标识当前主机上的唯一的一个网络进程,以及让网络进程 和 port进行绑定关联(让接收方确认是发给自己的某个进程)

2.1pid vs port

不难发现pid和port非常类似,但区分出来的原因是:

  1. 进程管理 和 网络管理 进行解耦(防止OS中进程改变影响网络通信)
  2. port用来专门进行网络通信
  3. 一个端口号只能和一个进程相关联(反之一个进程可以和多个端口号相关联)

socket:可以理解成打10086 + 转人工(工号):其中10086相当于ip、人工相当于port

3.TCP协议

1. 有连接
2. 可靠传输:tcp面对异常他会进行可靠性处理,丢包了重传,乱序了排成有序 …(但也意味着复杂要做更多的工作)
3. 面向字节流

4.UDP协议

1. 无连接
2. 不可靠传输:反之发数据后,不管数据是否异常(丢包,乱序…)(也意味着简单,适合于对数据可靠性要求不高的场景(直播))
3. 面向数据报


tcp、udp:只有不同,没有好坏,分情况使用。


5.网络字节序

在网络中可能会有大端存储,小端存储
不懂的建议观看这篇blog
存储的方式可能因为不同的机器,存储方案也是不同的

其中在网络中:

  1. 发送主机通常将发送缓冲区中的数据按内存地址从低到高的顺序发出
  2. 接收主机把从网络上接到的字节依次保存在接收缓冲区中,也是按内存地址从低到高的顺序保存
  3. 总结上述意思就是:组织规定了所有到达网络的数据,必须是大端,所有从网络收到数据的机器,都会知道数据是大端(这样也能方便的查看数据)

6.socket编程接口

6.1UDP协议

网络编程的时候,socket是有很多类型的

  1. unix socket:域间socket->同一台主机上的文件路径,命名管道,本主机内部进行通信
  2. 网络socket:主要使用ip+port进行网络通信(用的是传输层和网络层(tcp、udp))
  3. 原始socket:编写一些网络工具(应用层跳过传输层(不进行网络通信)直接到达网络层和数据链路层)

网络编程的时候,是由不同的场景的,理论上而言,我们应该给每种场景设计一套编程接口,而设计者只想用一套接口(改变传递参数就能实现找到对应的结构,基础的结构是struct sockaddr)
struct socket是一个通用的地址类型:

在这里插入图片描述
通过判断前两个字节(16位地址类型)来判断是哪一种类型

  1. 当第一个参数是AF_INET表示:网络通信
  2. 当第一个参数是AF_UNIX:域间套接

上述用的都是同一套API:


常用的socket套接字函数(能写UDP协议了)

  1. int socket(int domain, int type, int protocol);
    1. 头文件:#include <sys/types.h>、#include <sys/socket.h>
    2. domain:设置通信的协议(AF_INET:网络通信、AF_UNIX:域间套接)在这里插入图片描述
    3. type:套接字类型(SOCK_STREAM:流式套接(TCP常用)、SOCK_DGRAM:面对数据报,提供数据报协议(UDP)、SOCK_RAM:原始套接)在这里插入图片描述
    4. protocol:表示使用的那个(tcp/udp)协议,通过前面两个参数已经能确定是那个协议了,故可直接写成0

  1. int bind(int sockfd, const struct sockaddr *addr , socklen_t addrlen);
    1. 头文件:#include <sys/types.h>、#include<sys/socket.h>
    2. sockfd:创建的网络套接字
    3. addrlen:传入结构体的长度
    4. addr:
    sockaddr_in的结构,其中包括了:
    1. sin_family:AF_INET;//协议家族 ,绑定网络通信的信息
    2. sin_port
    3. s_addr
    在这里插入图片描述
    在这里插入图片描述
    所以对sockaddr_in local(addr)初始化写成(具体细节已注释):
 //补充结构体sockaddr_in:bzero(&local,sizeof(local));//将指定的空间的大小内存清零local.sin_family = AF_INET;//协议家族 ,绑定网络通信的信息//sin:s:socket in:inet(IP地址!)local.sin_port = htons(_port);//将string的_port转换成网络序列,通过htons h:host、to、n:net、s:socketlocal_sin_addr.s_addr = inet_addr(_ip);//需要把uint16_t的ip转化成4字节的uint32_t的网络序列的ip,可以通过inet_addr

第一个参数:sin_family就是sockaddr_in将sa_prefix传进宏和family进行拼接得
,初始化成AF_INET(表示绑定网络通信)。在这里插入图片描述
第二个参数:sin_port:初始化为_port主机转网络序列(用函数htons)。在这里插入图片描述
第三个参数:sin_addr.s_addr:通过in_addr的结构知道还要对s_addr初始化成32位(4byte)。初始化为:把ip转换成32位并且网络序列化(直接用函数inet_addr)。
在这里插入图片描述在这里插入图片描述

附:
函数:void bzero(void *s, size_t n)
作用:将指定内存全部清零、头文件:#include<strings.h>


接收用户信息函数:

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
  1. sockfd:创建的sock文件
  2. buf:缓冲区(获取用户发来的信息)
  3. len:期望大小(返回值才是实际大小)
  4. flags:收数据的模式(默认设为0,阻塞式收消息)
  5. src_addr:输出型参数,接收到发送端的套接字信息(ip、port)
  6. addrlen:传入结构体的长度

发送用户信息函数:

	ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);

参数和上面都一样(不过诉了),不同点:

  1. dest_addr:填写要发给的对象信息(服务器可以先通过接收到的发送端信息直接填写,而客户端则是将提前得知的服务器的信息填写进去)

6.1.1编写服务器和客户端的代码:

UdpServer.hpp:

#pragma once
#include<iostream>
#include<string>
#include<unistd.h>
#include<strings.h>#include <sys/types.h>         
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>#include<cerrno>
#include<cstring>#include"Comm.hpp"
#include"nocopy.hpp"
#include"Log.hpp"using namespace std;static const uint16_t defaultport = 8888; 
static const uint16_t defaultfd = -1; 
static const int defaultsize = 1024; class UdpServer : public nocopy
{
public:UdpServer(const string& ip,uint16_t port = defaultport):_ip(ip),_port(port),_sockfd(defaultfd){}void Init(){//1. 创建套接字,创建成功后会返回 a ffile descriptor ,出错返回-1_sockfd = socket(AF_INET,SOCK_DGRAM,0);//SOCK_DGRAM : udpif(_sockfd < 0)//创建套接字失败{lg.LogMessage(Fatal,"socket errno, %d : %s\n",errno,strerror(errno));exit(Socket_Err);}lg.LogMessage(Info,"socket success,sockfd : %d\n",_sockfd);//2. 绑定,指定网络信息 相当于给套接字命名struct sockaddr_in local;//该结构体 需要新加头文件:#include <netinet/in.h>、#include <arpa/inet.h>//补充结构体sockaddr_in:bzero(&local,sizeof(local));//将指定的空间的大小内存清零local.sin_family = AF_INET;//协议家族 ,绑定网络通信的信息//sin:s:socket in:inet(IP地址!)local.sin_port = htons(_port);//将string的_port转换成网络序列,通过htons h:host、to、n:net、s:socketlocal.sin_addr.s_addr = inet_addr(_ip.c_str());//需要把uint16_t的ip转化成4字节的uint32_t的网络序列的ip,可以通过inet_addr//将刚刚设置好的网络信息和的sockfd文件信息进行绑定int n = ::bind(_sockfd,(struct sockaddr*)&local,sizeof(local));if(n != 0){lg.LogMessage(Fatal,"bind errno, %d : %s\n",errno,strerror(errno));exit(Bind_Err);}
//绑定完成后就:服务器就已经初始化完成了}void Start() {char buffer[defaultsize];//服务器永远不退出!for(;;){ //服务器进行udp收发消息struct sockaddr_in peer;socklen_t len = sizeof(peer);//socklen_t 就是无符号整形ssize_t n = recvfrom(_sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);if(n > 0){ buffer[n] = 0;//当字符串cout << "client say# " << buffer << endl;//给对方发消息:sendto(_sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,len);}}}~UdpServer(){}
private:string _ip;uint16_t _port;int _sockfd;
};

UdpClient.cc:

#include <iostream>#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>#include <cstring>
#include <unistd.h>
#include <cerrno>
using namespace std;void Usage(const string &process)
{cout << "Usage: " << process << " server_ip server_ip" << endl;
}//./udp_client server_ip server_port  向服务器发消息
int main(int argc, char **argv)
{if (argc != 3){Usage(argv[0]);return 1;}string serverip = argv[1]; // 字符串的点分十进制uint16_t serverport = stoi(argv[2]);// 1. 创建套接字,创建文件信息int sock = socket(AF_INET, SOCK_DGRAM, 0);if (sock < 0){cerr << "socket error: " << strerror(errno) << endl;return 2;}cout << "create socket success: " << sock << endl;// 2. 绑定// 其中客户端的socket也要绑定,但是不需要显示bind(client会在首次发送数据的时候会自动进行bind)// 不推荐自己绑定,为什么?服务器的端口号一定是众所周知的,不可改变。而client 需要 port,让其随机bind端口// client一般不绑定一个确定的socket因为:client 会非常多(打开如何软件),可能会导致别人占了你的端口// 就导致无法启动// 2.1 填充server信息:struct sockaddr_in server;memset(&server,0,sizeof(server));server.sin_family = AF_INET;server.sin_port = htons(serverport);server.sin_addr.s_addr = inet_addr(serverip.c_str());while (true){string inbuffer;cout << "Please Enter# ";getline(cin, inbuffer);// 发给:int n = sendto(sock,inbuffer.c_str(),inbuffer.size(),0,(struct sockaddr*)&server,sizeof(server));if(n>0)//发送成功{char buffer[1024];//收消息struct sockaddr_in temp;socklen_t len = sizeof(temp);ssize_t m = recvfrom(sock,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&temp,&len);//虽然没用但也是要填的tempif(m > 0){buffer[m] = 0;cout << "server echo# " << buffer << endl;}else break;}}   close(sock); // 把文件描述符关掉return 0;
}

nerstat -anup:查看网络连接,网络情况的指令
-a:所有信息
n:num:将主机名转换为数字化
p:process信息
u:udp
在这里插入图片描述
当把ip设置为127.0.0.1表示在当前主机内进行本地通信

再对代码进行优化,当客户端发来信息时显示客户端的socket:

//...
class UdpServer : public nocopy
{
public:UdpServer(const string& ip,uint16_t port = defaultport):_ip(ip),_port(port),_sockfd(defaultfd){}void Init(){//...}void Start() {char buffer[defaultsize];//服务器永远不退出!for(;;){ //服务器进行udp收发消息struct sockaddr_in peer;socklen_t len = sizeof(peer);//socklen_t 就是无符号整形ssize_t n = recvfrom(_sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,&len);if(n > 0){ InetAddr addr(peer);//新写一个类,对类进行封装buffer[n] = 0;//当字符串cout << "[" << addr.PrintDebug() << "]# "<<  buffer << endl;//调用该类中类函数//给对方发消息:sendto(_sockfd,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&peer,len);}}}~UdpServer(){}
private:string _ip;uint16_t _port;int _sockfd;
};

InetAddr.hpp:

#pragma once
#include<iostream>
#include<string>
#include <sys/types.h>         
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include<unistd.h>using namespace std;class InetAddr
{
public:InetAddr(struct sockaddr_in &addr){port = ntohs(addr.sin_port);ip = inet_ntoa(addr.sin_addr);//将网络序列转化成addr}string IP(){return ip;}uint16_t Port(){return port;}string PrintDebug(){string info = ip;info += ":";info += to_string(port);return info;}private:string ip;uint16_t port;
};

云服务器公网IP其实是供应商给你虚拟出来的公网ip,无法bind
如果你是真的Linux环境,可以直接bind你的ip,但强烈不建议给服务器bind固定ip(这样服务器和特定ip绑死后就只能收到该绑定的机器的信息),更推荐本地任意ip绑定的方式:
所以我们能修改UdpServer.hpp的代码:
把UdpServer的成员变量ip取消掉(local.sin_addr.s_addr = inet_addr(_ip.c_str());),并且把ip绑定成INADDR_ANY(0,local.sin_addr.s_addr = INADDR_ANY;)

//...// 优化代码不绑定特定的ip
class UdpServer : public nocopy
{
public:UdpServer(uint16_t port = defaultport):_port(port),_sockfd(defaultfd){}void Init(){
//...local.sin_addr.s_addr = INADDR_ANY;//一般不固定一个ip,而是任意生成一个ip的动态绑定:INADDR_ANY == 0       // local.sin_addr.s_addr = inet_addr(_ip.c_str());//需要把uint16_t的ip转化成4字节的uint32_t的网络序列的ip,可以通过inet_addr//...}void Start() {
//...}~UdpServer(){}
private:// string _ip;uint16_t _port;int _sockfd;
};

Makefile:
主函数:

#include"UdpServer.hpp"
#include<memory>
#include<string>
#include"Comm.hpp"
using namespace std;void Usage(string proc)
{cout << "Usag : \n\t" << proc << " local_ip local_port\n" << endl;
}//./udp_sv 8888
int main(int argc,char* argv[])
{if(argc != 2){Usage(argv[0]);return Usage_Err;}string ip = argv[1];uint16_t port = stoi(argv[2]);unique_ptr<UdpServer> usvr = make_unique<UdpServer>(port);usvr->Init();usvr->Start();return 0;
}

Makefile:

.PHONY:all
all:udp_server udp_clientudp_server:Main.ccg++ -o $@ $^ -std=c++14udp_client:UdpClient.ccg++ -o $@ $^ -std=c++14.PHONY:clean
clean:rm -f udp_server udp_client

在这里插入图片描述
其中服务器的ip为0.0.0.0表示任意ip地址绑定
可以通过在别的客户端上对该服务器进行发送消息,只需要知道当前服务器的ip和端口,并且有当前客户端的可执行程序

云服务器中大部分端口号是被腾讯云、阿里云…拦截的。
我要能够进行数据的发送,需要开放指定的多个端口 8080、8081、…

6.2TCP协议

除UDP以学的外,主要新增有:

在socket函数创建文件时,第二个参数使用SOCK_STREAM(提供序列化的、可靠的、双方的、建立链接的字节流)

int sockfd = socket(AF_INET,SOCK_STREAM,0);
  1. Server端:c/s双方要进行通信,得先建立连接(接收client的连接):
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
  1. sockfd sock文件
  2. addr 和 addrlen (与recvfrom中的参数一样)
  3. 头文件:#include <sys/types.h>、#include <sys/socket.h>
  4. 返回值:成功后会返回一个新增的文件描述符accepted socket,失败返回-1并设置错误码,而该返回的文件描述符sockfd就是用来将server获取新链接(sockfd在该内部进行服务的处理,我们创建的listensock只是用来找到client的!

用来侦听功能将套接字置于侦听传入连接的状态:

int listen(int sockfd, int backlog);

sockfd:sock文件
(backlog后面会具体写暂时只要知道在tcpServer中肯定要写)


  1. Client端:连接到服务器
 int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
  1. 头文件:#include <sys/types.h>、#include <sys/socket.h>

  2. 成功返回0,否则返回-1,错误码被设置

  3. 对于客户端来说并不需要自己绑定(bind)ip和port,client系统会随机一个端口! 当我们在发起连接的时候,client会被OS自动进行本地绑定


inet_pton 和 inet_addr类似都能把四字节的字符串ip转化成网络序列:

int inet_pton(int af, const char *src, void *dst);
  1. af:通信的协议(AF_INET网络通信)
  2. src:来源(写ip)
  3. dst:目的的(写&local.sin_addr)
  4. 头文件:#include <arpa/inet.h>

6.2.1基本tcp实现通信:

Client.cc

#include <iostream>
#include <string>
#include <cstring>
#include <cstdlib>
#include <stdio.h>
#include <unistd.h>#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h> #include "Comm.hpp"
#include "Log.hpp"using namespace std;int main(int argc , char*argv[])
{if(argc != 3){cout << "Usag: \n\t"<< argv[0] << " " << "local_ip local_port \n" << endl;return Usage_Err;}  string serverip = argv[1];int serverport = atoi(argv[2]);//1. 创建套接字socketint sockfd = socket(AF_INET,SOCK_STREAM,0);if(sockfd < 0){lg.LogMessage(Fatal,"create sock errorr, %d : %s\n",errno,strerror(errno));exit(Fatal);}//2. connect
//不用bind , 系统在local的时候会自动bindstruct sockaddr_in local;memset(&local,0,sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(serverport); inet_pton(AF_INET,serverip.c_str(),&local.sin_addr);int n = connect(sockfd,CONV(&local),sizeof(local));if(n < 0){cout << "connect error" << endl;return 2;}while(true){string inbuffer;cout << "Pleace Enter# ";getline(cin,inbuffer);ssize_t n = write(sockfd,inbuffer.c_str(),inbuffer.size());//直接使用sockfdif(n > 0){char buffer[1024];ssize_t m = read(sockfd,buffer,sizeof(buffer)-1);//直接使用sockfdif(m > 0){buffer[m] = 0;//将最后位置写个0,作为结束标志 cout << "get a echo message -> " << buffer << endl;}else{break;}}else{cout << "write error" << endl;break;}}close(sockfd);return 0;
}

Server.hpp

#pragma once#include <iostream>
#include <string>
#include <cstring>
#include <cerrno>
#include <unistd.h>#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h> #include "Log.hpp"
#include "nocopy.hpp"
#include "Comm.hpp"static const int default_backlog = 5;class TcpServer : public nocopy
{
public:TcpServer(uint16_t port):_port(port),_isrunning(false){}void Init(){//创建sock文件_listensock = socket(AF_INET,SOCK_STREAM,0);if(_listensock < 0){lg.LogMessage(Fatal,"create sock errorr, %d : %s\n",errno,strerror(errno));exit(Fatal);}   lg.LogMessage(Debug,"create sock success, sock:%d\n",_listensock);//填充本地信息并bind给内核struct sockaddr_in local;memset(&local,0,sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(_port);//将整形转化成网络序列local.sin_addr.s_addr = htonl(INADDR_ANY);//0 //bind sock文件和网络序列if(bind(_listensock,CONV(&local),sizeof(local)) != 0){lg.LogMessage(Fatal,"bind error, %d : %s\n",errno,strerror(errno));exit(Bind_Err);}lg.LogMessage(Debug,"bind success,sock: %d\n",_listensock);//设置socket为监听模式,tcp特有的if(listen(_listensock,default_backlog) != 0){lg.LogMessage(Fatal,"listen error, %d : %s\n",errno,strerror(errno));exit(Listen_Err);}lg.LogMessage(Debug,"listen success,sock: %d\n",_listensock);}//Tcp连接全双工通信void Service(int sockfd){char buffer[1024];while(true){ssize_t n = read(sockfd,buffer,sizeof(buffer));if(n > 0){buffer[n] = 0;cout << "client echo# " << buffer << endl;string echo_string = "server echo# ";echo_string += buffer;write(sockfd,echo_string.c_str(),echo_string.size());}else if(n == 0){lg.LogMessage(Info,"client quit...\n");break;}else{lg.LogMessage(Error,"listen error, %d : %s\n",errno,strerror(errno));break;}}}void Start(){_isrunning = true;while(_isrunning)//_listensock相当于是负责拉客的人,找到客人返回的文件描述符{//获取连接struct sockaddr peer;socklen_t len = sizeof(peer);int sockfd = accept(_listensock,CONV(&peer),&len);//sockfd才是提供服务的服务员if(sockfd < 0){lg.LogMessage(Fatal,"accept fail,%d : %s\n",errno,strerror(errno));continue;}lg.LogMessage(Debug,"accept success,sock: %d\n",_listensock);//提供服务: v1 ~ v4    Service(sockfd);close(sockfd);}}~TcpServer(){}private:    int _listensock;uint16_t _port;bool _isrunning;
};

Makefile:

.PHONY:all
all:tcp_server tcp_clienttcp_server:Main.ccg++ -o $@ $^ -std=c++14 -lpthreadtcp_client:TcpClient.ccg++ -o $@ $^ -std=c++14 -lpthread.PHONY:clean
clean:rm -f tcp_server tcp_client

Main.cc:
此处要通过Main.cc 启动服务器

#include <iostream>
#include <memory>
#include <string>
#include <stdio.h>#include "TcpServer.hpp"
#include "Comm.hpp"using namespace std;int main(int argc,char* argv[])
{if(argc != 2){cout << "Usag: \n\t"<< argv[0] << " " << "local_port\n" << endl;return Usage_Err;}uint16_t port = stoi(argv[1]);unique_ptr<TcpServer> tsvr(new TcpServer(port)); // 另外的使用方式//unique_ptr<TcpServer> tsvr = make_unique<TcpServer>(port);tsvr->Init();tsvr->Start();return 0;
}

测试结果:
在这里插入图片描述

通过netstat -nltp可以查看已有的网络链接
在这里插入图片描述

1. IO类函数,write/read其实他们已经做了转网络序列
2. udp:用户数据报,数据都是一个个报文的发送、tcp:面向字节流数据,一次发一条数据,这些数据可能是一个报文也可能是不完整的报文,后面通过边界来找到对应的
1. sendto、recvfrom -> sendto 发了一次,一定对应对端recvfrom一次,面向数据报(类似于发邮件、快递一件一件的!)
2. write -> 写了1,10,…次 ,接收方read可能一次就全部收完了,也可能多次,无论多少次,和对方发的无关,面向字节流 (类似于使用的自来水,用多少取决于自己)
3. 我们的网络服务,不能在bash中以前台进程的方式运行,真正的服务器必须在Linux后台,以 守护进程(精灵进程)的方式持续不断的运行!

在这里插入图片描述

守护进程

会话与进程组

查看已用进程(查看sleep,通过ps查看在通过管道grep和head过滤出进程sleep和ps的第一行 )

ps -ajx | head -1 && ps ajx | grep sleep

在这里插入图片描述同时创建的进程会在同一个会话中,其中第一个创建的进程组id(和他的pid一样)和会话id就是所有进程的进程组id和会话id:
在这里插入图片描述

每次登录Linux -> OS 会给登录用户提供:

  1. bash
  2. 提供一个终端(给用户提供命令行解析->叫做一个会话)
  3. 在命令行中启动的所有的进程,最终默认都是在当前会话内部的一个进程组(可以是一个进程自成进程组)
    在这里插入图片描述
    任何时刻,一个会话内部,可以存在很多个进程组(用户级任务),但是默认如何时刻,只允许一个进程组在前台(前台进程组)
    通过在启动进程是 加 & 让其进程组变成后台进程
    在这里插入图片描述
    查看后台进程
jobs

在这里插入图片描述
其中第一列的序号就是后台进程的编号
在这里插入图片描述
将后台进程放回前台

fg + 后台进程编号

在这里插入图片描述
其中注意的是:
bash也是一个进程组!
而一个会话中只能有一个进程组在前台!

所以当sleep的进程组从后台到前台后bash就无法使用了,而前台是和终端和键盘相关,可以通过键位再把当前进程组放回后台(直接发送终止信号终止进程)

ctrl + z

在这里插入图片描述
此时bash会自动被放回前台,并且发现其状态变成了Stopped,所以需要再把状态调回Runing

bg + 进程组编号

在这里插入图片描述
用户级任务 Vs 进程组:它们是一个概念,只不过进程组更加的专业化,进程组就是实现现有的任务。
在这里插入图片描述

会话:相当于开的一个窗口,其前台进程一开始就是bash,若要再创建进程就是在该会话内创建。


守护进程的作用以及实现原理

若想要服务器,不受用户登录和注销的影响:
就只能使用守护进程:它就是一个独立的会话,不隶属于任何bash的会话!将前面写的tcpserver、udpserver守护进程化,进程守护话后表示:该服务即使会话关闭后也仍然存在,也就相当于一个后台服务(24h的云服务器)

  1. 创建一个会话,并且设置自己为会话的首进程,这样会话id和进程组id与该进程的会话id相同
  2. 若要创建一个新的会话,调用进程不能是一个组长
    组长一般是多个进程的第一个,若只有一个进程,自成进程组,那组长自然就是自己
    所以要:创建子进程(fork),再让父进程终止(这样子进程就既不是组长了,并且会话也只有一个进程),所以守护进程就一定是孤儿进程(系统是父进程 ppid:1)
  3. 因为守护进程和当前目录无关所以可以把其目录改成根目录(当然也能不进行修改)
    在这里插入图片描述
    4. 对于守护进程不需要和用户进行输入输出,错误的关联了,所以就能关闭或者重定向到null文件,因为写到null文件(null字符设备)中的都会被丢弃,凡是从null文件中拿的都是空,该文件目录为:/dev/null
    在这里插入图片描述

模拟实现守护进程:

#pragma once//Daemon 守护、精灵
#include <signal.h>
#include <unistd.h>
#include <cstdlib>#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>const char* root = "/";
const char* dev_null = "/dev/null";//守护进程的核心5个步骤
void Daemon(bool ischdir,bool isclose)
{
//1. 忽略可能出错的信号signal(SIGCHLD,SIG_IGN);signal(SIGPIPE,SIG_IGN);//2. 关闭父进程if(fork() > 0) exit(0);//3. 变成组长setsid();//4. 每一个进程都有自己的cwd,是否将当前进程cwd改成 / 根目录if(ischdir) chdir(root);//5. 已经变成守护进程了,就不需要和用户进行输入输出,错误的关联了if(isclose){close(0);close(1);close(2);}else//一般就直接把他们重定向到O_RDWR{int fd = open(dev_null,O_RDWR);if(fd > 0)//文件打开成功{dup2(fd,0);dup2(fd,1);dup2(fd,2);close(fd);}}}//main.cc
#include "Daemon.hpp"
#include <unistd.h>int main()
{//变成守护进程Daemon(true,false);while(true){
//要执行的核心代码}return 0;
}

创建好的守护进程:
在这里插入图片描述
系统也是有提供把进程守护进程化的方法的:

#include <unistd.h>int daemon(int nochdir, int noclose);

和上面模拟写的类似,但传的参数不一样内部实现也若有不同:
在这里插入图片描述

  1. 如果nochdir为零,daemon()将调用进程的当前工作目录更改为根目录(“/”);否则,当前工作目录保持不变。
  2. 如果noclose为零,daemon()将标准输入、标准输出和标准错误重定向到/dev/null;否则,不会对这些文件描述符进行任何更改。

TCP通信协议过程

在这里插入图片描述

在这里插入图片描述
在tcp客户端和服务端连接采用:三次握手也就是进行三次的报文交换(SYN、SYN+ACK、ACK),当三次握手完成后accept才会返回。connect(一个系统调用)只是发起了第一次握手,三次握手的本质是建立共识!
1. connect和accept都会阻塞式的等待三次握手的完成
2. 三次握手是建立链接前必须做的

在这里插入图片描述

在tcp客户端和服务端断开链接采用:一次close(关闭客户端->服务器通信信道)对应着两次挥手(发送FIN,ACK报文),所以两次close(关闭-服务器>客户端通信信道)对应着四次挥手,因为是全双工通信,所以两方都需要要关闭信道

所以tcp建立链接需要三次握手,断开链接需要四次挥手


本章完。预知后事如何,暂听下回分解。

如果有任何问题欢迎讨论哈!

如果觉得这篇文章对你有所帮助的话点点赞吧!

持续更新大量计算机网络细致内容,早关注不迷路。

相关文章:

计算机网络基础之网络套接字socket编程(初步认识UDP、TCP协议)

绪论​ “宿命论是那些缺乏意志力的弱者的借口。 ——罗曼&#xff0e;罗兰”&#xff0c;本章是为应用层打基础&#xff0c;因为在写应用层时将直接通过文本和代码的形式来更加可视化的理解网络&#xff0c;本章主要写的是如何使用网络套接字和udp、tcp初步认识。 话不多说安…...

手撕Python!模块、包、库,傻傻分不清?一分钟带你弄明白!

哈喽&#xff0c;各位小伙伴们&#xff01;今天咱们来聊聊Python中的模块、包和库&#xff0c;很多新手小白经常搞混&#xff0c;别担心&#xff0c;看完这篇&#xff0c;保证你一分钟就能搞定&#xff01; 打个比方&#xff1a; 模块 (Module): 就好比是一块块乐高积木&#…...

Linux--序列化与反序列化

序列化 序列化是指将数据结构或对象状态转换成可以存储或传输的格式的过程。在序列化过程中&#xff0c;对象的状态信息被转换为可以保持或传输的格式&#xff08;如二进制、XML、JSON等&#xff09;。序列化后的数据可以被写入到文件、数据库、内存缓冲区中&#xff0c;或者通…...

使用C#和 aspose.total 实现替换pdf中的文字(外语:捷克语言的pdf),并生成新的pdf导出到指定路径

程序主入口&#xff1a; Program.cs &#xfeff;using System; using System.Collections.Generic; using System.Configuration; using System.Diagnostics; using System.Linq; using System.Text; using System.Threading.Tasks;namespace PdfEditor {public class Progra…...

【Material-UI】Autocomplete中的高亮功能(Highlights)详解

文章目录 一、简介二、实现高亮功能示例代码代码解释 三、实际应用场景1. 搜索功能2. 表单自动完成 四、总结 在现代Web开发中&#xff0c;提供清晰的用户反馈是提升用户体验的重要组成部分。Material-UI的Autocomplete组件通过高亮功能&#xff0c;帮助用户快速识别搜索结果中…...

Android 11(R)启动流程 初版

启动流程 bootloader会去启动android第一个进程Idle&#xff0c;pid为0&#xff0c;会对进程 内存管理等进行初始化。Idle还被称作swapper。Idle会去创建两个进程&#xff0c;一个是init&#xff0c;另外一个是kthread。 kthread会去启动内核&#xff0c;用户是由init进行启动。…...

从零安装pytorch

背景介绍 目前主流使用的工具有Facebook搞的pythorch和谷歌开发的tensorflow两种&#xff0c;二者在实现理念上有一定区别&#xff0c;pytorch和人的思维模式与变成习惯更像&#xff0c;而tensorflow则是先构建整体结构&#xff0c;然后整体运行&#xff0c;开发调试过程较为繁…...

2024.07.28 校招 实习 内推 面经

绿*泡*泡VX&#xff1a; neituijunsir 交流*裙 &#xff0c;内推/实习/校招汇总表格 1、自动驾驶一周资讯 - 特斯拉FSD年底入华&#xff1f;理想成立“端到端”实体组织&#xff1b;小马智行或最快于今年9月赴美IPO 自动驾驶一周资讯 - 特斯拉FSD年底入华&#xff1f;理想…...

python实现小游戏——植物大战僵尸(魔改版本)

制作一款DIY的‘植物大战僵尸’游戏引起了很多人的兴趣。在这里&#xff0c;我将分享一个使用Python语言在PyCharm环境中开发的初始状态版本。这个版本主要应用了pygame库来完成&#xff0c;是一个充满创意和趣味的魔改版本。 文章目录 前言一、开发环境准备二、代码1.main方法…...

基于K210智能人脸识别+车牌识别系统(完整工程资料源码)

运行效果&#xff1a; 基于K210的智能人脸与车牌识别系统工程 目录&#xff1a; 运行效果&#xff1a; 目录&#xff1a; 前言&#xff1a; 一、国内外研究现状与发展趋势 二、相关技术基础 2.1 人脸识别技术 2.2 车牌识别技术 三、智能小区门禁系统设计 3.1 系统设计方案 3.2 …...

8.怎么配嵌套子路由,以及它的作用

作用 配嵌套子路由,就是可以通过同一个页面,让不同的位置发生变化,其他的位置不会发生变化,而做到一个局部刷新 例子 红线框住的部分,头部和导航栏是不会发生变化的,变化的只有中间的内容 子路由的操作步骤 将这个页面的头部和导航栏部分的样式和风格,移到主路由上(<tem…...

【海贼王航海日志:前端技术探索】HTML你学会了吗?(二)

目录 1 -> HTML常见标签 1.1 -> 表格标签 1.1.1 -> 基本使用 1.1.2 -> 合并单元格 1.2 -> 列表标签 1.3 -> 表单标签 1.3.1 -> form标签 1.3.2 -> input标签 1.4 -> label标签 1.5 -> select标签 1.6 -> textarea标签 1.7 -> …...

体系结构论文导读(三十一)(下):Soft errors in DNN accelerators: A comprehensive review

第五部分&#xff1a;DNN加速器中的软错误 本部分回顾和分析了有关人工神经网络&#xff08;ANN&#xff09;可靠性的研究。特别是关注通过DNN加速器解决DNN可靠性的研究&#xff0c;从软错误的角度进行探讨。许多前期工作声称ANN本身对故障具有固有的容错能力。然而&#xff…...

Python在指定文件夹下创建虚拟环境

基于不同python版本和第三方包版本开发的项目&#xff0c;为了方便学习和管理python环境&#xff0c;可以在指定的文件夹里创建项目所需的虚拟环境。具体流程如下&#xff1a; (1) 以管理员身份打开Ananconda Prompt&#xff0c;查看当前虚拟环境&#xff0c;输入命令如下&…...

【SpringBoot】 定时任务之任务执行和调度及使用指南

【SpringBoot】 定时任务之任务执行和调度及使用指南 Spring框架分别通过TaskExecutor和TaskScheduler接口为任务的异步执行和调度提供了抽象。Spring还提供了支持应用程序服务器环境中的线程池或CommonJ委托的那些接口的实现。最终&#xff0c;在公共接口后面使用这些实现&…...

理解 Objective-C 中 +load 方法的执行顺序

在 Objective-C 中&#xff0c;load 方法是在类或分类&#xff08;category&#xff09;被加载到内存时调用的。它的执行顺序非常严格&#xff0c;并且在应用启动过程中可能会导致一些令人困惑的行为。理解 load 方法的执行顺序对调试和控制应用的初始化过程非常重要。 load 方…...

切面条问题算法的详解

切面条问题是一个经典的动态规划问题&#xff0c;也称为切钢条问题。问题描述为&#xff1a;给定一根长度为n的钢条和一个价格表P[i]&#xff0c;表示长度为i的钢条的价格。求解如何切割钢条使得收益最大。 解决这个问题的关键是找到一个最优子结构和递推关系。 首先&#xf…...

JNDI注入

&#x1f3bc;个人主页&#xff1a;金灰 &#x1f60e;作者简介:一名简单的大一学生;易编橙终身成长社群的嘉宾.✨ 专注网络空间安全服务,期待与您的交流分享~ 感谢您的点赞、关注、评论、收藏、是对我最大的认可和支持&#xff01;❤️ &#x1f34a;易编橙终身成长社群&#…...

SQL Server数据库文件过大而无法直接导出解决方案

目录 1. 使用分割备份 (Split Backup) 2. 使用文件和文件组备份 (File and Filegroup Backup) 3. 使用压缩备份 (Compressed Backup) 4. 逻辑备份 (BCP工具) 5. 使用导出工具 (SQL Server Management Studio) 6. 部分备份 (Partial Backup) 7. 使用第三方工具 1. 使用分割…...

学习日志8.4--DHCP攻击防范

目录 DHCP饿死攻击 DHCP Sever仿冒攻击 DHCP攻击防范 DHCP动态主机配置协议&#xff0c;是给主机提供自动获取IP地址等配置信息的服务。在主机对DHCP服务器发送DHCP Discover请求之后&#xff0c;服务器回复offer&#xff0c;主机再回复request&#xff0c;最后服务器回复AC…...

解决多个Jenkins Master实例共享Jenkins_home目录的问题(加锁解锁机制)

在Jenkins的持续集成和持续部署&#xff08;CI/CD&#xff09;环境中&#xff0c;JENKINS_HOME目录扮演着至关重要的角色。它存储了Jenkins的配置、插件、作业历史记录等核心数据。然而&#xff0c;在某些场景下&#xff0c;我们可能面临多个Jenkins master实例需要共享同一个J…...

postgresql array 反向截取

postgresql array 反向截取 array_to_string((string_to_array(REPLACE(delcell.小区网管名称,‘‘,’-‘),’-‘))[:array_length(string_to_array(REPLACE(delcell.小区网管名称,’’,‘-’),‘-’),1)-1],‘-’) as 基站名称 在PostgreSQL中&#xff0c;如果你想要对数组进…...

最新口型同步技术EchoMimic部署

EchoMimic是由蚂蚁集团推出的一个 AI 驱动的口型同步技术项目&#xff0c;能够通过人像面部特征和音频来帮助人物“对口型”&#xff0c;生成逼真的动态肖像视频。 EchoMimic的技术亮点在于其创新的动画生成方法&#xff0c;它不仅能够通过音频和面部关键点单独驱动图像动画&a…...

程序设计基础(c语言)_补充_1

1、编程应用双层循环输出九九乘法表 #include <stdio.h> #include <stdlib.h> int main() {int i,j;for(i1;i<9;i){for(j1;j<i;j)if(ji)printf("%d*%d%d",j,i,j*i);elseprintf("%d*%d%-2d ",j,i,j*i);printf("\n");}return 0…...

8.4 day bug

bug1 忘记给css变量加var 复制代码到通义千问&#xff0c;解决 bug2 这不是我的bug&#xff0c;是freecodecamp的bug 题目中“ 将 --building-color2 变量的颜色更改为 #000” “ 应改为” 将 #000 变量的颜色更改为 --building-color2 “ bug3 又忘记加var(–xxx) 还去问…...

【Material-UI】Autocomplete中的禁用选项:Disabled options

文章目录 一、简介二、基本用法三、进阶用法1. 动态禁用2. 提示禁用原因3. 复杂的禁用条件 四、最佳实践1. 一致性2. 提供反馈3. 优化性能 五、总结 Material-UI的Autocomplete组件提供了丰富的功能&#xff0c;包括禁用特定选项的能力。这一特性对于限制用户选择、提供更好的用…...

Pytest测试报告生成专题

在 pytest 中,你可以使用多个选项生成不同格式的测试报告。以下是几种常用的生成测试报告的方法: 1. 生成简单的测试结果文件 你可以使用 pytest 的 --junitxml 选项生成一个 XML 格式的测试报告,这个报告可以与 CI/CD 工具集成。 pytest --junitxml=report.xml这将在当前…...

QT 笔记

HTTPS SSL配置 下载配置 子父对象 QTimer *timer new QTimer; // QTimer inherits QObject timer->inherits("QTimer"); // returns true timer->inherits("QObject"); // returns true timer->inherits("QAbst…...

【redis 第七篇章】动态字符串

一、概述 string 类型底层实现的简单动态字符串 sds&#xff0c;是可以修改的字符串。它采用预分配冗余空间的方式来减少内存的频繁分配。 二、SDS动态字符串 动态字符串 是以 \0 为分隔符。最大容量 是 redis 主动分配的一块内存空间&#xff0c;实际存储内容 是具体的存的数…...

rk3588 部署yolov8.rknn

本文从步骤来记录在rk3588芯片上部署yolov8模型 主机&#xff1a;windows10 VMware Workstation 16 Pro 硬件&#xff1a;RK3588 EVB板 模型&#xff1a; RK3588.rknn 软件开发环境&#xff1a; c cmake step1: 主机上执行&#xff1a; 将rknn_model_zoo 工程文件下载…...