并发服务器

pid_t pid;
    int listenfd,connfd;
    listenfd=socket(.....);
    bind(listenfd,....);
    listen(listenfd,LISTENQ);
    for(;;)
    {
        connfd=accept(listenfd,...);
        if((pid=fork())==0)
        {
            close(listenfd); /* child closes listening sockfd */
            doit(connfd);    /* process the request */
            close(connfd);   /* done with this client */
            exit(0);         /* child terminates */
        }
        close(connfd);       /* parent close connected sockfd */
    }

  当一个连接建立时,accept返回,服务器接着fork,然后由子进程服务客户(通过已连接套接字connfd),父进程则等待另一个连接。既然新的客户由子进程服务,父进程就关闭已连接套接字。

  doit是我们假设执行服务客户所需要的所有操作。当该函数返回时,我们在子进程中显式的关闭已连接套接字。这一点并非必要,因为下一个语句就是exit,而进程终止处理的部分工作就是关闭所有由内核打开的描述符。

  一个TCP套接字调用close会导致发送一个FIN,随后是正常的TCP连接终止序列。但是上述代码中父进程队connfd调用close没有终止他与客户的连接。每个文件或套接字都有一个引用计数。引用计数在文件表项中维护,它是当打开着的引用文件或套接字的描述符的个数。上述代码中,socket返回与listenfd关联的文件表项引用计数值为1.accept返回后与connfd关联的文件表项的引用计数值也为1.然而fork返回后,这两个描述符就在父进程与子进程间共享,因此与这两个套接字相关联的文件表项各自的访问计数值均为2.这么一来,当父进程关闭connfd是,他只是把相应的引用计数从2减为1.该套接字真正的清理和资源释放要等到其引用计数值达到0时才发生。这会在稍后子进程也关闭connfd时发生。

  上述代码我们用图示演示:

 

         服务器阻塞于accept调用且来自客户的请求到达时客户和服务器的状态

 

 

   从accept返回后,我们立即有了上图的状态,连接被内核接受,新的套接字conned被创建。这是一个已连接套接字,可由此跨连接读写数据。

 

 

   并发服务器的下雨不是调用fork

 

 

   此时listened和conned这两个描述符都在父进程和子进程之间共享(被复制)

  在下一步就是由父进程关闭已连接套接字,由子进程关闭监听套接字

    这是这两个套接字所期望的最终状态,子进程处理与客户的连接,父进程则可以在监听套接字上再次调用accept来处理下一个客户连接。

  

  #include <unistd.h>

  int close(int sockfd);

    返回:成功返回0,出错返回-1

  close一个TCP套接字的默认行为是吧该套接字标记成已关闭,然后立即返回到调用进程。该套接字描述符不能在由调用进程使用,有就是说不能再作为read或write的第一个参数。

  然而TCP将尝试发送已排队等待发送到对端的任何数据,发送完毕后发生的是正常TCP连接终止序列。

  

 

posted @ 2022-08-05 11:47  撒西不纳拉  阅读(69)  评论(0)    收藏  举报