父进程和子进程
fork 函数实现的时候,实际上会把当前父进程的所有相关值都克隆一份,包括地址空间、打开的文件描述符、程序计数器等,就连执行代码也会拷贝一份,新派生的进程的表现行为和父进程近乎一样,就好像是派生进程调用过 fork 函数一样。为了区别两个不同的进程,实现者可以通过改变 fork 函数的栈空间值来判断,对应到程序中就是返回值的不同。
if (fork() == 0) {
do_child_process();//子进程执行代码
} else {
do_parent_process();//父进程执行代码
}
当一个子进程退出时,如果不回收,就会变成僵尸进程。占用不必要的内存空间和系统资源
有两种方式可以在子进程退出后回收资源
pid_t wait(int *statloc);
pid_t waitpid(pid_t, int *statloc, int options);
- 函数返回值,表示已终止子进程的进程 ID 号,
- statloc指针返回子进程终止的实际状态,这个状态可能的值为正常终止、被信号杀死、作业控制停止等。
如果没有已终止的子进程,而是有一个或多个子进程在正常运行,那么 wait 将阻塞(pid值-1),直到第一个子进程终止。
处理子进程退出方式一般是注册一个信号处理函数,捕捉信号SIGCHILD信号,然后再在信号处理函数里调用waitpid函数来完成子进程资源的回收
signal(SIGCHLD, sigchld_handler);
阻塞 I/O 的进程模型
我们假设有两个客户端,服务器初始监听在套接字 lisnted_fd 上。当第一个客户端发起连接请求,连接建立后产生出连接套接字,此时,父进程派生出一个子进程,在子进程中,使用连接套接字和客户端通信,因此子进程不需要关心监听套接字,只需要关心连接套接字;父进程则相反,将客户服务交给子进程来处理,因此父进程不需要关心连接套接字,只需要关心监听套接字。
假设父进程之后又接收了新的连接请求,从 accept 调用返回新的已连接套接字,父进程又派生出另一个子进程,这个子进程用第二个已连接套接字为客户端服务。
现在,服务器端的父进程继续监听在套接字上,等待新的客户连接到来;两个子进程分别使用两个不同的连接套接字为两个客户服务。
总结
使用阻塞I/O和进程模型,创建每一个独立的子进程来进行服务,是非常简单有效的实现方式,但可能很难满足高性能程序的需求。
- 要注意对套接字的关闭梳理
- 注意对子进程回收,避免产生僵尸进程