• socket服务器并发处理


    我们知道,服务器通常是要同时服务多个客户端的,如果我们运行上一篇实现的server和client之后,再开一个终端运行client试试,新的client就不能能得到服务了。因为服务器之支持一个连接。

    网络服务器通常用fork来同时服务多个客户端,父进程专门负责监听端口,每次accept一个新的客户端连接就fork出一个子进程专门服务这个客户端。但是子进程退出时会产生僵尸进程,父进程要注意处理SIGCHLD信号和调用wait清理僵尸进程。

    下面是代码框架:

    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. listenfd = socket(...);  
    2. bind(listenfd, ...);  
    3. listen(listenfd, ...);  
    4. while (1) {  
    5.          connfd= accept(listenfd, ...);  
    6.          n= fork();  
    7.          if(n == -1) {  
    8.                    perror("callto fork");  
    9.                    exit(1);  
    10.          }else if (n == 0) {  
    11.                    close(listenfd);  
    12.                    while(1) {  
    13.                             read(connfd,...);  
    14.                             ...  
    15.                             write(connfd,...);  
    16.                    }  
    17.                    close(connfd);  
    18.                    exit(0);  
    19.          }else  
    20.                    close(connfd);  
    21. }  


     

    现在做一个测试,首先启动server,然后启动client,然后用Ctrl-C使server终止,这时马上再运行server,结果是:

     binderror: Address already in use

    这是因为,虽然server的应用程序终止了,但TCP协议层的连接并没有完全断开,因此不能再次监听同样的server端口。server终止时,socket描述符会自动关闭并发FIN段给client,client收到FIN后处于CLOSE_WAIT状态,但是client并没有终止,也没有关闭socket描述符,因此不会发FIN给server,因此server的TCP连接处于FIN_WAIT2状态。

    现在用Ctrl-C把client也终止掉,再观察现象结果是:

     binderror: Address already in useclient

    终止时自动关闭socket描述符,server的TCP连接收到client发的FIN段后处于TIME_WAIT状态。TCP协议规定,主动关闭连接的一方要处于TIME_WAIT状态,等待两个MSL(maximum segment lifetime)的时间后才能回到CLOSED状态,因为我们先Ctrl-C终止了server,所以server是主动关闭连接的一方,在TIME_WAIT期间仍然不能再次监听同样的server端口。MSL在RFC1122中规定为两分钟,但是各操作系统的实现不同,在Linux上一般经过半分钟后就可以再次启动server了。

    在server的TCP连接没有完全断开之前不允许重新监听是不合理的,因为,TCP连接没有完全断开指的是connfd(127.0.0.1:8000)没有完全断开,而我们重新监听的是listenfd(0.0.0.0:8000),虽然是占用同一个端口,但IP地址不同,connfd对应的是与某个客户端通讯的一个具体的IP地址,而listenfd对应的是wildcard address。解决这个问题的方法是使用setsockopt()设置socket描述符的选项SO_REUSEADDR为1,表示允许创建端口号相同但IP地址不同的多个socket描述符。在server代码的socket()和bind()调用之间插入如下代码: 

    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
    1. int opt = 1;  
    2. setsockopt(listenfd, SOL_SOCKET,SO_REUSEADDR, &opt, sizeof(opt));  


    select是网络程序中很常用的一个系统调用,它可以同时监听多个阻塞的文件描述符(例如多个网络连接),哪个有数据到达就处理哪个,这样,不需要fork和多进程就可以实现并发服务的server。

    [cpp] view plain copy
     
     在CODE上查看代码片派生到我的代码片
      1. /* server.c */  
      2. #include <stdio.h>  
      3. #include <stdlib.h>  
      4. #include <string.h>  
      5. #include <netinet/in.h>  
      6. #include "wrap.h"  
      7.    
      8. #define MAXLINE 80  
      9. #define SERV_PORT 8000  
      10.    
      11. int main(int argc, char **argv)  
      12. {  
      13.          inti, maxi, maxfd, listenfd, connfd, sockfd;  
      14.          intnready, client[FD_SETSIZE];  
      15.          ssize_tn;  
      16.          fd_setrset, allset;  
      17.          charbuf[MAXLINE];  
      18.          charstr[INET_ADDRSTRLEN];  
      19.          socklen_tcliaddr_len;  
      20.          structsockaddr_in  cliaddr, servaddr;  
      21.    
      22.          listenfd= Socket(AF_INET, SOCK_STREAM, 0);  
      23.    
      24.          bzero(&servaddr,sizeof(servaddr));  
      25.          servaddr.sin_family      = AF_INET;  
      26.          servaddr.sin_addr.s_addr= htonl(INADDR_ANY);  
      27.          servaddr.sin_port        = htons(SERV_PORT);  
      28.    
      29.          Bind(listenfd,(struct sockaddr *)&servaddr, sizeof(servaddr));  
      30.    
      31.          Listen(listenfd,20);  
      32.    
      33.          maxfd= listenfd;               /* initialize */  
      34.          maxi= -1;                            /* indexinto client[] array */  
      35.          for(i = 0; i < FD_SETSIZE; i++)  
      36.                   client[i] = -1;     /* -1 indicates available entry */  
      37.          FD_ZERO(&allset);  
      38.          FD_SET(listenfd,&allset);  
      39.    
      40.          for( ; ; ) {  
      41.                    rset= allset;    /* structure assignment */  
      42.                    nready= select(maxfd+1, &rset, NULL, NULL, NULL);  
      43.                    if(nready < 0)  
      44.                             perr_exit("selecterror");  
      45.    
      46.                    if(FD_ISSET(listenfd, &rset)) { /* new client connection */  
      47.                             cliaddr_len= sizeof(cliaddr);  
      48.                             connfd= Accept(listenfd, (struct sockaddr *)&cliaddr, &cliaddr_len);  
      49.    
      50.                             printf("receivedfrom %s at PORT %d ",  
      51.                                    inet_ntop(AF_INET, &cliaddr.sin_addr,str, sizeof(str)),  
      52.                                    ntohs(cliaddr.sin_port));  
      53.    
      54.                             for(i = 0; i < FD_SETSIZE; i++)  
      55.                                      if(client[i] < 0) {  
      56.                                                client[i]= connfd; /* save descriptor */  
      57.                                                break;  
      58.                                      }  
      59.                             if(i == FD_SETSIZE) {  
      60.                                      fputs("toomany clients ", stderr);  
      61.                                      exit(1);  
      62.                             }  
      63.    
      64.                             FD_SET(connfd,&allset);         /* add newdescriptor to set */  
      65.                             if(connfd > maxfd)  
      66.                                      maxfd= connfd; /* for select */  
      67.                             if(i > maxi)  
      68.                                      maxi= i;   /* max index in client[] array */  
      69.    
      70.                             if(--nready == 0)  
      71.                                      continue; /* no more readable descriptors */  
      72.                    }  
      73.    
      74.                    for(i = 0; i <= maxi; i++) { /* check allclients for data */  
      75.                             if( (sockfd = client[i]) < 0)  
      76.                                      continue;  
      77.                             if(FD_ISSET(sockfd, &rset)) {  
      78.                                      if( (n = Read(sockfd, buf, MAXLINE)) == 0) {  
      79.                                                /*connection closed by client */  
      80.                                                Close(sockfd);  
      81.                                                FD_CLR(sockfd,&allset);  
      82.                                                client[i]= -1;  
      83.                                      }else {  
      84.                                                intj;  
      85.                                                for(j = 0; j < n; j++)  
      86.                                                         buf[j]= toupper(buf[j]);  
      87.                                                Write(sockfd,buf, n);  
      88.                                      }  
      89.    
      90.                                      if(--nready == 0)  
      91.                                                break;       /* no more readable descriptors */  
      92.                             }  
      93.                    }  
      94.          }  
      95. }  
  • 相关阅读:
    python编程学习进度七
    python编程学习进度六
    SOA——2020.5.15
    代码大全001/
    Refined Architecture阶段——细化架构
    架构即未来003(摘自网络)
    我对外包公司的小小看法
    架构即未来002
    每日日报
    架构即未来阅读笔记001
  • 原文地址:https://www.cnblogs.com/zhangbing12304/p/8028707.html
Copyright © 2020-2023  润新知