epoll有两种触发的方式即LT(水平触发)和ET(边缘触发)两种,在前者,只要存在着事件就会不断的触发,直到处理完成,而后者只触发一次相同事件或者说只在从非触发到触发两个状态转换的时候儿才触发。

这会出现下面一种情况,如果是多线程在处理,一个SOCKET事件到来,数据开始解析,这时候这个SOCKET又来了同样一个这样的事件,而你的数据解析尚未完成,那么程序会自动调度另外一个线程或者进程来处理新的事件,这造成一个很严重的问题,不同的线程或者进程在处理同一个SOCKET的事件,这会使程序的健壮性大降低而编程的复杂度大大增加!!即使在ET模式下也有可能出现这种情况!!

解决这种现象有两种方法:

第一种方法是在单独的线程或进程里解析数据,也就是说,接收数据的线程接收到数据后立刻将数据转移至另外的线程。

第二种方法就是本文要提到的EPOLLONESHOT这种方法,可以在epoll上注册这个事件,注册这个事件后,如果在处理写成当前的SOCKET后不再重新注册相关事件,那么这个事件就不再响应了或者说触发了。要想重新注册事件则需要调用epoll_ctl重置文件描述符上的事件,这样前面的socket就不会出现竞态这样就可以通过手动的方式来保证同一SOCKET只能被一个线程处理,不会跨越多个线程。

看下面的代码:

void Eepoll::ResetOneShot(int  epollfd,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的LT和ET使用EPOLLONESHOT的更多相关文章

  1. 【UNIX】select、poll、epoll学习

    三者都是UNIX下多路复用的内核接口,select是跨平台的接口,poll是systemV标准,epoll是linux专有的接口,基于poll改造而成. select 函数原型: int select ...

  2. mysql半同步复制问题排查

    1.问题背景      默认情况下,线上的mysql复制都是异步复制,因此在极端情况下,主备切换时,会有一定的概率备库比主库数据少,因此切换后,我们会通过工具进行回滚回补,确保数据不丢失.半同步复制则 ...

  3. (转载) Linux IO模式及 select、poll、epoll详解

    注:本文是对众多博客的学习和总结,可能存在理解错误.请带着怀疑的眼光,同时如果有错误希望能指出. 同步IO和异步IO,阻塞IO和非阻塞IO分别是什么,到底有什么区别?不同的人在不同的上下文下给出的答案 ...

  4. Linux epoll

    一. epoll函数集 epoll主要有三个函数: 1. int epoll_create(int size); 创建一个epoll的句柄,size用来告诉内核这个监听的数目一共有多大.这个参数不同于 ...

  5. IO多路复用之epoll总结

    1.基本知识 epoll是在2.6内核中提出的,是之前的select和poll的增强版本.相对于select和poll来说,epoll更加灵活,没有描述符限制.epoll使用一个文件描述符管理多个描述 ...

  6. 利用epoll写一个"迷你"的网络事件库

    epoll是linux下高性能的IO复用技术,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率.另一点原因就是获取 ...

  7. Linux Epoll相关知识

    其实在Linux下设计并发网络程序,向来不缺少方法,比如典型的Apache模型(Process Per Connection,简称PPC),TPC(Thread PerConnection)模型,以及 ...

  8. Linux 网络编程(epoll)

    服务器端代码 #include<stdio.h> #include<stdlib.h> #include<string.h> #include<sys/soc ...

  9. 基本I/O模型与Epoll简介

    5种基本的I/O模型:1)阻塞I/O ;2)非阻塞I/O; 3)I/O复用(select和poll);4)信号驱动I/O(SIGIO);5)异步I/O(POSIX.1的aio_系列函数). 操作系统中 ...

随机推荐

  1. mysql percona安装

    注:此方式目前安装存在问题(弃用此方式) 1.在官方网站下载percona  XtraBackup https://www.percona.com/downloads/XtraBackup/LATES ...

  2. 使用TensorFlow给花朵🌺分类

    第一步:准备好需要的库 tensorflow-gpu  1.8.0 opencv-python     3.3.1 numpy skimage os pillow 第二步:准备数据集: 链接:http ...

  3. oracle中循环读出一个表的信息插入到另外一个表中

    declare cursor TAGENTMENUd is select * from TAGENTMENU where 1=1; -- 获取游标begin --遍历查询出的表 (注意 tn是整条记录 ...

  4. sed正则表达式匹配,各种括号的转义和不转义

    https://blog.csdn.net/zl87758539/article/details/77481679

  5. 前端Datatables自定义事件(监听Datatables插件一些常见的事件动作)

    今天开发项目的时候,用Datatables插件做前端分页列表,想在列表发生翻页.排序.搜索.改变单页显示数据条数这些行为的时候做一些其他的操作,看了半天Datatables官网终于找到可以监测到这些事 ...

  6. 准备尝试openFrameworks

    用PCL(Point Cloud Library)已经两年多,当时选择PCL的原因是适合使用的也仅仅知道CGAL和PCL,而CGAL太大,又无从下手,所以选择了PCL. 一开始安装和配置PCL就被虐的 ...

  7. BZOJ 1878 HH的项链 | 主席树

    题意 询问区间有多少不同的数. 题解 和Luogu 1903一样,这道题也是用pre数组来求区间不同数的个数,这里pre[i]表示a[i]上一次出现的位置 +1,询问相当于查询区间内有多少pre小于等 ...

  8. P1164 小A点菜 洛谷

    题目背景 uim神犇拿到了uoi的ra(镭牌)后,立刻拉着基友小A到了一家……餐馆,很低端的那种. uim指着墙上的价目表(太低级了没有菜单),说:“随便点”. 题目描述 不过uim由于买了一些辅(e ...

  9. 洛谷P4107 [HEOI2015]兔子与樱花 [贪心,DFS]

    题目传送门 兔子与樱花 题目描述 很久很久之前,森林里住着一群兔子.有一天,兔子们突然决定要去看樱花.兔子们所在森林里的樱花树很特殊.樱花树由n个树枝分叉点组成,编号从0到n-1,这n个分叉点由n-1 ...

  10. python3之Django模型(一)

    1.模型概述 模型是关于您的数据的唯一,明确的信息来源,它包含您正在存储的数据的重要字段和行为.通常,每个模型映射到单个数据库表. 每个模型都是一个子类的python类django.db.models ...