epoll的总结 LT和ET使用EPOLLONESHOT
在前面说过,epoll有两种触发的方式即LT(水平触发)和ET(边缘触发)两种,在前者,只要存在着事件就会不断的触发,直到处理完成,而后者只触发一次相同事件或者说只在从非触发到触发两个状态转换的时候儿才触发。
这会出现下在一种情况,如果是多线程在处理,一个SOCKET事件到来,数据开始解析,这时候这个SOCKET又来了同样一个这样的事件,而你的数据解析尚未完成,那么程序会自动调度另外一个线程或者进程来处理新的事件,这造成一个很严重的问题,不同的线程或者进程在处理同一个SOCKET的事件,这会使程序的健壮性大降低而编程的复杂度大大增加。
解决这种现象有两种方法,一种是在单独的线程或进程里解析数据,也就是说,接收数据的线程接收到数据后立刻将数据转移至另外的线程。
第二种方法就是本文要提到的EPOLLONESHOT这种方法,可以在epoll上注册这个事件,注册这个事件后,如果在处理写成当前的SOCKET后不再重新注册相关事件,那么这个事件就不再响应了或者说触发了。这样就可以通过手动的方式来保证同一SOCKET只能被一个线程处理,不会跨越多个线程。
看下面的代码:
void Eepoll::ResetOneShot(int epollfd,SOCKET fd,bool bOne)
{
epoll_event event;
event.data.fd = fd;
event.events = EPOLLIN | EPOLLET ;
if (bOne)
{
event.events |= EPOLLONESHOT;
}
if (-1 == epoll_ctl(epollfd,EPOLL_CTL_MOD,fd,&event))
{
perror("resetoneshot epoll_ctl error!");
}
}
这里有一个问题,在操作ET模式下的EPOLL时,对EPOLLONESHOT没有什么太大的注意点,但是在LT时,就有一些注意的了。
前面说过LT会不断触发,所以在处理数据时,不需要在RECV时不断的循环去读一直读到EAGAIN,但如果设置了EPOLLONESHOT后,也得如此办理,否则,就可能会丢掉数据。
这里有一个问题,在操作ET模式下的EPOLL时,对EPOLLONESHOT没有什么太大的注意点,但是在LT时,就有一些注意的了。
前面说过LT会不断触发,所以在处理数据时,不需要在RECV时不断的循环去读一直读到EAGAIN,但如果设置了EPOLLONESHOT后,也得如此办理,否则,就可能会丢掉数据。
如果对描述符socket注册了EPOLLONESHOT事件,那么操作系统最多触发其上注册的一个可读、可写或者异常事件,且只触发一次。。想要下次再触发则必须使用epoll_ctl重置该描述符上注册的事件,包括EPOLLONESHOT 事件。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
例子:
epoll_event event;
event..data.fd=fd;
evnet.events=EPOLLIN | EPOLLT |EPOOLLONESHOT
epoll_ctl( epollfd,EPOLL_CTL_ADD , fd , &event );// 第一次添加
epoll_wait 返回, 处理fd的读事件,一直读一直读,读到没有数据 ( errno==EAGAIN) ,这时才重置fd上的事件
epoll_event event;
event..data.fd=fd;
evnet.events=EPOLLIN | EPOLLT |EPOOLLONESHOT
epoll_ctl( epollfd,EPOLL_CTL_MOD , fd , &event );
一个采用EPOLLONETSHOT的例子:
epoll_oneshot._server.cpp服务端程序
- #include<sys/types.h>
- #include<sys/socket.h>
- #include<netinet/in.h>
- #include<arpa/inet.h>
- #include<assert.h>
- #include<stdio.h>
- #include<unistd.h>
- #include<errno.h>
- #include<string.h>
- #include<fcntl.h>
- #include<stdlib.h>
- #include<sys/epoll.h>
- #include<pthread.h>
- #include<iostream>
- #define MAX_EVENT_NUMBER 1024//最大事件连接数
- #define BUFFER_SIZE 1024//接收缓冲区大小
- using namespace std;
- struct fds{
- int epollfd;
- int sockfd;
- };
- int setnonblocking(int fd){
- int old_option=fcntl(fd,F_GETFL);
- int new_option=old_option|O_NONBLOCK;
- fcntl(fd,F_SETFL,new_option);
- return old_option;
- }
- void addfd(int epollfd,int fd,bool oneshot){
- epoll_event event;
- event.data.fd=fd;
- event.events=EPOLLIN|EPOLLET;
- if(oneshot){
- event.events|=EPOLLONESHOT;
- }
- epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,&event);
- setnonblocking(fd);
- }
- void reset_oneshot(int epollfd,int fd){
- epoll_event event;
- event.data.fd=fd;
- event.events=EPOLLIN|EPOLLET|EPOLLONESHOT;
- epoll_ctl(epollfd,EPOLL_CTL_MOD,fd,&event);
- }
- void* worker(void* arg){
- int sockfd=((fds*)arg)->sockfd;
- int epollfd=((fds*)arg)->epollfd;
- cout<<"start new thread to receive data on fd:"<<sockfd<<endl;
- char buf[BUFFER_SIZE];
- memset(buf,'\0',BUFFER_SIZE);
- while(1){
- int ret=recv(sockfd,buf,BUFFER_SIZE-1,0);
- if(ret==0){
- close(sockfd);
- cout<<"close "<<sockfd<<endl;
- break;
- }
- else if(ret<0){
- if(errno==EAGAIN){
- reset_oneshot(epollfd,sockfd);
- cout<<"reset epollfd"<<endl;
- break;
- }
- }
- else{
- cout<<buf;
- sleep(5);
- }
- }
- cout<<"thread exit on fd:"<<sockfd;
-
- return NULL;
- }
- int main(int argc,char* argv[]){
- if(argc<=2){
- cout<<"argc<=2"<<endl;
- return 1;
- }
- const char* ip=argv[1];
- int port=atoi(argv[2]);
- int ret=0;
- struct sockaddr_in address;
- bzero(&address,sizeof(address));
- address.sin_family=AF_INET;
- inet_pton(AF_INET,ip,&address.sin_addr);
- address.sin_port=htons(port);
- int listenfd=socket(PF_INET,SOCK_STREAM,0);
- assert(listenfd>=0);
- ret=bind(listenfd,(struct sockaddr*)&address,sizeof(address));
- assert(ret!=-1);
- ret=listen(listenfd,5);
- assert(ret!=-1);
- epoll_event events[MAX_EVENT_NUMBER];
- int epollfd=epoll_create(5);
- assert(epollfd!=-1);
- addfd(epollfd,listenfd,false);
- while(1){
- int ret=epoll_wait(epollfd,events,MAX_EVENT_NUMBER,-1);
- if(ret<0){
- cout<<"epoll error"<<endl;
- break;
- }
- for(int i=0;i<ret;i++){
- int sockfd=events[i].data.fd;
- if(sockfd==listenfd){
- struct sockaddr_in client_address;
- socklen_t client_addrlength=sizeof(client_address);
- int connfd=accept(listenfd,(struct sockaddr*)&client_address,&client_addrlength);
- addfd(epollfd,connfd,true);
- }
- else if(events[i].events&EPOLLIN){
- pthread_t thread;
- fds fds_for_new_worker;
- fds_for_new_worker.epollfd=epollfd;
- fds_for_new_worker.sockfd=sockfd;
- pthread_create(&thread,NULL,worker,(void*)&fds_for_new_worker);
- }
- else{
- cout<<"something wrong"<<endl;
- }
- }
- }
- close(listenfd);
- return 0;
- }