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_data_t data;
};
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;
}
struct epoll_event ev,events[
20];
epfd=epoll_create(
256);
struct sockaddr_in clientaddr;
struct sockaddr_in serveraddr;
listenfd = socket(AF_INET, SOCK_STREAM,
0);
ev.data.fd=listenfd;
ev.events=EPOLLIN|EPOLLET;
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));
serveraddr.sin_port=htons(portnumber);
bind(listenfd,(sockaddr *)&serveraddr,
sizeof(serveraddr));
listen(listenfd, LISTENQ);
maxi =
0;
for ( ; ; ) {
nfds=epoll_wait(epfd,events,
20,
500);
for(i=
0;i<nfds;++i)
{
if(events[i].data.fd==listenfd)
{
connfd = accept(listenfd,(sockaddr *)&clientaddr, &clilen);
if(connfd<
0){
perror(
"connfd<0");
exit(
1);
}
char *str = inet_ntoa(clientaddr.sin_addr);
cout <<
"accapt a connection from " << str << endl;
ev.data.fd=connfd;
ev.events=EPOLLIN|EPOLLET;
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;
}
else if(events[i].events&EPOLLOUT)
{
sockfd = events[i].data.fd;
write(sockfd, line, n);
ev.data.fd=sockfd;
ev.events=EPOLLIN|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);
}
}
}
return 0;
}