Epoll介绍和使用

xiaoxiao2021-02-28  95

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; }
转载请注明原文地址: https://www.6miu.com/read-63361.html

最新回复(0)