while (TRUE) { //等待EPOLL事件的发生,相当于监听,至于相关的端口,需要在初始化EPOLL的时候绑定。 int nfds = epoll_wait (m_epoll_fd, m_events, MAX_EVENTS, EPOLL_TIME_OUT); if (nfds <= 0) continue; m_bOnTimeChecking = FALSE; G_CurTime = time(NULL); for (int i=0; i<nfds; i++) { try { //如果新监测到一个HTTP用户连接到绑定的HTTP端口,建立新的连接。由于我们新采用了SOCKET连接,所以基本没用 if (m_events[i].data.fd == m_listen_http_fd)。 { OnAcceptHttpEpoll (); } else if (m_events[i].data.fd == m_listen_sock_fd)//如果新监测到一个SOCKET用户连接到了绑定的SOCKET端口,建立新的连接。 { OnAcceptSockEpoll (); } else if (m_events[i].events & EPOLLIN)//如果是已经连接的用户,并且收到数据,那么进行读入。 { OnReadEpoll (i); } OnWriteEpoll (i);//查看当前的活动连接是否有需要写出的数据。 } catch (int) { PRINTF ("CATCH捕获错误\n"); continue; } } m_bOnTimeChecking = TRUE; OnTimer ();//进行一些定时的操作,主要就是删除一些断线用户等。 }其实EPOLL的精华,按照我目前的理解,也就是上述的几段短短的代码,看来时代真的不同了,以前如何接受大量用户连接的问题,现在却被如此轻松的搞定,真是让人不得不感叹。
typedef union epoll_data { void *ptr; int fd; __uint32_t u32; __uint64_t u64; } epoll_data_t; struct epoll_event { __uint32_t events; /* Epoll events */ epoll_data_t data; /* User data variable */ };如:
struct epoll_event ev;//设置与要处理的事件相关的文件描述符
ev.data.fd=listenfd;//设置要处理的事件类型
ev.events=EPOLLIN|EPOLLET;//注册epoll事件
epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);常用的事件类型:
/*build the epoll enent for recall */ struct epoll_event ev_read[20]; int nfds = 0; //return the events count nfds=epoll_wait(epoll_fd,ev_read,20, -1); for(i=0; i { if(ev_read[i].data.fd == sock)// the listener port hava data ......epoll_wait运行的原理是
for(;;) { nfds = epoll_wait(kdpfd, events, maxevents, -1); for(n = 0; n < nfds; ++n) { if(events[n].data.fd == listener) { client = accept(listener, (struct sockaddr *) &local, &addrlen); if(client < 0){ perror("accept"); continue; } setnonblocking(client); ev.events = EPOLLIN | EPOLLET; ev.data.fd = client; if (epoll_ctl(kdpfd, EPOLL_CTL_ADD, client, &ev) < 0) { fprintf(stderr, "epoll set insertion error: fd=%d\n", client); return -1; } } else do_use_fd(events[n].data.fd); } }此时使用的是ET模式,即,边沿触发,类似于电平触发,epoll中的边沿触发的意思是只对新到的数据进行通知,而内核缓冲区中如果是旧数据则不进行通知,所以在do_use_fd函数中应该使用如下循环,才能将内核缓冲区中的数据读完。
while (1) { len = recv(*******); if (len == -1) { if(errno == EAGAIN) break; perror("recv"); break; } do something with the recved data........ }在上面例子中没有说明对于listen socket fd该如何处理,有的时候会使用两个线程,一个用来监听accept另一个用来监听epoll_wait,如果是这样使用的话,则listen socket fd使用默认的阻塞方式就行了,而如果epoll_wait和accept处于一个线程中,即,全部由epoll_wait进行监听,则,需将listen socket fd也设置成非阻塞的,这样,对accept也应该使用while包起来(类似于上面的recv),因为,epoll_wait返回时只是说有连接到来了,并没有说有几个连接,而且在ET模式下epoll_wait不会再因为上一次的连接还没读完而返回,这种情况确实存在,我因为这个问题而耗费了一天多的时间,这里需要说明的是,每调用一次accept将从内核中的已连接队列中的队头读取一个连接,因为在并发访问的环境下,有可能有多个连接“同时”到达,而epoll_wait只返回了一次。
ET (edge-triggered)是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在发送,接收或者接收请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK 错误)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once),不过在TCP协议中,ET模式的加速效用仍需要更多的benchmark确认。
转载:http://hi.baidu.com/ym012/blog/item/5f3466037f38dce609fa93c3.html