EPOLL下的accept(转载)

Posted on 2011-09-29 17:08 冷锋 阅读(5719) 评论(2)  编辑 收藏 引用 所属分类: linux

(转载者注:看完这个,再回头看看nginx源码,发现它在accept时用的是LT模式,read,write时是ET模式)
不知道是谁第一个犯了错,在网上贴出所谓epoll通用框架的代码。注意看accpet的处理:

1epfd = epoll_create(10);
2 
3struct sockaddr_in clientaddr;
4struct sockaddr_in serveraddr;
5listenfd = socket(AF_INET, SOCK_STREAM, 0);
6 
7bool bReuseaddr = 1;
8//setsockopt(listenfd,SOL_SOCKET,SO_REUSEADDR,(const char*)&bReuseaddr,sizeof(bool));
9setnonblocking(listenfd);
10ev.data.fd = listenfd;
11ev.events = EPOLLIN | EPOLLET;
12// ev.events=EPOLLIN;
13epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev);
14bzero(&serveraddr, sizeof(serveraddr));
15serveraddr.sin_family = AF_INET;
16// char *local_addr=INADDR_ANY;
17// inet_aton(local_addr,&(serveraddr.sin_addr));//htons(SERV_PORT);
18serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
19serveraddr.sin_port = htons(SERV_PORT);
20bind(listenfd, (sockaddr *) &serveraddr, sizeof(serveraddr));
21listen(listenfd, LISTENQ);
22maxi = 0;
23int nfds_count = 0, recvcount = 0, recvlen = 0;
24for (;;) {
25    cout << "before epoll_wait! nfds_count=" << nfds_count << endl;
26    nfds = epoll_wait(epfd, events, 20, 10000000);
27 
28    // if(nfds>1) cout<<"nfds="<<nfds<<endl;
29    cout << "nfds=" << nfds << endl;
30 
31    for (i = 0; i < nfds; ++i) {
32        if (events[i].data.fd == listenfd) {
33            connfd = accept(listenfd, (sockaddr *) &clientaddr, &clilen);
34            nfds_count += 1;
35            if (connfd < 0) {
36                perror("connfd<0");
37                exit(1);
38            }
39            setnonblocking(connfd);
40            char *str = inet_ntoa(clientaddr.sin_addr);
41            cout << "connect from " << str << "connfd=" << connfd << endl;
42            ev.data.fd = connfd;
43            ev.events = EPOLLIN | EPOLLET;
44            //ev.events=EPOLLIN;
45            //ev.events=EPOLLIN|EPOLLOUT|EPOLLET;
46            epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev);
47 
48        }
49    }
50}


代码是从某处(很多地方都是这段,连注释都一样)拷过来的。熟悉epoll的人看了应该很熟系,其实就是将linsten的fd也加入到epoll中,当有新连接加入时可以epoll_wait到,随后再用accpet处理。
事实上这段代码是有问题的——高并发的情况下,accept到的fd的数量跟client端的发起请求的数量并不相等。我测了一下,100个并发(其实也不算高了)往往少几个到几十个不等。

相信很多人被这段代码误导了,因为我在遇到问题的时候搜到不少帖子,用的都是这样的代码。只是奇怪的是没见几个人说遇到我提到的这个问题。不过有人说这段代码效率不高,提出用阻塞式IO把accpet提到一个单独的线程里做。撇开这样做性能上的优劣不说,倒是能解决我遇到的问题,但这治标不治本——我要用epoll+nonblockio

下面说说这段误人子弟的代码,其实 man epoll 就能找到这段代码的出处。相信它是某位同志从里面帖出来然后玩弄了许久后好心放到网上的,不然不会有这么多的//注释。而问题就是出在注释上,注意到 man-page 里的代码是这样的:

1ev.events = EPOLLIN;
2ev.data.fd = listen_sock;
3if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sock, &ev) == -1) {
4    perror("epoll_ctl: listen_sock");
5    exit(EXIT_FAILURE);
6}

没错,man里并没有将listenfd以ET的方式加入epoll(不指定EPOLLET默认就是Level Triggered),事实也证明不设ET就没有问题了。

难道说ET模式下就不能以非阻塞模式来accpet?答案是否定的。其实只要把accpet方式改一下就可以了,简单说就是if改while:

1while ((confd = accept(listenfd, (struct sockaddr*) sa, &clientlen)) > 0) {
2 
3    //add connection to epoll
4    peer = (struct sockaddr*) sa;
5    printf("%s:%d connect at %d.\n", inet_ntoa(peer->sin_addr),
6            peer->sin_port, confd);
7    setnonblocking(confd);
8 
9    ev.data.fd = confd;
10    ev.events = EPOLLIN | EPOLLET ;
11 
12    epoll_ctl(myfd, EPOLL_CTL_ADD, confd, &ev);
13 
14}

Feedback

# re: EPOLL下的accept(转载)  回复  更多评论   

2011-09-29 18:58 by dong
嗯,确实是这样的,我刚开始学的时候就发生过这种错,我看过man文档,负责监听 accpet的fd是LT模式的,并不是在网上搜到的ET模式。我之前测过的时候就是遇到客户端量一多的时候,就出现监听到的客户端数量不一的问题,当时查得我那个心烦呀~当时就奇怪别人的代码贴出来之后也没反应有啥问题,但通过在多个版本的LINUX上测试之后,可以肯定就是不用ET模式就没问题。

# re: EPOLL下的accept(转载)  回复  更多评论   

2012-08-24 00:44 by linxr
呵呵,我今天也找到问题了,才看到你的文章。网上的代码,帮助人也害人哪。

只有注册用户登录后才能发表评论。
网站导航: 博客园   IT新闻   BlogJava   知识库   博问   管理


posts - 15, comments - 18, trackbacks - 0, articles - 0

Copyright © 冷锋