accept惊群也就是指多个进程阻塞在accept,当有连接完成,会唤醒所有进程。
经过测试,发现现在的内核已经修复了这个问题,当有多个进程阻塞在accept,只会唤醒一个进程。
下面这个是一篇论文,就是讲这个问题的。
http://www.usenix.org/event/usenix2000/freenix/full_papers/molloy/molloy.pdf
这里会先测试,然后分析内核代码。
下面是服务端的测试代码(很丑陋的代码,只是测试用):
可以看到我们fork两个进程同时阻塞在accept。当客户端connect完成之后,只有第一个子进程被唤醒。
不过这里要注意当用select这类监控listen的句柄的时候,有连接到来,这时所有的进程都会被唤醒的。这里的原因是因为对于select来说,数据不是互斥的,也就是说有可能就需要多个进程同时读取资源。而对于accept,只有可能是一个进程取得数据,因此这里如果用select监控listen的句柄然后阻塞的话,当有连接到来就会唤醒所有的进程,这里测试程序就不贴了。
ok,接下来我们来看源码中是如何做得。
首先我们知道当accept的时候,如果没有连接则会一直阻塞(没有设置非阻塞),而阻塞代码是在inet_csk_wait_for_connect中,这个代码我们前面已经分析过了,一次你我们来看代码片断:
经过测试,发现现在的内核已经修复了这个问题,当有多个进程阻塞在accept,只会唤醒一个进程。
下面这个是一篇论文,就是讲这个问题的。
http://www.usenix.org/event/usenix2000/freenix/full_papers/molloy/molloy.pdf
这里会先测试,然后分析内核代码。
下面是服务端的测试代码(很丑陋的代码,只是测试用):
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <strings.h>
#define SERV_PORT 9999
int main(int argc,char **argv)
{
int listenfd,connfd;
pid_t childpid,childpid2;
socklen_t clilen;
struct sockaddr_in cliaddr,servaddr;
listenfd = socket(AF_INET,SOCK_STREAM,0);
bzero(&servaddr,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl (INADDR_ANY);
servaddr.sin_port = htons (SERV_PORT);
bind(listenfd, (struct sockaddr *) &servaddr, sizeof(servaddr));
listen(listenfd,1000);
clilen = sizeof(cliaddr);
if( (childpid = fork()) == 0)
{
while(1)
{
connfd = accept(listenfd,(struct sockaddr *) &cliaddr,&clilen);
printf("fork 1 is [%d],error is %m\n",connfd);
}
}
if( (childpid2 = fork()) == 0)
{
while(1){
connfd = accept(listenfd,(struct sockaddr *) &cliaddr,&clilen);
printf("fork 2 is [%d],error is %m\n",connfd);
}
}
sleep(100);
return 1;
}
可以看到我们fork两个进程同时阻塞在accept。当客户端connect完成之后,只有第一个子进程被唤醒。
不过这里要注意当用select这类监控listen的句柄的时候,有连接到来,这时所有的进程都会被唤醒的。这里的原因是因为对于select来说,数据不是互斥的,也就是说有可能就需要多个进程同时读取资源。而对于accept,只有可能是一个进程取得数据,因此这里如果用select监控listen的句柄然后阻塞的话,当有连接到来就会唤醒所有的进程,这里测试程序就不贴了。
ok,接下来我们来看源码中是如何做得。
首先我们知道当accept的时候,如果没有连接则会一直阻塞(没有设置非阻塞),而阻塞代码是在inet_csk_wait_for_connect中,这个代码我们前面已经分析过了,一次你我们来看代码片断: