在UNP
的第30章客户/服务器程序设计范式
中提到了这种模型.
主要的思想
这种模型的思想非常简单,具体来说,就是,没当用户connect
到来之后,立马fork
一个子进程去处理连接,代码如下:
int main(int argc, char *argv[])
{
int listenfd = Open_listenfd(8080); /* 8080号端口监听 */
while (true) /* 无限循环 */
{
struct sockaddr_in clientaddr;
socklen_t len = sizeof(clientaddr);
int connfd = Accept(listenfd, (SA*)&clientaddr, &len);
addsig(SIGCHLD, sig_chld); /* 添加信号处理函数 */
int pid;
if ((pid = Fork()) == 0) /* 子线程 */
{
printf("\nnew process:%d\n", getpid());
close(listenfd);
doit(connfd); /* 处理连接 */
close(connfd); /* 关闭socket描述符 */
printf("\nend of process:%d\n", getpid());
exit(0);
}
close(connfd);
}
return 0;
}
借用stevens
老爷子的一张图,应该是这么干的:
首先客户端通过conncect
函数请求和服务器的连接.
接下来服务器调用
Accept
函数接收了对方的连接,connfd
指代这个连接.然后父进程调用
fork
函数生成了一个子进程.生成的子进程里面的数据和父进程是一模一样的,它也包含了这个connfd
,所以现在客户和两个进程连接.父进程关闭这个连接,然后由子进程来处理连接,这样一个交互就完成了.
需要注意的地方
僵死进程
子进程运行完毕之后,如果父进程不调用wait
或者waitpid
函数来处理处理的话,这些已经运行完成的子进程便成为了僵死进程,僵死进程会占用系统的资源,小部分的僵死进程还好,不过像我们这种需要长时间运行的服务器程序,点滴的僵死进程积累起来,这个量是非常恐怖的.所以对于僵死进程,我们是不能够忍受的.你可能会奇怪,为什么子进程运行完了还要父进程来收尸,直接释放资源不就好了吗?其实操作系统这么安排必定有它的道理,只是我们暂时用不到罢了.
设置僵死状态的目的是维护子进程的信息,以便父进程在以后某个时候获取,这些信息包括子进程的进程ID,终止状态以及资源利用信息(cpu时间,内存使用量等等).如果一个进程终止,而该进程有子进程处于僵死状态,那么它的所有僵死进程的父进程ID将会重置为1(init进程).继承这些子进程的init进程将清理他们(也就是说,init进程将wait它们,从而去除它们的僵死进程).
值得我们注意的是,当一个子进程运行完毕之后,系统会给父进程发送一个SIGCHLD
信号,只要我们处理这个信号,就能够及时地回收子进程占用的一些资源.
所以在代码的第10行处addsig(SIGCHLD, sig_chld);
,我们添加了SIGCHLD
的信号处理函数.
函数如下:
void
sig_chld(int signo) /* 处理僵死进程 */
{
pid_t pid;
int stat;
while ((pid = waitpid(-1, &stat, WNOHANG)) > 0)
printf("child %d terminated\n", pid);
return;
}
waitpid
函数的第一个参数-1
表示等待第一个终止的子进程,&stat
可以获取终止进程的一些信息,而WNOHANG
表示不用阻塞,不管有没有子进程终止,立即返回,没有子进程终止的话,会返回一个小于0
的数,否则的话返回的是一个大于0
的数.为什么要用while
循环呢?很简单,因为系统是有子进程终止的时候才通知我们,不会因为僵死的子进程还未处理而同通知我们,所以我们要一次性将已经挂掉的子进程全部处理掉才行.
总结
这次的代码也非常简单,但是性能比之前的要强上不少.当然,这个能够同时处理的连接数目取决于系统可以生成的进程的数目.这次的代码是多进程版本的,下次的代码,我们就要编程多线程版本的啦.
代码可以在这里查看:https://github.com/lishuhuakai/Spweb
网友评论