Posted on 2011-09-29 17:08
冷锋 阅读(5719)
评论(2) 编辑 收藏 引用 所属分类:
linux
(转载者注:看完这个,再回头看看nginx源码,发现它在accept时用的是LT模式,read,write时是ET模式)
不知道是谁第一个犯了错,在网上贴出所谓epoll通用框架的代码。注意看accpet的处理:
1 | epfd = epoll_create(10); |
3 | struct sockaddr_in clientaddr; |
4 | struct sockaddr_in serveraddr; |
5 | listenfd = socket(AF_INET, SOCK_STREAM, 0); |
9 | setnonblocking(listenfd); |
11 | ev.events = EPOLLIN | EPOLLET; |
13 | epoll_ctl(epfd, EPOLL_CTL_ADD, listenfd, &ev); |
14 | bzero(&serveraddr, sizeof (serveraddr)); |
15 | serveraddr.sin_family = AF_INET; |
18 | serveraddr.sin_addr.s_addr = htonl(INADDR_ANY); |
19 | serveraddr.sin_port = htons(SERV_PORT); |
20 | bind(listenfd, (sockaddr *) &serveraddr, sizeof (serveraddr)); |
21 | listen(listenfd, LISTENQ); |
23 | int nfds_count = 0, recvcount = 0, recvlen = 0; |
25 | cout << "before epoll_wait! nfds_count=" << nfds_count << endl; |
26 | nfds = epoll_wait(epfd, events, 20, 10000000); |
29 | cout << "nfds=" << nfds << endl; |
31 | for (i = 0; i < nfds; ++i) { |
32 | if (events[i].data.fd == listenfd) { |
33 | connfd = accept(listenfd, (sockaddr *) &clientaddr, &clilen); |
39 | setnonblocking(connfd); |
40 | char *str = inet_ntoa(clientaddr.sin_addr); |
41 | cout << "connect from " << str << "connfd=" << connfd << endl; |
43 | ev.events = EPOLLIN | EPOLLET; |
46 | epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev); |
代码是从某处(很多地方都是这段,连注释都一样)拷过来的。熟悉epoll的人看了应该很熟系,其实就是将linsten的fd也加入到epoll中,当有新连接加入时可以epoll_wait到,随后再用accpet处理。
事实上这段代码是有问题的——高并发的情况下,accept到的fd的数量跟client端的发起请求的数量并不相等。我测了一下,100个并发(其实也不算高了)往往少几个到几十个不等。
相信很多人被这段代码误导了,因为我在遇到问题的时候搜到不少帖子,用的都是这样的代码。只是奇怪的是没见几个人说遇到我提到的这个问题。不过有人说这段代码效率不高,提出用阻塞式IO把accpet提到一个单独的线程里做。撇开这样做性能上的优劣不说,倒是能解决我遇到的问题,但这治标不治本——我要用epoll+nonblockio
下面说说这段误人子弟的代码,其实 man epoll 就能找到这段代码的出处。相信它是某位同志从里面帖出来然后玩弄了许久后好心放到网上的,不然不会有这么多的//注释。而问题就是出在注释上,注意到 man-page 里的代码是这样的:
2 | ev.data.fd = listen_sock; |
3 | if (epoll_ctl(epollfd, EPOLL_CTL_ADD, listen_sock, &ev) == -1) { |
4 | perror ( "epoll_ctl: listen_sock" ); |
没错,man里并没有将listenfd以ET的方式加入epoll(不指定EPOLLET默认就是Level Triggered),事实也证明不设ET就没有问题了。
难道说ET模式下就不能以非阻塞模式来accpet?答案是否定的。其实只要把accpet方式改一下就可以了,简单说就是if改while:
1 | while ((confd = accept(listenfd, ( struct sockaddr*) sa, &clientlen)) > 0) { |
4 | peer = ( struct sockaddr*) sa; |
5 | printf ( "%s:%d connect at %d.\n" , inet_ntoa(peer->sin_addr), |
6 | peer->sin_port, confd); |
10 | ev.events = EPOLLIN | EPOLLET ; |
12 | epoll_ctl(myfd, EPOLL_CTL_ADD, confd, &ev); |