epoll 系统调用
1. 内核事件表
epoll使用一系列函数来完成任务,把用户关心的文件描述符中的事件放到内核里的一个事件表中,因此不用像select、poll那样每次调用都要重复传入文件描述符集或事件表。epoll需要一个文件描述符来唯一标识该事件表,该文件描述符使用epoll_create
函数创建:
#include <sys/epoll.h>int epoll_create( int size );
size 标记事件表大小。该函数返回的文件描述符将用作其他所有epoll系统调用的第一个参数,以指定要访问的内核事件表。
epoll_ctl
函数用于操作epoll的内核事件表:
#include <sys/epoll.h>int epoll_ctl( int epfd, int op, int fd, struct epoll_event * event )
fd参数是要操作的文件描述符,op参数指定操作类型(如下):
- EPOLL_CTL_ADD,往事件表中注册fd上的事件
- EPOLL_CTL_DEL,删除fd上的注册事件
- EPOLL_CTL_MOD,修改fd上的注册事件
event参数指定被操作的事件,它是epoll_event结构类型指针。epoll_event的定义如下:
struct epoll_event{__uint32_t events; //epoll事件epoll_data_t data; //用户数据}
其中events成员描述事件类型(如 EPOLLIN表示数据可读事件)。epoll_data_t 定义如下:
typedef union epoll_data{void * ptr;int fd;uint32_t u32;uint64_t u64;} epoll_data_t;
epoll_data_t 是一个共用体,其4个成员使用最多的是fd,它指定事件所从属的目标文件描述符。各成员不能同时使用。
2. epoll_wait 函数
epoll_wait 函数在一段超时时间内等待一组文件描述符上的事件,其原型如下:
#include <sys/epoll.h>
int epoll_wait( int epfd, struct epoll_event * events, int maxevents, int timeout )
该函数成功时返回就绪的文件描述符的个数,失败时返回-1并设置errno。timeout参数为延时时间。maxevents参数指定最多监听多少个事件,它必须大于0。
epoll_wait函数如果检测到事件,就将所有就绪的事件从内核事件表(由epfd指定)中复制到它的第二个参数events指向的数组中,该数组只用于输出epoll_wait检测到的就绪事件,而不像select和poll那样既传入用户注册事件,又输出内核检测到的就绪事件。这极大提高了应用程序索引就绪文件描述符的效率。
/* 如何索引 epoll 返回的就绪文件描述符 */int ret = epol_wait( epoll_fd, events, MAX_EVENT_NUMBER, -1 );// 仅遍历就绪的ret个文件描述符for ( int i = 0; i < ret; i++ ){int sock_fd = events[i].data.fd; //events中的所有文件描述符都已就绪handle( sock_fd ); //处理就绪的sock_fd}
3. LT和ET模式
LT : 电平触发(默认工作方式)
ET :边沿触发(高效工作方式)
对于采用LT
工作模式的文件描述符,当epoll_wait检测到其上有事件发生并将此事件通知应用程序后,应用程序可以不立即处理该事件。这样,当下一次调用epoll_wait()时,epoll_wait()还会再次向应用程序通知此事件,直至该事件被处理。
对于采用ET
工作模式的文件描述符,当epoll_wait检测到其上有事件发生并将此事件通知应用程序后,应用程序必须立即处理该事件,因为后续的epoll_wait()将不再向应用程序通知该事件。
可见,ET模式在很大程度上降低了同一个epoll事件被重复触发的次数,因此效率会比LT模式高。
4. code
/* 该程序以 “ ./a.out ip_number port_number ” 方式使用
** 通过telnet连接到该运行程序 */#include <iostream>
#include <cstdio>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <sys/sendfile.h>
#include <unistd.h>
#include <signal.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <cstdlib>
#include <cstring>
#include <fcntl.h>
#include <sys/epoll.h>
using namespace std;const int MAX_EVENT_NUMBER = 1024;
const int BUFFER_SIZE = 10;void err( int );
void addfd( int , int , bool );
int setnonblocking( int );
void lt( epoll_event * , int , int , int );
void et( epoll_event * , int , int , int );int main(int argc, char * argv[]) {if ( argc < 3 ) {cout << "usage:\n";return 1;}const char *ip = argv[1];const int port = atoi( argv[2] );struct sockaddr_in address;memset( &address, 0, sizeof( address ) );address.sin_family = AF_INET;address.sin_port = htons( port );inet_pton( AF_INET, ip, &address.sin_addr );int sock_fd = socket( AF_INET, SOCK_STREAM, 0 );if ( sock_fd < 0) {err( __LINE__ );}int ret = bind( sock_fd, ( struct sockaddr * )&address, sizeof( address ) );if ( ret < 0 ) {err( __LINE__ );}ret = listen( sock_fd, 5 );if ( ret < 0 ) {err( __LINE__ );}epoll_event events[MAX_EVENT_NUMBER]; //内核事件表,通过epoll_wait()函数发生作用int epoll_fd = epoll_create( 5 );if ( epoll_fd < 0 ) {err( __LINE__ );}addfd( epoll_fd, sock_fd, true );while( true ) {ret = epoll_wait( epoll_fd, events, MAX_EVENT_NUMBER, -1 );if ( ret < 0 ) {cout << "epoll failure\n";break;}lt( events, ret, epoll_fd, sock_fd ); //使用LT模式// et( events, ret, epoll_fd, sock_fd ); //使用ET模式}close( sock_fd );return 0;
}void err(int line) {cout << "error: line " << line << endl;
}void addfd( int epoll_fd, int fd, bool enable_et ) {epoll_event event;event.data.fd = fd;event.events = EPOLLIN;if ( enable_et ) {event.events |= EPOLLET;}epoll_ctl( epoll_fd, EPOLL_CTL_ADD, fd, &event );setnonblocking( fd );
}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 lt( epoll_event * events, int number, int epoll_fd, int listen_fd) {char buf[ BUFFER_SIZE ];for( int i = 0; i < number; i++ ) {int sock_fd = events[i].data.fd;if ( sock_fd == listen_fd ) { //有新的连接请求struct sockaddr_in client;socklen_t client_length = sizeof( client );int conn_fd = accept( sock_fd, ( struct sockaddr * )&client,&client_length );addfd( epoll_fd, conn_fd, false );} else if ( events[i].events & EPOLLIN ) { //还存在未读数据cout << "event trigger once\n";memset( buf, 0, sizeof( buf ) );int ret = recv( sock_fd, buf, BUFFER_SIZE - 1, 0 );if ( ret <= 0 ) {close( sock_fd );}printf( "get %d bytes of content: -%s-\n", ret, buf );} else {cout << "something else happened\n";}}
}void et( epoll_event * events, int number, int epoll_fd, int listen_fd) {char buf[ BUFFER_SIZE ];for ( int i = 0; i < number; i++ ) {int sock_fd = events[i].data.fd;if ( sock_fd == listen_fd ) { //有新的连接请求struct sockaddr_in client;socklen_t client_length = sizeof( client );int conn_fd = accept( sock_fd, ( struct sockaddr * )&client,&client_length );addfd( epoll_fd, conn_fd, true );} else if ( events[i].events & EPOLLIN ) { //还存在未读数据printf("event trigger once\n");while( true ) {memset( buf, 0, sizeof( buf ) );int ret = recv( sock_fd, buf, BUFFER_SIZE - 1, 0 );if ( ret < 0 ) {/* 数据已全部读取完毕 */if ( errno == EAGAIN || errno == EWOULDBLOCK ) {cout << "read later\n";break;}close( sock_fd );break;} else if ( !ret ) {close( sock_fd );} else {printf("got %d bytes of content: -%s-\n", ret, buf);}}}}
}