一、epoll函数
epoll是linux特有的I/O复用函数,它比select和poll要高效的多。epoll用一个事件表来保存用户关心的文件描述符,但是这个事件表需要一个文件描述符来标识。
1、内核事件表
#include<sys/epoll.h>
int epoll_create(int size);
功能:创建一个事件表,并返回这个事件表的文件描述符。这个文件描述符就代表一个epoll模型,用作其他epoll系统调用的第一个参数,以指定要访问的内核事件表。
参数:size参数并不起作用,只是给内核一个提示,告诉它事件表需要多大。
2、操作内核事件表
#include<sys/epoll.h>
int epoll_ctl(int epfd,int op,int fd,struct epoll_event *event);
参数:
epfd:表示要操作的内核事件表的文件描述符。
op:指定操作的类型。操作类型有三种。
EPOLL_CTL_ADD:向事件表中注册fd上的事件。
EPOLL_CTL_MOD:修改fd上的注册事件。
EPOLL_CTL_DEL:删除fd上的注册事件。
fd:是要操作的文件描述符。
event:用来指定事件,它是epoll_event结构指针类型。
struct epoll_event
{
_uint32_t events; //epoll事件
epoll_data_t data; //用户数据
}
epoll_data是一个联合体,其中fd使用的最多,表示指定事件的文件描述符。ptr可以用来指定与fd相关的用户数据。如果要将文件描述符和用户数据关联起来,可以让ptr指向的用户数据这种包含fd。
typedef union epoll_data
{
void* ptr;
int fd;
uint32_t u32;
uint64_t u64;
}
epoll_ctl成功时返回0,失败时返回-1并设置errno。
3、等待事件发生
#include<sys/epoll.h>
int epoll_wait(int epfd,struct epoll_event* events,int maxevents,int timeout);
功能:该函数成功时返回就绪的文件描述符的个数,失败返回-1并设置errno。
参数:
epfd:要监测的epoll模型,也就是要监测的内核事件表。
events:它表示一个结构体数组,是一个输出型参数,用来获取已经就绪的事件的相关信息。events不可以是空指针,内核只负责把数据复制到这个events数组中,而不会去帮助我们在用户态中分配内存。
maxevents:指明events的大小。这个值不能大于epoll_create的参数size。
timeout:设置超时时间的。
4、LT和ET模式
LT(水平触发)模式是默认的模式,在这种模式下epoll相当于一个效率较高poll。采用LT的工作模式时:当epoll_wait检测到其上有事件发生并将此事件通知应用程序后,应用程序可以不立即处理该事件。这样,当应用程序下一次调用epoll_wait时,epoll_wait还会再次向应用程序通告此事件,直到事件被处理。LT同时支持阻塞和非阻塞方式。
ET:当向epoll内核事件表中注册一个文件描述符上的EPOLLET事件的时,epoll将通过ET(边沿模式)来操作该文件描述符,ET模式是epoll的高效工作模式。对于在ET模式工作的文件描述符来说:当epoll_wait检测到其上有事件发生并将此事件通知应用程序后,应用程序应该立即处理该事件,因为后续的epoll_wait调用将不再向应用程序通知这一事件。ET模式在很大程度上降低了同一个epoll事件被重复触发的次数,所以ET要更高效。使用ET模式的文件描述符都应该是非阻塞的,以避免由于一个文件句柄的阻塞读/阻塞写操作把处理多个文件描述符的任务都饿死。ET只支持非阻塞方式。
二、EPOLL工作原理
1、当调用epoll_wait获得就绪文件描述符时,返回的不是实际的描述符,而是一个代表就绪描述符数量的值,你只需要去epoll指定一个数组依次取得相应数量的文件描述符即可,这里使用了内存映射(mmap)技术,会节省一些文件描述符在系统调用时的复制开销。
2、一但文件描述符就绪,内核会采用类似callback的回调机制,迅速激活这个文件描述符,当进程调用epoll_wait时便得到通知。
3、epoll与select和poll不同,epoll将事件表完全交给内核去管理,用户只需要将要监测的文件描述符添加进入内核表中即可,等到事件就绪后内核会自动将就绪事件的文件描述符激活。
那么在内核中是怎样对这些文件描述符进行监测的呢?又是怎样处理就绪事件的呢?
三、epoll的优点
1、内核通过一个事件表直接管理用户感兴趣的所有事件。因此每次调用epoll_wait时,无需反复传入用户感兴趣的事件。
2、epoll采用回调方式来监测就绪事件,算法复杂度为O(1)。epoll_wait返回值是就绪事件的个数。
3、epoll将事件表交给内核去管理,底层用红黑树,效率高。
4、epoll支持ET模式,非常高效。
5、epoll支持的文件描述符最大值一般是65535,比select要优。
例:用epoll方式实现一个服务器,要求用浏览器访问这个服务器后能够简单的在浏览器上输出一句“hello wrold”。
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<string.h>
#include<sys/epoll.h>
#define _SIZE_ 128
typedef struct epoll_msg //创建一个结构体类型,里面保存文件描述符,和一个缓冲区
{
int fd;
char buf[_SIZE_];
}epoll_t,*epoll_p,**epoll_pp;
static void* allocator(int fd)
{
epoll_p buf=(epoll_p)malloc(sizeof(epoll_t));
if(NULL==buf)
{
perror("malloc");
exit(6);
}
buf->fd=fd;
return buf;
}
void delalloc(void* ptr)
{
if(NULL!=ptr)
{
free(ptr);
}
}
int startup(const char* ip,int port)
{
int sock=socket(AF_INET,SOCK_STREAM,0);
if(sock<0)
{
perror("socket");
exit(1);
}
int opt=1;
if(setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt))<0)
{
perror("setsockopt");
exit(2);
}
struct sockaddr_in local;
local.sin_family=AF_INET;
local.sin_port=htons(port);
local.sin_addr.s_addr=inet_addr(ip);
if(bind(sock,(struct sockaddr*)&local,sizeof(local))<0)
{
perror("bind");
exit(3);
}
if(listen(sock,5)<0)
{
perror("listen");
exit(4);
}
return sock;
}
int main(int argc,char* argv[])
{
if(argc!=3)
{
printf("usage:%s ip_local port_local\n",argv[0]);
return 1;
}
int listen_sock=startup(argv[1],atoi(argv[2]));
int epfd=epoll_create(256);
if(epfd<0)
{
perror("epoll_create");
exit(5);
}
struct epoll_event envs;
envs.events=EPOLLIN|EPOLLET;
envs.data.ptr=allocator(listen_sock);
epoll_ctl(epfd,EPOLL_CTL_ADD,listen_sock,&envs);
while(1)
{
int num=0;
int timeout=-1;
struct epoll_event evs[32];
int max=32;
switch((num=epoll_wait(epfd,evs,max,timeout)))
{
case 0:
printf("timeout...");
break;
case -1:
perror("epoll_wait");
break;
default:
{
int i=0;
for(i=0;i<num;i++)
{
int fd=((epoll_p)(evs[i].data.ptr))->fd;
if(fd==listen_sock&&evs[i].events&EPOLLIN)
{
struct sockaddr_in peer;
socklen_t len=sizeof(peer);
int connfd=accept(listen_sock,(struct sockaddr*)&peer,&len);
if(connfd<0)
{
perror("accept");
continue;
}
envs.events=EPOLLIN;
envs.data.ptr=allocator(connfd);
epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&envs);
} //fi
else if(fd!=listen_sock&&evs[i].events&EPOLLIN)
{
int s=read(fd,((epoll_p)(evs[i].data.ptr))->buf,_SIZE_-1);
if(s>0)
{
char* buf=((epoll_p)(evs[i].data.ptr))->buf;
buf[s]=0;
printf("client# %s\n",buf);
evs[i].events=EPOLLOUT;
epoll_ctl(epfd,EPOLL_CTL_MOD,fd,&evs[i]);
}
else if(s==0)
{
epoll_ctl(epfd,EPOLL_CTL_DEL,fd,NULL);
delalloc(evs[i].data.ptr);
evs[i].data.ptr=NULL;
close(fd);
}
else
{
perror("read");
}
} //fi
else if(fd!=listen_sock&&evs[i].events&EPOLLOUT)
{
char *msg="http/1.0 200 ok\r\n\r\n<html><h1>hello wrold</h1></html>";
write(fd,msg,strlen(msg));
delalloc(evs[i].data.ptr);
epoll_ctl(epfd,EPOLL_CTL_DEL,fd,&evs[i]);
// close(fd);
}
}//for
}
}
}
return 0;
}