服务器端预先创建子进程(work)同时监听服务端口和惊群现象

原文链接: http://blog.csdn.net/ordeder/article/details/21721141

1.进程A在n端口上监听,即调用listen(listenfd,backlog);

2.之后A调用fork产生子进程B,此时B拷贝了A的listenfd,该描述符使用的是相同的“文件表项”(具体参考 http://blog.csdn.net/ordeder/article/details/21716639)

3.那么A进程和B进程将共享一个socket,具体图解如下:


惊群现象        

        在该模型下(多个子进程同时共享监听套接字)即可实现服务器并发处理客户端的连接。这里要注意的是,计算机三次握手创建连接是不需要服务进程参数的,而服务进程仅仅要做的事调用accept将已建立的连接构建对应的连接套接字connfd(可参考 http://blog.csdn.net/ordeder/article/details/21551567)。多个服务进程同时阻塞在accept等待监听套接字已建立连接的信息,那么当内核在该监听套接字上建立一个连接,那么将同时唤起这些处于accept阻塞的服务进程,从而导致“惊群现象”的产生,唤起多余的进程间影响服务器的性能(仅有一个服务进程accept成功,其他进程被唤起后没抢到“连接”而再次进入休眠)。


实例分析

服务进程:三个服务进程同时监听一个端口,代码如下:

[cpp]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. #include<stdio.h>  
  2. #include<unistd.h>  
  3. #include<sys/types.h>       /* basic system data types */  
  4. #include<sys/socket.h>      /* basic socket definitions */  
  5. #include<netinet/in.h>      /* sockaddr_in{} and other Internet defns */  
  6. #include<arpa/inet.h>       /* inet(3) functions */  
  7. #include<sys/epoll.h>           /* epoll function */  
  8. #include<fcntl.h>  
  9. #include<stdlib.h>  
  10. #include<errno.h>  
  11. #include<stdio.h>  
  12. #include<string.h>  
  13. #include<sys/select.h>  
  14.   
  15. #define WORKERSIZE 3  
  16.   
  17. void waitall()  
  18. {  
  19.         pid_t cpid;  
  20.         while(1)  
  21.         {  
  22.            cpid = wait(NULL);  
  23.            if(cpid==-1){  
  24.            perror("end of wait");  
  25.                break;  
  26.            }  
  27.            printf("worker pid#%d exit...\n",cpid);  
  28.         }  
  29. }  
  30.   
  31. void worker_hander(int listenfd)  
  32. {  
  33.         fd_set rset;  
  34.         int cnt = 100,connfd,rc;  
  35.         struct timeval tv;  
  36.         tv.tv_sec = 0;  
  37.         tv.tv_usec=0;  
  38.   
  39.         printf("worker pid#%d is waiting for connection...\n",getpid());  
  40.   
  41.         while(1)  
  42.         {  
  43.                 FD_ZERO(&rset);  
  44.                 FD_SET(listenfd,&rset);  
  45.   
  46.                 //rc = select(listenfd+1,&rset,NULL,NULL,&tv);//设置为非阻塞状态  
  47.         rc = select(listenfd+1,&rset,NULL,NULL,NULL);//设置为阻塞  
  48.   
  49.                 if(rc == -1) perror("select");  
  50.         else if(rc>0 && FD_ISSET(listenfd,&rset))  
  51.                 {  
  52.             //sleep(1);//第四种,让三个进程都有足够的时间资源唤起(防止可能出现某个进程已近开始进行accept结束了,另一个进程还未被唤起(调度的问题))  
  53.                         printf("worker pid#%d 's listenfd is readable\n",getpid(),rc);  
  54.                         connfd = accept(listenfd,NULL,0);  
  55.                         if(connfd == -1)  
  56.                         {  
  57.                                 perror("accept error");  
  58.                                 continue;  
  59.                         }  
  60.                         printf("worker pid#%d create a new connection...\n",getpid());  
  61.                         sleep(1);  
  62.                         close(connfd);  
  63.                 }  
  64.         }  
  65. }  
  66.   
  67. int main(int argc,char*argv[])  
  68. {  
  69.         int listenfd,connfd;  
  70.         struct sockaddr_in cliaddr,servaddr;  
  71.         int queuelen=5,i,flag;  
  72.         pid_t cpid[WORKERSIZE];  
  73.   
  74.         listenfd = socket(AF_INET,SOCK_STREAM,0);  
  75.   
  76.     //此处设置listenfd的为阻塞  
  77.         /*flag = fcntl(listenfd,F_GETFL,0); 
  78.         fcntl(listenfd,F_SETFL,flag|O_NONBLOCK);*/  
  79.   
  80.         bzero(&servaddr,sizeof(servaddr));  
  81.         servaddr.sin_family = AF_INET;  
  82.         inet_pton(AF_INET,"172.20.52.140",&servaddr.sin_addr);  
  83.         //servaddr.sin_addr.s_addr = htonl(INADDR_ANY);  
  84.         servaddr.sin_port = htons(2989);  
  85.   
  86.         bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr));  
  87.   
  88.         listen(listenfd,queuelen);  
  89.   
  90.         for(i=0;i<WORKERSIZE;i++)  
  91.         {  
  92.                 cpid[i]=fork();  
  93.                 if(cpid[i] == -1){  
  94.                         perror("fork error");  
  95.                         waitall();  
  96.                         exit(0);  
  97.                 }  
  98.                 if(cpid[i]==0){  
  99.                         worker_hander(listenfd);  
  100.                         exit(0);  
  101.                 }  
  102.         }  
  103.         waitall();  
  104.     return 0;  
  105. }  

