概述
epoll模式中事件可能被触发多次,比如socket接收到数据交给一个线程处理数据,在数据没有处理完之前又有新数据达到触发了事件,另一个线程被激活获得该socket,从而产生多个线程操作同一socket,即使在ET模式下也有可能出现这种情况。采用EPOLLONETSHOT事件的文件描述符上的注册事件只触发一次,要想重新注册事件则需要调用epoll_ctl重置文件描述符上的事件,这样前面的socket就不会出现竞态。
一个采用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,'