epoll的總結之四LT和ET使用EPOLLONESHOT
在前面說過,epoll有兩種觸發的方式即LT(水平觸發)和ET(邊緣觸發)兩種,在前者,只要存在着事件就會不斷的觸發,直到處理完成,而後者只觸發一次相同事件或者說只在從非觸發到觸發兩個狀態轉換的時候兒才觸發。
這會出現下面一種情況,如果是多線程在處理,一個SOCKET事件到來,數據開始解析,這時候這個SOCKET又來了同樣一個這樣的事件,而你的數據解析尚未完成,那麼程序會自動調度另外一個線程或者進程來處理新的事件,這造成一個很嚴重的問題,不同的線程或者進程在處理同一個SOCKET的事件,這會使程序的健壯性大降低而編程的複雜度大大增加!!即使在ET模式下也有可能出現這種情況!!
解決這種現象有兩種方法,一種是在單獨的線程或進程裏解析數據,也就是說,接收數據的線程接收到數據後立刻將數據轉移至另外的線程。
第一種方法
第二種方法就是本文要提到的EPOLLONESHOT這種方法,可以在epoll上註冊這個事件,註冊這個事件後,如果在處理寫成當前的SOCKET後不再重新註冊相關事件,那麼這個事件就不再響應了或者說觸發了。要想重新註冊事件則需要調用epoll_ctl重置文件描述符上的事件,這樣前面的socket就不會出現競態這樣就可以通過手動的方式來保證同一SOCKET只能被一個線程處理,不會跨越多個線程。
看下面的代碼:
void Eepoll::ResetOneShot(intepollfd,SOCKET fd,bool bOne)
{
epoll_eventevent;
event.data.fd= fd;
event.events= EPOLLIN | EPOLLET ;
if(bOne)
{
event.events |=EPOLLONESHOT;
}
if(-1 == epoll_ctl(epollfd,EPOLL_CTL_MOD,fd,&event))
{
perror("resetoneshotepoll_ctl error!");
}
}
這裏有一個問題,在操作ET模式下的EPOLL時,對EPOLLONESHOT沒有什麼太大的注意點,但是在LT時,就有一些注意的了。
前面說過LT會不斷觸發,所以在處理數據時,不需要在RECV時不斷的循環去讀一直讀到EAGAIN,但如果設置了EPOLLONESHOT後,也得如此辦理,否則,就可能會丟掉數據。一個採用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){//採用EPOLLONETSHOT事件
- 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){//工作者線程(子線程)接收socket上的數據並重置事件
- int sockfd=((fds*)arg)->sockfd;
- int epollfd=((fds*)arg)->epollfd;//事件表描述符從arg參數(結構體fds)得來
- 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);//採用睡眠是爲了在5s內若有新數據到來則該線程繼續處理,否則線程退出
- }
- }
- cout<<"thread exit on fd:"<<sockfd;
- //_exit(0);//這個會終止整個進程!!
- 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);//不能將監聽端口listenfd設置爲EPOLLONESHOT否則會丟失客戶連接
- 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);//新的客戶連接置爲EPOLLONESHOT事件
- }
- 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;
- }