不同设置的结果输出:

一个客户端发起链接,而服务端有三个服务进程在监听同一端口,从而内核会唤起所有处于监听该端口的服务进程,从而导致惊群现象的发生。服务端设置不通的阻塞情况,可得如下不同的结果:

[html]  view plain  copy
  在CODE上查看代码片 派生到我的代码片
  1. 一个客户端发起链接,而服务端有三个服务进程在监听同一端口。服务端设置不通的阻塞情况,可得如下不同的结果。  
  2. 1.select设置为阻塞,listenfd设置为阻塞或非阻塞,结果如下:  
  3. worker pid#25583 is waiting for connection...  
  4. worker pid#25584 is waiting for connection...  
  5. worker pid#25585 is waiting for connection...  
  6. worker pid#25585 's listenfd is readable  
  7. worker pid#25585 create a new connection...  
  8. 分析:三个服务进程被唤起,25585第一个调度所以执行accept,所以此时连接被取走,其他两个进程之后开始被调度,而此时连接数目为0,又进入随眠...  
  9.   
  10. 2.select设置为非阻塞,listenfd设置为阻塞,结果如下:  
  11. worker pid#25743 is waiting for connection...  
  12. worker pid#25744 is waiting for connection...  
  13. worker pid#25745 is waiting for connection...  
  14. worker pid#25745 's listenfd is readable  
  15. worker pid#25744 's listenfd is readable  
  16. worker pid#25743 's listenfd is readable  
  17. worker pid#25745 create a new connection...  
  18. 分析:三个服务进程都select都成功返回可读的套接字,从而各个进程都唤起处于阻塞的accept,但是只有一个进程建立链接,其余两个进程没有获取到链接而又进入睡眠...  
  19.   
  20. 3.select设置为非阻塞,listenfd设置为为非阻塞,结果如下:  
  21. worker pid#25240 is waiting for connection...  
  22. worker pid#25241 is waiting for connection...  
  23. worker pid#25242 is waiting for connection...  
  24. worker pid#25242 's listenfd is readable  
  25. worker pid#25240 's listenfd is readable  
  26. worker pid#25241 's listenfd is readable  
  27. worker pid#25242 create a new connection...  
  28. accept error: Resource temporarily unavailable  
  29. accept error: Resource temporarily unavailable  
  30. 分析:三个服务进程都进入accept,但是只有一个进程获取到链接,其他两个进程没有获取到链接而出错  
  31.   
  32. 4.在select设置为阻塞,select和accept之间添加sleep(1),accept的套接字为非阻塞,结果如下:  
  33. worker pid#30689 is waiting for connection...  
  34. worker pid#30690 is waiting for connection...  
  35. worker pid#30691 is waiting for connection...  
  36. worker pid#30691 's listenfd is readable  
  37. worker pid#30691 create a new connection...  
  38. worker pid#30690 's listenfd is readable  
  39. worker pid#30689 's listenfd is readable  
  40. accept error: Resource temporarily unavailable  
  41. accept error: Resource temporarily unavailable  
  42. 分析:三个进程被唤起后,由于有sleep(1),三个进程都被调度了,所以select都能查找到连接,故而从select返回,但是在accept处,只有一个进程得到了连接  
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值