Epoll介绍
Epoll 可以使用一次等待监听多个描述符的可读\可写状态。等待返回时携带了可读的描述符或者自定义的数据,使用者据此读取所需的数据后可以再次进入等待。因此不需要为每个描述符创建独立的线程进行阻塞读取,避免了资源浪费的同时又可以获得较快的响应速度。
Epoll的接口
int epoll_create(int max_fds);
创建一个epoll对象的描述符,之后对epoll的操作均使用这个描述符完成.max_fds参数表示此epoll对象可以监听的描述符的最大量.
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
用于管理注册时间的函数.这个函数可以增加/删除/修改/删除事件的注册。
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
用于等待的事件到来。当此函数返回时,events数组参数中将会包含产生事件的描述符。
Epoll 用法
1 创建epoll对象
int epfd = epoll_create(MAX_FDS);
2 填充epoll_event对象
接着为每一个需要监控的描述符填充epoll_event结构体,以描述监控事件,并通过epoll_ctl函数将此描述符与epoll_event结构体注册进epoll对象。epoll_event结构体如下
struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
events可以是以下几个宏的集合:
EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
epoll_data_t联合体定义如下,当然,同一时间只能使用一个字段:
typedef union epoll_data {
void *ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t;
data字段是一个联合体,他让使用者可以将一些自定义数据加入事件通知中,当此事件发生时,用户设置的data字段将会返回给使用者。在实际使用中经常设置epoll_data.data.fd为监听的文件描述符,事件发生时可以根据epoll_data.data.fd得知引发事件的描述符。当然,也可以设置epoll_data.data.fd为其他便于识别的数据。
填充epoll_event方法如下:
struct epoll_event eventItem;
memset(&eventItem,0,sizeof(eventItem));
eventItem.events = EPOLLIN | EPOLLERR | EPOLLHUP;
eventItem.data.fd = listeningFd;
接下来就可以使用epoll_ctl将事件注册进epoll对象了。
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
epfd:是由epoll_crate()函数所创建的epoll对象的描述符。
op :EPOLL_CTL_ADD/DEL/MOD三种操作,增加/删除/修改
fd :表示了需要监听的描述符
event:描述监听事件的详细信息的epoll_event结构体
注册方法如下:
result = epoll_ctl(epfd,EPOLL_CTL_ADD,listeningFd,&eventItem);
重复这个步骤可以将多个文件描述符的多种事件监听注册到epoll对象中.完成监听的注册之后,便可以通过epoll_wait()函数等待事件到来。
3 使用epoll_wait()函数等待事件
epoll_wait()函数将会使调用者陷入等待状态,直到其注册的事件发生之后才会返回,并且携带刚刚发生的事件的详细信息。其签名如下:
int epoll_wait(int epfd, struct epoll_event *events, int maxEvents, int timeout);
epfd :是由epoll_crate()函数所创建的epoll对象描述符。
events:是一个epoll_events 数组,此函数返回时,事件的信息将被填充至此。
maxevents :表示此次调用最多可以调用的事件数,当然,events参数必须足够容纳这么多事件。
timeout:表示等待超市的时间
返回值,表示获取了多少个事件。
4 处理事件
epoll_wait 返回后,便可以根据events数组中保存的所有epoll_event结构体的events字段与data字段识别事件的类型与来源。
EPoll的使用步骤总结如下
- 通过EPoll_Create 创建epoll对象
- 为需要监听的描述符填充epoll_events结构体,并使用epoll_ctl注册到epoll对象中。
- 使用epoll_wait等待事件发生
- 根据epoll_wait返回的epoll_events结构体数组判断事件的类型与来源进行处理
- 继续使用epoll_wait等待事件发生
Epoll 使用示例
#include <iostream>
#include <sys/socket.h>
#include <sys/epoll.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdio.h>
#include <errno.h>
using namespace std;
#define MAXLINE 5
#define OPEN_MAX 100
#define LISTENQ 20
#define SERV_PORT 5000
#define INFTIM 1000
void setnonblocking(int sock)
{
int opts;
opts=fcntl(sock,F_GETFL);
if(opts<0)
{
perror("fcntl(sock,GETFL)");
exit(1);
}
opts = opts|O_NONBLOCK;
if(fcntl(sock,F_SETFL,opts)<0)
{
perror("fcntl(sock,SETFL,opts)");
exit(1);
}
}
int main(int argc, char* argv[])
{
int i, maxi, listenfd, connfd, sockfd,epfd,nfds, portnumber;
ssize_t n;
char line[MAXLINE];
socklen_t clilen;
if ( 2 == argc )
{
if( (portnumber = atoi(argv[1])) < 0 )
{
fprintf(stderr,"Usage:%s portnumber/a/n",argv[0]);
return 1;
}
}
else
{
fprintf(stderr,"Usage:%s portnumber/a/n",argv[0]);
return 1;
}
//声明epoll_event结构体的变量,ev用于注册事件,数组用于回传要处理的事件
struct epoll_event ev,events[20];
//生成用于处理accept的epoll专用的文件描述符
epfd=epoll_create(256);
struct sockaddr_in clientaddr;
struct sockaddr_in serveraddr;
listenfd = socket(AF_INET, SOCK_STREAM, 0);
//把socket设置为非阻塞方式
//setnonblocking(listenfd);
//设置与要处理的事件相关的文件描述符
ev.data.fd=listenfd;
//设置要处理的事件类型
ev.events=EPOLLIN|EPOLLET;
//ev.events=EPOLLIN;
//注册epoll事件
epoll_ctl(epfd,EPOLL_CTL_ADD,listenfd,&ev);
bzero(&serveraddr, sizeof(serveraddr));
serveraddr.sin_family = AF_INET;
char *local_addr="127.0.0.1";
inet_aton(local_addr,&(serveraddr.sin_addr));//htons(portnumber);
serveraddr.sin_port=htons(portnumber);
bind(listenfd,(sockaddr *)&serveraddr, sizeof(serveraddr));
listen(listenfd, LISTENQ);
maxi = 0;
for ( ; ; ) {
//等待epoll事件的发生
nfds=epoll_wait(epfd,events,20,500);
//处理所发生的所有事件
for(i=0;i<nfds;++i)
{
if(events[i].data.fd==listenfd)//如果新监测到一个SOCKET用户连接到了绑定的SOCKET端口,建立新的连接。
{
connfd = accept(listenfd,(sockaddr *)&clientaddr, &clilen);
if(connfd<0){
perror("connfd<0");
exit(1);
}
//setnonblocking(connfd);
char *str = inet_ntoa(clientaddr.sin_addr);
cout << "accapt a connection from " << str << endl;
//设置用于读操作的文件描述符
ev.data.fd=connfd;
//设置用于注测的读操作事件
ev.events=EPOLLIN|EPOLLET;
//ev.events=EPOLLIN;
//注册ev
epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev);
}
else if(events[i].events&EPOLLIN)//如果是已经连接的用户,并且收到数据,那么进行读入。
{
cout << "EPOLLIN" << endl;
if ( (sockfd = events[i].data.fd) < 0)
continue;
if ( (n = read(sockfd, line, MAXLINE)) < 0) {
if (errno == ECONNRESET) {
close(sockfd);
events[i].data.fd = -1;
} else
std::cout<<"readline error"<<std::endl;
} else if (n == 0) {
close(sockfd);
events[i].data.fd = -1;
}
line[n] = '/0';
cout << "read " << line << endl;
//设置用于写操作的文件描述符
ev.data.fd=sockfd;
//设置用于注测的写操作事件
ev.events=EPOLLOUT|EPOLLET;
//修改sockfd上要处理的事件为EPOLLOUT
//epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);
}
else if(events[i].events&EPOLLOUT) // 如果有数据发送
{
sockfd = events[i].data.fd;
write(sockfd, line, n);
//设置用于读操作的文件描述符
ev.data.fd=sockfd;
//设置用于注测的读操作事件
ev.events=EPOLLIN|EPOLLET;
//修改sockfd上要处理的事件为EPOLIN
epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);
}
}
}
return 0;
}