原文链接: 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成功,其他进程被唤起后没抢到“连接”而再次进入休眠)。
实例分析
- #include<stdio.h>
- #include<unistd.h>
- #include<sys/types.h> /* basic system data types */
- #include<sys/socket.h> /* basic socket definitions */
- #include<netinet/in.h> /* sockaddr_in{} and other Internet defns */
- #include<arpa/inet.h> /* inet(3) functions */
- #include<sys/epoll.h> /* epoll function */
- #include<fcntl.h>
- #include<stdlib.h>
- #include<errno.h>
- #include<stdio.h>
- #include<string.h>
- #include<sys/select.h>
- #define WORKERSIZE 3
- void waitall()
- {
- pid_t cpid;
- while(1)
- {
- cpid = wait(NULL);
- if(cpid==-1){
- perror("end of wait");
- break;
- }
- printf("worker pid#%d exit...\n",cpid);
- }
- }
- void worker_hander(int listenfd)
- {
- fd_set rset;
- int cnt = 100,connfd,rc;
- struct timeval tv;
- tv.tv_sec = 0;
- tv.tv_usec=0;
- printf("worker pid#%d is waiting for connection...\n",getpid());
- while(1)
- {
- FD_ZERO(&rset);
- FD_SET(listenfd,&rset);
- //rc = select(listenfd+1,&rset,NULL,NULL,&tv);//设置为非阻塞状态
- rc = select(listenfd+1,&rset,NULL,NULL,NULL);//设置为阻塞
- if(rc == -1) perror("select");
- else if(rc>0 && FD_ISSET(listenfd,&rset))
- {
- //sleep(1);//第四种,让三个进程都有足够的时间资源唤起(防止可能出现某个进程已近开始进行accept结束了,另一个进程还未被唤起(调度的问题))
- printf("worker pid#%d 's listenfd is readable\n",getpid(),rc);
- connfd = accept(listenfd,NULL,0);
- if(connfd == -1)
- {
- perror("accept error");
- continue;
- }
- printf("worker pid#%d create a new connection...\n",getpid());
- sleep(1);
- close(connfd);
- }
- }
- }
- int main(int argc,char*argv[])
- {
- int listenfd,connfd;
- struct sockaddr_in cliaddr,servaddr;
- int queuelen=5,i,flag;
- pid_t cpid[WORKERSIZE];
- listenfd = socket(AF_INET,SOCK_STREAM,0);
- //此处设置listenfd的为阻塞
- /*flag = fcntl(listenfd,F_GETFL,0);
- fcntl(listenfd,F_SETFL,flag|O_NONBLOCK);*/
- bzero(&servaddr,sizeof(servaddr));
- servaddr.sin_family = AF_INET;
- inet_pton(AF_INET,"172.20.52.140",&servaddr.sin_addr);
- //servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
- servaddr.sin_port = htons(2989);
- bind(listenfd,(struct sockaddr*)&servaddr,sizeof(servaddr));
- listen(listenfd,queuelen);
- for(i=0;i<WORKERSIZE;i++)
- {
- cpid[i]=fork();
- if(cpid[i] == -1){
- perror("fork error");
- waitall();
- exit(0);
- }
- if(cpid[i]==0){
- worker_hander(listenfd);
- exit(0);
- }
- }
- waitall();
- return 0;
- }
不同设置的结果输出:
一个客户端发起链接,而服务端有三个服务进程在监听同一端口,从而内核会唤起所有处于监听该端口的服务进程,从而导致惊群现象的发生。服务端设置不通的阻塞情况,可得如下不同的结果:
- 一个客户端发起链接,而服务端有三个服务进程在监听同一端口。服务端设置不通的阻塞情况,可得如下不同的结果。
- 1.select设置为阻塞,listenfd设置为阻塞或非阻塞,结果如下:
- worker pid#25583 is waiting for connection...
- worker pid#25584 is waiting for connection...
- worker pid#25585 is waiting for connection...
- worker pid#25585 's listenfd is readable
- worker pid#25585 create a new connection...
- 分析:三个服务进程被唤起,25585第一个调度所以执行accept,所以此时连接被取走,其他两个进程之后开始被调度,而此时连接数目为0,又进入随眠...
- 2.select设置为非阻塞,listenfd设置为阻塞,结果如下:
- worker pid#25743 is waiting for connection...
- worker pid#25744 is waiting for connection...
- worker pid#25745 is waiting for connection...
- worker pid#25745 's listenfd is readable
- worker pid#25744 's listenfd is readable
- worker pid#25743 's listenfd is readable
- worker pid#25745 create a new connection...
- 分析:三个服务进程都select都成功返回可读的套接字,从而各个进程都唤起处于阻塞的accept,但是只有一个进程建立链接,其余两个进程没有获取到链接而又进入睡眠...
- 3.select设置为非阻塞,listenfd设置为为非阻塞,结果如下:
- worker pid#25240 is waiting for connection...
- worker pid#25241 is waiting for connection...
- worker pid#25242 is waiting for connection...
- worker pid#25242 's listenfd is readable
- worker pid#25240 's listenfd is readable
- worker pid#25241 's listenfd is readable
- worker pid#25242 create a new connection...
- accept error: Resource temporarily unavailable
- accept error: Resource temporarily unavailable
- 分析:三个服务进程都进入accept,但是只有一个进程获取到链接,其他两个进程没有获取到链接而出错
- 4.在select设置为阻塞,select和accept之间添加sleep(1),accept的套接字为非阻塞,结果如下:
- worker pid#30689 is waiting for connection...
- worker pid#30690 is waiting for connection...
- worker pid#30691 is waiting for connection...
- worker pid#30691 's listenfd is readable
- worker pid#30691 create a new connection...
- worker pid#30690 's listenfd is readable
- worker pid#30689 's listenfd is readable
- accept error: Resource temporarily unavailable
- accept error: Resource temporarily unavailable
- 分析:三个进程被唤起后,由于有sleep(1),三个进程都被调度了,所以select都能查找到连接,故而从select返回,但是在accept处,只有一个进程得到了连接