epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。
根据man手册的说法,epoll是为处理大批量句柄而作了改进的poll。
epoll是在205044内核中被引进的,被公认为Linux2.6下性能最好的多路I/O就绪通知方法。
epoll有三个相关的系统调用。
epoll_create
调用epoll_create,创建epoll模型。
自从Linux2.6.8之后,size参数是被忽略的。但是为了确保可移植性,我们建议将size的参数填写进去。通常size的值需要填一个很大的数。
用完之后,必须调用close()关闭。
epoll_ctl
epoll_create向epoll模型注册文件描述符对应的事件。它不同于select()是在监听事件时告诉内核要监听什么类型的事件,而是在这里先注册要监听的事件类型。
第一个参数是epoll_create()的返回值。
第二个参数表示动作,用三个宏来表示:
EPOLL_CTL_ADD:注册新的fd到epfd中;
EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
EPOLL_CTL_DEL:从epfd中删除一个fd。
第三个参数表示需要监听的fd。
第四个参数是告诉内核需要监听什么事,struct epoll_event的结构如下:
events可以是以下几个宏的集合:
EPOLLIN:表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
EPOLLOUT:表示对应的文件描述符可以写;
EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
EPOLLERR:表示对应的文件描述符发生错误;
EPOLLHUP:表示对应的文件描述符被挂断;
EPOLLET:将EPOLL设为边缘触发模式,这是相对于水平触发来说的;
EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次将这个socket加入到EPOLL队列里。
epoll_wait
收集在epoll监控的事件中已经发送的事件。
参数events是分配好的epoll_event结构体数组。
epoll将会把发生的事件赋值到events数组中(events不可以是空指针,内核只负责将数据复制到这个events数组中,不会去帮助我们在用户态中分配内存)。
maxevents告知内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size。
参数timeout是超时时间,单位是毫秒。当设置为0时会立即返回,-1时会永久阻塞。
如果函数调用成功,返回对应I/O上已经准备好的文件描述符数目。若返回0表示已超时,返回小于0表示函数失败。
(1)文件描述符数目无上限:通过epoll_ctl( )来注册一个文件描述符,内核中使用红黑树的数据结构来管理所有需要监控的文件描述符;
(2)基于事件的就绪通知方式:一旦被监听的某个文件描述符就绪,内核会采用类似于callback的回调机制,迅速激活这个文件描述符,这样随着文件描述符数量的增加,也不会影响判定就绪的性能;
(3)维护就绪队列:当文件描述符就绪,就会被放到内核中的一个就绪队列中。这样调用epoll_wait获取就绪文件描述符的时候,只要取队列中的元素即可,操作的时间复杂度是O(1)。
epoll有两种工作方式:水平触发(LT)和边缘触发(ET)。
水平触发Level Triggered工作模式:
epoll默认状态下就是LT工作模式。
当epoll检测到socket上事件就绪的时候,可以不立刻进行处理,或者只处理一部分。
对于读操作:只要缓冲内容不为空,LT模式返回读就绪;
对于写操作:只要缓冲区还不满,LT模式会返回写就绪。
LT支持阻塞读写和非阻塞读写。
边缘触发Edge Triggered工作模式:
当epoll检测到socket上事件就绪时,必须立刻处理。也就是说,ET模式下,文件描述符上的事件就绪后,只有一次处理机会。
ET的性能比LT性能更高。
ET只支持非阻塞的读写。
select和poll其实也是工作在LT模式下。epoll既可以支持LT,也可以支持ET。
epoll的高性能,是有一定的特定场景的。如果场景选择的不适宜,epoll的性能可能适得其反。
对于多连接,且多连接中只有一部分连接比较活跃时,比较适合使用epoll。
如果只是系统内部,服务器和服务器之间进行通信,只有少数的几个连接,这种情况下用epoll就并不合适。具体要根据需求和场景特点来决定使用哪种IO模型。
服务器端:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
void SetNoBlock(int fd){
int fl = fcntl(fd,F_GETFL);
if(fl < 0){
perror("fcntl");
return;
}
fcntl(fd,F_SETFL,fl | O_NONBLOCK);
}
void ProcessConnect(int listen_fd,int epoll_fd){
struct sockaddr_in client_addr;
socklen_t len = sizeof(client_addr);
int connect_fd = accept(listen_fd,(struct sockaddr*)&client_addr,&len);
if(connect_fd < 0){
perror("accept");
return;
}
printf("client %s:%d connect\n",inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port));
SetNoBlock(connect_fd);
struct epoll_event ev;
ev.data.fd = connect_fd;
ev.events = EPOLLIN | EPOLLET;
int ret = epoll_ctl(epoll_fd,EPOLL_CTL_ADD,connect_fd,&ev);
if(ret < 0){
perror("epoll_ctl");
return;
}
return;
}
ssize_t NoBlockRead(int fd,char* buf,int size){
(void) size;
ssize_t total_size = 0;
for(;;){
ssize_t cur_size = read(fd,buf+total_size,1024);
total_size += cur_size;
if(cur_size < 1024 || errno == EAGAIN){
break;
}
}
buf[total_size] = '\0';
return total_size;
}
void ProcessRequest(int connect_fd,int epoll_fd){
char buf[1024] = {0};
ssize_t read_size = NoBlockRead(connect_fd,buf,sizeof(buf)-1);
if(read_size < 0){
perror("read");
return;
}
if(read_size == 0){
close(connect_fd);
epoll_ctl(epoll_fd,EPOLL_CTL_DEL,connect_fd,NULL);
printf("client echo: stop...\n");
return;
}
printf("client say: %s",buf);
write(connect_fd,buf,strlen(buf));
}
int main(int argc,char* argv[]){
if(argc != 3){
printf("Usage: ./server [ip] [port]\n");
return 1;
}
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_addr.s_addr = inet_addr(argv[1]);
addr.sin_port = htons(atoi(argv[2]));
int listen_fd = socket(AF_INET,SOCK_STREAM,0);
if(listen_fd < 0){
perror("socket");
return 1;
}
int ret = bind(listen_fd,(struct sockaddr*)&addr,sizeof(addr));
if(ret < 0){
perror("bind");
return 1;
}
ret = listen(listen_fd,5);
if(ret < 0){
perror("listen");
return 1;
}
int epoll_fd = epoll_create(10);
if(epoll_fd < 0){
perror("epoll_create");
return 1;
}
SetNoBlock(listen_fd);
struct epoll_event event;
event.events = EPOLLIN | EPOLLET;
event.data.fd = listen_fd;
ret = epoll_ctl(epoll_fd,EPOLL_CTL_ADD,listen_fd,&event);
if(ret < 0){
perror("epoll_ctl");
return 1;
}
for(;;){
struct epoll_event events[10];
int size = epoll_wait(epoll_fd,events,sizeof(events)/sizeof(events[0]),-1);
if(size < 0){
perror("epoll_wait");
continue;
}
if(size == 0){
printf("epoll timeout...\n");
continue;
}
int i = 0;
for(;i < size;++i){
if(!(events[i].events & EPOLLIN))
continue;
if(events[i].data.fd == listen_fd)
ProcessConnect(listen_fd,epoll_fd);
else
ProcessRequest(events[i].data.fd,epoll_fd);
}
}
return 0;
}
客户端:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#define MAX 128
int main(int argc,char* argv[]){
if(argc != 3){
printf("Usage:%s [ip] [port]\n",argv[0]);
return 1;
}
int sock = socket(AF_INET,SOCK_STREAM,0);
if(sock < 0){
printf("socket error!\n");
return 2;
}
struct sockaddr_in server;
server.sin_family = AF_INET;
server.sin_port = htons(atoi(argv[2]));
server.sin_addr.s_addr = inet_addr(argv[1]);
if(connect(sock,(struct sockaddr*)&server,sizeof(server)) < 0){
printf("connect error!\n");
return 3;
}
char buf[MAX];
while(1){
printf("please Enter# ");
fflush(stdout);
read(0,buf,sizeof(buf)-1);
/*if(s > 0){
buf[s-1] = 0;
if(strcmp("quit",buf) == 0){
printf("client quit!\n");
break;
}*/
ssize_t write_size = write(sock,buf,strlen(buf));
if(write_size < 0){
perror("write");
continue;
}
ssize_t s = read(sock,buf,sizeof(buf)-1);
if(s < 0){
perror("read");
continue;
}
if(s == 0){
printf("server close!\n");
break;
}
//buf[s] = 0;
printf("server Echo# %s\n",buf);
}
close(sock);
return 0;
}