进程池模型需要通过system V IPC机制或管道、信号、文件锁等进行同步。以下是进程池的一般模型。

               进程池模型_第1张图片


Linux惊群现象:

惊群:惊群是指多个进程/线程在等待同一资源时,每当资源可用,所有的进程/线程都来竞争资源的现象。

accept、select、epoll实现进程池模型时的惊群现象:

    1).Linux多进程accept系统调用的惊群问题(注意,这里没有使用select、epoll等事件机制),在linux 2.6版本之前的版本存在,在之后的版本中解决掉了。

    2).使用select epoll等事件机制,在linux早期的版本中,惊群问题依然存在(epoll_create在fork之前)。 原因与之前单纯使用accept导致惊群,原因类似。Epoll的惊群问题,同样在之后的某个版本部分解决了。

    3).Epoll_create在fork之后调用,不能避免惊群问题,Nginx使用互斥锁,解决epoll惊群问题。

    进程池中的进程调用accept如果阻塞在同一个listen队列中,有可能产生惊群现象(取决于Linux版本):当一connect到达时,所有accept都会唤醒,但只有一个accept会返回正确结果,其他的都会返回错误码。

    accept多进程实现:让一个进程bind一个网络地址(可能是AF_INET,AF_UNIX或者其他任何你想要的),然后fork这个进程自己:


int s = socket(...)  

bind(s, ...)  

listen(s, ...)   

fork()  

Fork自己几次之后,每个进程阻塞在accept()函数这里

for(;;) {  

    int client = accept(...);  //子进程阻塞在这了  

    if (client < 0) continue;  

    ...    

}  

             进程池模型_第2张图片

在较老的unix系统中,当有连接到来时,accept()在每个阻塞在这的进程里被唤醒。但是,只有这些进程中的一个能够真正的accept这个连接,其他的进程accept将返回EAGAIN惊群造成结果是系统对用户进程/线程频繁的做无效的调度、上下文切换,系统系能大打折扣。


实现代码:

服务器端代码:

#include

#include

#include

#include

#include

#include

#include

#include

#include

#include


#define BUFLEN 1024

#define PIDNUM 3


/*******************并发服务器模型之一:预先分配好了进程的个数**********************/


static void handle_fork(int sockfd){

    int newfd;

    struct sockaddr_in c_addr;

    char buf[BUFLEN];

    socklen_t len;

    time_t now;

    while(1){

        len = sizeof(struct sockaddr);

        if((newfd = accept(sockfd,(struct sockaddr*) &c_addr, &len)) == -1){

            perror("accept");        

            exit(errno);

        }else

        printf("\n*****************通信开始***************\n");

        printf("正在与您通信的客户端是:%s: %d\n",inet_ntoa(c_addr.sin_addr),ntohs(c_addr.sin_port));


        /******处理客户端请求*******/

        bzero(buf,BUFLEN);

        len = recv(newfd,buf,BUFLEN,0);

        if(len >0 && !strncmp(buf,"TIME",4)){

            bzero(buf,BUFLEN);

            /*获取系统当前时间*/

            now = time(NULL);

            /*ctime将系统时间转换为字符串,sprintf使转化后的字符串保存在buf*/

            sprintf(buf,"%24s\r\n",ctime(&now));

            //******发送系统时间*******/

            send(newfd,buf,strlen(buf),0);

        }

        /*关闭通讯的套接字*/

        close(newfd);

    }

}


int main(int argc, char **argv)

{

    int sockfd;

    struct sockaddr_in s_addr;

    unsigned int port, listnum;

    pid_t pid[PIDNUM];

    

    /*建立socket*/

    if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){

        perror("socket");

        exit(errno);

    }else

        printf("socket create success!\n");

    /*设置服务器端口*/    

    if(argv[2])

        port = atoi(argv[2]);

    else

        port = 4567;

    /*设置侦听队列长度*/

    if(argv[3])

        listnum = atoi(argv[3]);

    else

        listnum = 3;

    /*设置服务器ip*/

    bzero(&s_addr, sizeof(s_addr));

    s_addr.sin_family = AF_INET;

    s_addr.sin_port = htons(port);

    if(argv[1])

        s_addr.sin_addr.s_addr = inet_addr(argv[1]);

    else

        s_addr.sin_addr.s_addr = INADDR_ANY;

    /*把地址和端口帮定到套接字上*/

    if((bind(sockfd, (struct sockaddr*) &s_addr,sizeof(struct sockaddr))) == -1){

        perror("bind");

        exit(errno);

    }else

        printf("bind success!\n");

    /*侦听本地端口*/

    if(listen(sockfd,listnum) == -1){

        perror("listen");

        exit(errno);    

    }else

        printf("the server is listening!\n");

    /*处理客户端的连接*/

    int i = 0;

    for(i = 0; i < PIDNUM; i++){

        pid[i] = fork();

        if(pid[i] == 0)

            handle_fork(sockfd);    

    }

    /*关闭服务器的套接字*/

    close(sockfd);

    return 0;

}


客户端代码:



#include

#include

#include

#include

#include

#include

#include

#include

#include

#include


#define BUFLEN 1024


int main(int argc, char **argv)

{

    int sockfd;

    struct sockaddr_in s_addr;

    socklen_t len;

    unsigned int port;

    char buf[BUFLEN];    

    

    /*建立socket*/

    if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){

        perror("socket");

        exit(errno);

    }else

        printf("socket create success!\n");


    /*设置服务器端口*/    

    if(argv[2])

        port = atoi(argv[2]);

    else

        port = 4567;

    /*设置服务器ip*/

    bzero(&s_addr, sizeof(s_addr));

    s_addr.sin_family = AF_INET;

    s_addr.sin_port = htons(port);

    if (inet_aton(argv[1], (struct in_addr *)&s_addr.sin_addr.s_addr) == 0) {

        perror(argv[1]);

        exit(errno);

    }

    /*开始连接服务器*/    

    if(connect(sockfd,(struct sockaddr*)&s_addr,sizeof(struct sockaddr)) == -1){

        perror("connect");

        exit(errno);

    }else

        printf("conncet success!\n"); 

    /******缓冲区清零*******/    

    bzero(buf,BUFLEN);

    strcpy(buf,"TIME");

    /******发送消息*******/

    send(sockfd,buf,strlen(buf),0);

    /******缓冲区清零*******/

    bzero(buf,BUFLEN);

    /******接收消息*******/

    len = recv(sockfd,buf,BUFLEN,0);

    if(len > 0)

        printf("服务器的系统时间是:%s\n",buf);

    close(sockfd); /*关闭连接*/

    return 0;

}