- 套接字通信
- socket 接口
- 守护进程
一.套接字通信
端口号:
- 端口号是一个2字节16位的整数;
- 端口号用来标识一个进程, 告诉操作系统, 当前的这个数据要交给哪一个进程来处理;
一台主机可以根据ip地址定位另一台主机,而两台主机之间的通信本质是进程在通信。根据ip地址我们可以定位一台主机,而根据端口号(port)可以定位一台主机上的一个进程。这样世界上任何一个进程都可以通过IP地址+端口号来定位。【源ip+源port ----》 目的ip+目的port】这样的通信方式称为套接字通信。
采用端口号而不是pid来标识进程的原因是降低网络和进程管理的耦合度,同时也更好的区分了网络进程。一个端口号只能对应一个进程,但是一个进程可以对应多个端口号。
套接字通信主要编写的是应用层的程序,使用os提供的网络部分的系统调用进行发送和接收消息。
二.socket 接口
udp协议特点:
- 无连接
- 不可靠传输
- 面向数据报
tcp协议特点:
- 有连接
- 可靠传输
- 面向字节流
2.1 udp协议相关的接口
- 创建socket文件
- 返回值:返回一个文件描述符,后续输入输出操作文件描述符
- domain:由于套接字通信可以进行网络通信,也可以进行本地通信,因此这个参数用来确定通信域
- AF_INET:网络通信
- AF_UNIX:本地通信
- type:表示通信类别是面向字节流还是面向数据报
- SOCK_DGRAM: 面向数据报
- SOCK_STREAM:面向字节流
- protocol:默认0即可,os会自动根据type来确定协议是tcp还是udp
- 绑定socket
- 返回值:bind成功返回0,失败返回-1
- sockfd:socket文件描述符
- addrlen:addr对象的实际大小
- addr:将端口号和ip地址 与当前进程绑定
addr中实际传入的对象是sockadd_in
或者sockaddr_un
,封装为sockaddr
只是为了统一参数类型。因为传入的参数类型为sockaddr
,所以需要根据首部16位来确定真实的参数类型。
- 如果传入的参数是
sockaddr_in
实例化后的对象,那么表示为网络通信,需要指明ip地址和端口号- 由于通信双方的字节序(大端/小端)有可能不同,所以网络中规定,发送到网络的数据都以大端方式存储。转换函数库中已经提供
- uint32_t htonl(uint32_t hostlong) 将32位主机序列转换位32位网络序列
- uint32_t ntohl(uint32_t netlong) 将32位网络序列转换位32位主机序列
- uint16_t ntohs(uint16_t netlong)
- uint16_t htons(uint16_t hostlong)
- 其中h:host主机 n:net网络 l:long s:short
- 如果传入的参数是
sockaddr_un
实例化后的对象,那么表示为本地通信
sockaddr_in
在头文件netinet/in.h 和 arpa/inet.h 中
注意:
- 服务器绑定的ip地址最好由os决定:使用INADDR_ANY填入参数
- 客户端的绑定由os在合适的时候完成,程序员不需要自己绑定,防止出现端口冲突问题。
- 接收消息
- 返回值:成功返回读取到的字节数,失败返回-1
- sockfd:文件描述符
- buf:要接收数据的缓存区
- len:要接收数据的长度
- flags:设置为0即可
- src_addr:输出型参数,会设置为发送方的ip地址和端口号
- addrlen:src_addr指针指向对象的长度,一定要初始化
- 发送消息
- sockfd:文件描述符
- buf:发送数据缓存区
- len:发送数据缓冲区的大小
- dest_addr:填写对端的ip地址和端口号
- addrlen:dest_addr所指向对象的大小
2.2 tcp协议相关的接口
- 创建socket文件-同上
- 绑定socket-同上
- 监听listen
- 功能:将sockfd文件设置为监听状态,使之有能力接收来自客户端的连接
- sockfd:要设置为监听状态的文件描述符
- backlog:tcp协议在底层维护的全连接队列,最大长度是:backlog+1,如果全连接队列满了,其他到来的连接会在半连接队列中,这个值不能大,否则会导致客户端长时间得不到响应,降低用户体验
- 返回值:成功返回0,失败-1
- accept服务器接收连接
- 如果服务器成功接收来自客户端的连接,那么将创建一个socket文件用于和这个客户端传输数据
- 返回值:返回一个新建的socket文件描述符
- sockfd:传入调用
socket()
函数创建的监听套接字文件描述符 ,这两个文件描述符的作用不同,一个是为了接收来自客户端的连接,一个是与客户端进行传输数据 - addr:保存的是客户端的ip地址+port
- addrlen:addr指向对象的实际大小
- connet客户端发起连接
- 返回值:成功返回0,失败返回-1
- sockfd:客户端创建的socket文件描述符
- addr:传入要连接的服务器的ip地址和端口号
- addrlen:addr指向对象的大小
- 读数据/写数据
- 由于tcp协议是面向字节流的,所以可以用文件的接口进行读写
三.守护进程
在linux中,使用ps axj
可以查看运行中的进程信息。在命令行中输入sleep 1000 | sleep 100000 | sleep 2000 &
然后使用ps axj | head -1 && ps axj | grep sleep
查看sleep进程的信息。
- PPID:当前进程的父进程的id
- PID:当前进程的id
- PGID:进程组id, 一条指令对应一个进程组。比如:前面3个
sleep
在一条指令中,所以这三个进程为一个组,PGID为进程组的组长id - SID:会话id,在xshell中,一个输入框对应一个会话,下面为两个会话窗口
- TTY:终端,一个会话对应一个终端,程序的打印结果是输出到终端上的。
会话,进程组,进程的关系:
一个会话内有许多进程组,一个进程组内有许多进程。
如果我们关闭一个会话,那么会话内部的进程将全部退出。众所周知,服务器是需要一直运行的,所以服务器不能用用户进行命令行解释的会话来运行,而需要单独成一个会话,这个会话不能轻易退出。单独成一个会话的进程叫做守护进程。
守护进程需要满足的条件:
- 不能是原来进程组的组长
- 进程路径可能会更改
- 文件描述符0 1 2 需要特殊处理,重定向到/dev/null 文件
- 需要忽略一些信号
- 需要调用
setsid()
创建会话
代码模拟:
凡是调用该函数的进程都将变为守护进程,除非调用kill
,否则不会退出。
void daemon()
{// 特殊处理信号signal(SIGPIPE, SIG_IGN);signal(SIGCHLD, SIG_IGN);// 保证自己不是组长if (fork() > 0)exit(0);// 创建会话pid_t sid = setsid();// 成功返回会话id 失败返回-1if (sid < 0){exit(1);}// 调用chdir()更改路径// 特殊处理0 1 2int fd = open("/dev/null", O_RDWR);if (fd == -1){exit(2);}dup2(fd, 0);dup2(fd, 1);dup2(fd, 2);close(fd);
}