epoll的LT和ET使用EPOLLONESHOT

转载 2015年07月16日 14:10:27

epoll的总结之四LTET使用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;
}

epoll EPOLLONESHOT 事件

eppll 即使使用ET模式,一个socket上的某个事件还是可能被触发多次,采用线程城池的方式来处理事件,可能一个socket同时被多个线程处理 如果对描述符socket注册了EPOLLONESHO...
  • le119126
  • le119126
  • 2015年06月04日 17:20
  • 1392

【服务器编程】EPOLL的LT和ET模式的区别和理解

【前言】 epoll模型是服务器编程的高性能框架,比select 和 poll模型高效很多,当然还有其它的模型,如kqueue等,具体linux发行版提供不同的模型,一般都支持epoll吧。 ...
  • Jammg
  • Jammg
  • 2016年07月07日 23:05
  • 5535

epoll EPOLLONESHOT,非阻塞,子线程处理客户端事件

epoll非阻塞,EPOLLONESHOT 子线程处理客户端事件
  • xwc583413343
  • xwc583413343
  • 2017年02月21日 14:05
  • 346

epoll:EPOLLLT模式下的正确读写方式

epoll编程需要关注以下一些细节: 1.进程可打开的最大文件描述符,通过ulimit -a查看open files选项 2.read函数的返回值与errno,errno为EAGAIN和EINTR的含...
  • daiyudong2020
  • daiyudong2020
  • 2016年01月02日 21:25
  • 1570

【Linux学习】epoll详解

什么是epoll epoll是什么?按照man手册的说法:是为处理大批量句柄而作了改进的poll。当然,这不是2.6内核才有的,它是在2.5.44内核中被引进的(epoll(4) is a new ...
  • xiajun07061225
  • xiajun07061225
  • 2013年07月05日 11:50
  • 103746

epoll的总结 LT和ET使用EPOLLONESHOT

epoll的总结 LT和ET使用EPOLLONESHOT 在前面说过,epoll有两种触发的方式即LT(水平触发)和ET(边缘触发)两种,在前者,只要存在着事件就会不断的触发,直到处理完成,而后...
  • liu0808
  • liu0808
  • 2016年10月31日 15:10
  • 268

epoll中et+多线程模式中很重要的EPOLL_ONESHOT实验

因为et模式需要循环读取,但是在读取过程中,如果有新的事件到达,很可能触发了其他线程来处理这个socket,那就乱了。 EPOLL_ONESHOT就是用来避免这种情况。注意在一个线程处理完一个soc...
  • linuxheik
  • linuxheik
  • 2017年06月15日 17:25
  • 246

linux高性能服务器编程之EPOLLONESHOT事件

1.**重点内容**EPOLLONESHOT事件的引入 即使使用ET模式,一个socket上的某个事件还是可能被触发多次.这在并发程序中会引发一个问题. 比如一个线程在读取完某个socket上的数...
  • haolipengzhanshen
  • haolipengzhanshen
  • 2016年12月19日 17:56
  • 403

自己趟过epoll的坑

坑的背景 本人用epoll来实现多路复用,epoll触发模式有两种: ET(边缘模式)LT(水平模式) LT模式 是标准模式,意味着每次epoll_wait()返回后,事件处理后,如果...
  • linuxheik
  • linuxheik
  • 2017年06月15日 17:41
  • 892

epoll的ET和LT模式详解

从man手册中,得到ET和LT的具体描述如下EPOLL事件有两种模型:Edge Triggered (ET)Level Triggered (LT)假如有这样一个例子:1. 我们已经把一个用来从管道中...
  • eroswang
  • eroswang
  • 2009年08月25日 07:08
  • 21451
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:epoll的LT和ET使用EPOLLONESHOT
举报原因:
原因补充:

(最多只允许输入30个字)