上一次我们利用readline函数改进了回射客户/服务器,本次我们将进一步完善TCP回射客户/服务器,以及僵进程与SIGCHLD信号相关内容。
1.TCP回射客户/服务器
(1)上述第一个模型图中,并没有处理粘包问题。
(2)我们可以使用/n来区分消息与消息的边界。因此,实现第二个模型图更加合理。(上一篇博文已经总结了readn,writen以及readline的封装)
2.TCP是个流协议
(1)TCP是基于字节流传输的,只维护发送出去多少,确认了多少,没有维护消息与消息之间的边界,因而可能导致粘包问题。
(2)粘包问题解决方法是在应用层维护消息边界。
3.SIGCHLD信号与僵进程
SIGCHLD信号就是由内核在任何一个进程终止时发给它的父进程的一个信号。
设置僵死(zombie)状态的目的是维护子进程的信息,以便父进程在以后某个时候获取。这些信息包括子进程的进程ID、终止状态以及资源利用信息等等。
- signal(SIGCHLD, SIG_IGN)
- signal(SIGCHLD, handle_sigchld)
(1)当客户端关闭之后,服务器端还在维护着一个僵尸进程。对此,我们可以通过在服务器端加入如下代码,忽略SIGCHLD信号:
signal(SIGCHLD, SIG_IGN);
(2)另外一种方法是,我们可以捕捉SIGCHLD信号,来避免僵尸进程。
1 2 3 4 5 6 |
void handle_sigchld(int sig) { wait(NULL); } signal(SIGCHLD, handle_sigchld); |
1)其中wait函数用来捕获子进程的状态,其中退出状态不关心就写为NULL。
1 2 3 4 5 |
#include <sys/types.h> #include <sys/wait.h> pid_t wait(int *status); pid_t waitpid(pid_t pid, int *status, int options); |
2)但是使用wait函数会有一个问题,如果有多个子进程同时退出,并不能去等待所有的子进程退出。因为wait仅仅等待第一个子进程退出就返回了。这个时候就需要用到waitpid函数。
(3)考虑如下示例:5个并发连接
1)客户端创建5个套接字与服务端相连。(为简单起见,下面代码只选择一个套接口sock[0]与服务端通信)。当客户端退出时,会有5个子进程都退出。
2)那么对服务端来说,开辟的5个子进程都向父进程发送了SIGCHLD信号。但是wait函数只等待一个子进程,其他4个子进程处于僵尸状态。
3)针对这种情况就需要使用waitpid函数
1 2 3 4 5 6 7 8 |
void handle_sigchld(int sig) { //wait(NULL); while((pid = waitpid(-1, NULL, WNOHANG)) > 0) printf("child %d terminated\n", pid); } signal(SIGCHLD, handle_sigchld); |
其中,值-1表示等待第一个终止的子进程。WNOHANG选项表示,它告知内核在灭有已终止子进程时不要阻塞。
这样就避免了僵尸进程。