epoll是Linux特有的I/O复用函数。它在实现和使用上与select、poll有很大差异。首先,epoll使用一组函数来完成任务,而不是单个函数。其次,epoll把用户关心的文件描述符上的事件放在内核里的一个事件表中,从而无需像select和poll那样每次调用都要重复传入文件描述符集或事件集。但epoll需要使用一个额外的文件描述符,来唯一标识内核中的这个事件表。这个文件描述符由epoll_creat函数来创建。(《Linux高性能服务器编程》)
int epoll_ctl (int epfd , int op , int fd , struct epoll_event *event)
fd参数是要操作的文件描述符,op指定操作类型。操作类型有如下3种:
1、EPOLL_CTL_ADD 往事件表中注册fd上的事件
2、EPOLL_CTL_MOD 修改fd上的注册事件
3、EPOLL_CTL_DEL 删除fd上注册的事件
event参数指定事件,它是epoll_event结构指针类型。
struct epoll_event
{
_uint32_t events; events成员描述事件类型
epoll_data_t data; data成员用于存储用户数据
};epoll_data_t是个联合体
epoll_ctl 成功时返回0,失败则返回-1 并设置errno
epoll_wait函数
epoll系列系统调用的主要接口是epoll_wait函数。它在一段超时时间内等待一组文件描述符上的事件
int epoll_wait (int epfd , struct epoll_event*events , int maxevents , int timeout);
该函数成功时返回就绪文件描述符的个数,失败时返回-1并设置erron。
timeout:超时时间
maxevents参数指定最多监听多少个事件,它必须大于0
epoll_wait函数如果检测到事件,就将所有就绪的事件从内核事件表(由epfd参数指定)中复制到它的第二个参数events指向的数组中。这个数组只用于输出epoll_wait函数检测到的就绪事件
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/time.h>
#include<string.h>
#include<assert.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<sys/epoll.h>
#define SIZE 100
int main()
{
int sockfd=socket(AF_INET,SOCK_STREAM,0);
assert(sockfd!=-1);
struct sockaddr_in ser ,cli;
memset(&ser,0,sizeof(ser));
ser.sin_family=AF_INET;
ser.sin_port=htons(6000);
ser.sin_addr.s_addr=inet_addr("127.0.0.1");
int res = bind(sockfd,(struct sockaddr*)&ser,sizeof(ser));
assert(res!=-1);
listen(sockfd,5);
int epollfd=epoll_create(5);//创建内核时间表
assert(epollfd!=-1);
struct epoll_event event;
event.events=EPOLLIN;//事件类型
event.data.fd=sockfd;
epoll_ctl(epollfd,EPOLL_CTL_ADD,sockfd,&event);//添加描述符,注册到内核事件表中
while(1)
{
struct epoll_event events[SIZE];
int n = epoll_wait(epollfd,events,SIZE,-1);//就绪文件描述符的个数events就是就绪的文件描述符
if(n<=0)
{
printf("epoll wait error\n");
continue;
}
int i=0;
for(;i<n;++i)
{
int fd=events[i].data.fd;
if(fd==sockfd)
{
int len=sizeof(cli);
int c=accept(fd,(struct sockaddr*)&cli,&len);
if(c<0)
{
continue;
}
event.events=EPOLLIN|EPOLLRDHUP;
event.data.fd=c;//事件所从属的目标文件描述符
epoll_ctl(epollfd,EPOLL_CTL_ADD,c,&event);//往事件表epollfd中注册c事件
}
else if(events[i].events & EPOLLRDHUP)//判断哪个事件文件描述符关闭
{
epoll_ctl(epollfd,EPOLL_CTL_DEL,fd,NULL);
close(fd);
printf("%d was over\n",fd);
}
else if(events[i].events & EPOLLIN)
{
char buff[128]={0};
recv(fd,buff,127,0);
printf("%d:%s\n",fd,buff);
send(fd,"OK",2,0);
}
}
}
}
1、select poll:每次循环都需要从用户空间向内核空间传递数据
epoll:直接在内核中创建事件表,每个描述符仅需要传递一次
2、select poll :在内核中以轮询的方式检测有就绪事件的描述符(O(n))
epoll:在每个描述符上注册回调函数,事件就绪后,执行回调函数将描述符添加到就绪队列(O(1))
3、select poll:返回后需要遍历所有文件描述符,才能找到就绪的(O(n))
epoll:返回后直接得到就绪文件描述符,不需要遍历所有文件描述符(O(1))