socket编程——服务器并发

 前言,本文引用了《Unix网络编程》,向作者致以 崇高的敬意。

   在前面的文章《 socket编程——TCP server编程框架分析》中,我们分析了一个典型的tcp server案例。真正使用的时候,服务器并发是经常遇到的。只能响应一个客户端的服务器叫迭代服务器,能同时 响应多个客户端访问的叫并发 服务器,服务器并发的实现要么是通过 多线程(比如JAVA的线程池)、要么是多进程,而《Unix网络编程》中显然是推荐使用多进程的。之所以推荐多进程,最大的原因在于子进程的“继承”特性,我们知道,服务器通过accept 获取客户端的套接字后,如果使用fork生成新的子进程,子进程天生就继承了父进程里的各种描述符,包括套接字,等资源,这样对特别方便了, 不需要再做任何的同步,我们以书上的案例来具体分析,下面的程序是一个 并发服务器程序框架代码,如下:

pid_t pid;
int listenfd, connfd;

listenfd = socket(.....);

/* 给监听套接字赋值 */

//绑定套接字
bind(listenfd,.....);

listen(listenfd, LISTENQ);  //最大客户端数目: LISTENQ

while( 1 ){
    connfd = accept(listenfd, .....);  
    if( (pid = fork()) == 0){          //子进程
        close(listenfd);  //子进程“关闭”了 监听套接字,
        /* 子进程处理代码 */
        doit(connfd);
        exit(0);
    }
    close(connfd);
    waitpid(.....);
    
    exit(0);
}

    上面的代码中,基本逻辑是很容易理解的,简单的讲就是,有客户端连接,就立刻生成一个子进程,然后子进程做相应响应处理,父进程 接着循环监听新的客户端。

    比较难理解的是,为什么在子进程中 关闭了父进程中 要使用的 监听套接字 listenfd,而在 父进程中要关闭客户端连接套接字。下面我们就来分析一下:

    一般我们会认为,对一个TCP套接字调用close会导致发送一个FIN,也就是终止连接,引发所谓的4次握手断开。而上面说的close为什么没有终止它与客户的连接呢,原因就在于 每个文件或套接字都有一个引用计数,所谓引用计数是在文件表项中维护,它是当前打开着的引用该文件或套接字的描述符个数,而所谓“引用”可以理解为copy,而不是简单的使用。很明显,fork函数的使用,子进程会继承父进程的套接字和描述符等,所以 起到了引用的效果。所以我们接着来梳理一下引用的顺序:

 1)socket函数创建了listenfd套接字,所以对应引用计数值为 1

 2)accept返回后与connfd关联,也可以理解成生成了connfd,所以对应引用计数值为 1

 3)fork创建子进程后,子进程会继承父进程的所有描述符,所以listenfd和connfd的引用计数都变成 2.而对于子进程来讲,监听listenfd是没有用处的,而对于父进程来讲,客户端的连接 connfd也是没有用的,所以都各自 进行close,而close只是把相应的引用计数值从2减为1,并不是直接关闭。

4)等到套接字真正的清理和资源释放后,其引用计数值到达0时,才会引发FIN。下面几张《Unix网络编程》中的图就能很好的说明问题。图片如下:

socket编程——服务器并发_第1张图片socket编程——服务器并发_第2张图片

socket编程——服务器并发_第3张图片

你可能感兴趣的:(TCP/IP)