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);
}
}
}
}
}