原文链接: 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处,只有一个进程得到了连接