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...

【Linux学习】epoll详解

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

EPOLLONESHOT事件

epoll模式中事件可能被触发多次,比如socket接收到数据交给一个线程处理数据,在数据没有处理完之前又有新数据达到触发了事件,另一个线程被激活获得该socket,从而产生多个线程操作同一socke...

Nginx学习之七-模块ngx_epoll_module详解(epoll机制在nginx中的实现)

本文结合实际代码介绍事件驱动模块接口与epoll用法是如何结合起来发挥作用的。Nginx中ngx_epoll_module模块基于epoll实现了Nginx的事件驱动。 模块定义(src/e...

Linux下套接字详解(十)---epoll模式下的IO多路复用服务器

epoll模型简介epoll可是当前在Linux下开发大规模并发网络程序的热门人选,epoll 在Linux2.6内核中正式引入,和select相似,其实都I/O多路复用技术而已,并没有什么神秘的。其...
  • gatieme
  • gatieme
  • 2016年03月25日 15:10
  • 3258

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

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

C++ linux epoll并发服务器模型初探

socket通讯流程图 最简单的可以通讯的C++服务器端代码:#include #include #include #include #include #include #define...

C++ - 网络编程模型 - Linux EPOLL

C++ - 网络编程模型 - Linux EPOLL

Linux网络编程--epoll模型ET触发模式之epolloneshot事件

epoll模型的ET模式一般来说只触发一次,然而在并发程序中有特殊情况的存在,譬如当epoll_wait已经检测到socket描述符fd1,并通知应用程序处理fd1的数据,那么处理过程中该fd1又有新...

epoll的LT和ET模式

转载自:http://kenby.iteye.com/blog/1162853 epoll的两种模式 LT 和 ET 二者的差异在于 level-trigger 模式下只要某个 soc...
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:epoll的LT和ET使用EPOLLONESHOT
举报原因:
原因补充:

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