关于epoll是干什么的,网上已经有很多了,不再赘述,我就是想写写我是怎么用的,很初级的用法。
epoll的用法非常简单,简单到三个函数已经足够了。他们分别是
epoll描述符的创建
int epoll_create(int size)
该函数生成一个epoll专用的文件描述符(FD)。本质就是在内核申请一定的空间,用来存放你想关注的socket fd上是否发生了什么事件(读写)。size就是你在这个epoll fd上能关注的最大的socket fd数目。只要你有空间,随便你开到多大。
epoll描述符的修改
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
该函数用于控制某个epoll fd上的事件,可以注册,修改,删除事件。
参数:
1. epfd 这个没什么好说,就是你想作用的epoll fd
2. op 对epoll fd要进行的操作。可取的有EPOLL_CTL_ADD EPOLL_CTL_MOD EPOLL_CTL_DEL
3. fd 关联的文件描述符,一般是某个socket fd
4. event 指向epoll_event的指针
如果调用成功返回0,失败则返回-1
epoll_ctl中用到的数据结构
typedef union epoll_data {
void *ptr;
int fd;
__uint32_t u32;
__uint64_t u64;
} epoll_data_t;
struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
在epoll_data中,我们可以自定义需要的东西,所用到的就是void *ptr
,他可以指向一个自定义的数据结构,在那个数据结构中,我们可以定义callback函数,或者一些类似状态的东西,供epoll fd被唤醒时用。
epoll_event
中的events有如下几种事件类型:
1. EPOLLIN:可读
2. EPOLLOUT:可写
3. EPOLLPRI:有紧急的数据可读
4. EPOLLERR:发生错误
5. EPOLLHUP:被挂断
6. EPOLLET:有事件发生
在设置要处理的事件类型时,我们一般这么写:ev.events=EPOLLIN|EPOLLOUT;
epoll轮询
int epoll_wait(int epfd,struct epoll_event * events,int maxevents,int timeout)
用于轮询epfd上I/O事件的发生
参数:
1. epfd:由epoll_create生成的epoll fd
2. epoll_event:用于回传待处理事件的数组
3. maxevents:每次能处理的最多的事件数
4. timeout:等待I/O事件发生的超时值
用法如下所示:
//build the epoll event for recall
struct epoll_event ev[20];
int nfds = epoll_wait(epoll_fd,ev,20,1000);
for(int i=0; i<nfds; ++i){
if(ev[i].data.fd==sock)
...
}
epoll_wait运行的原理是
等侍注册在epfd上的socket fd的事件的发生,如果发生则将发生的sokct fd和事件类型放入到events数组中。
需要注意的是:
epoll_wait会将注册在epfd上的socket fd的事件类型给清空,所以如果下一个循环你还要关注这个socket fd的话,则需要用epoll_ctl(epfd,EPOLL_CTL_MOD,listenfd,&ev)
来重新设置socket fd的事件类型。这时不用EPOLL_CTL_ADD,因为socket fd并未清空,只是事件类型清空。这一步非常重要。
epoll有两种工作方式:LT和ET,默认是LT。
LT(level triggered)是缺省的工作方式,并且同时支持block和no-block socket.在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll都是这种模型的代表.
ET (edge-triggered)是高速工作方式,只支持no-block socket。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知,直到你做了某些操作导致那个文件描述符不再为就绪状态了(比如,你在发送,接收或者接收请求,或者发送接收的数据少于一定量时导致了一个EWOULDBLOCK 错误)。但是请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once)。所以在这种情况下,ET读的读要一直读,写要一直写,对程序员提出了更高的要求。
如果程序中用了两个线程,一个监听accept,用一个监听epoll_wait,这个时候listen socket fd用默认的阻塞就好了。如果只有一个线程,那么listen socket
fd在创建的时候就要对他进行非阻塞的声明。
fcntl(listenFd, F_SETFL, O_NONBLOCK);