现在的位置: 首页 > 综合 > 正文

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

2018年09月21日 ⁄ 综合 ⁄ 共 4259字 ⁄ 字号 评论关闭
文章目录

背景

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

抱歉!评论已关闭.