深入理解Epoll

376 阅读2分钟

Epoll

基本原理

Epoll是linux提供的多路复用网络IO模型,多路复用意味着epoll能够用较少的资源监听较多的网络链接,从而支撑高并发场景。

TCP的读写都是通过缓冲区来实现的,操作系统会为每一个tcp链接维护读缓冲区和写缓冲区。Epoll就是基于监听读写缓冲区的IO事件来实现对网络连接的读写和管理。

Epoll的的api:

//用于创建并返回一个epfd句柄,后续关于fd的添加删除等操作都依据这个句柄。

int epoll_create(int size);

//用于向epfd添加,删除,修改要监听的fd及感兴趣的事件

int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event);

//传入创建返回的epfd句柄,以及超时时间,返回就绪的fd句柄。返回的事件会塞到events中
int epoll_wait(int epfd, struct epoll_event* events, int maxevents, int timeout);

Epoll的典型使用流程

//创建socket

fd = socket(domain, typ, proto)

fd.setNonBlocking(true)

//bind socket

bind(fd,port)

//listen socket

listen(fd,10)



//新建epoll

epfd = epoll_create(1024)

//将感兴趣的fd和事件加入到epoll的监听列表中,此处fd也可为tcp链接的fd

epoll_ctl(epfd, _EPOLL_CTL_ADD,fd,_EPOLLIN | _EPOLLOUT | _EPOLLRDHUP | _EPOLLET)

//循环处理接收到的事件进行处理

for {

    ready_events []event

    //调用epoll_wait进行监听

    result = epoll_wait(epfd, ready_events,MAX_EVENT_LENGTH,time_wait)

    //根据事件处理,取conn,读写io等

    if result > 0 {

        handle(ready_events)

    }

}

在高并发场景下,通常server端需要维护大量的tcp链接,因此epoll必须保证高效的fd和事件管理机制。

实际上epoll是通过红黑树完成对fd的高效查找,同时为每个监听的网络io事件向操作系统注册回调函数,当有实际的网络事件发生时,通过回调函数将对应的事件添加到rdlist中。而epoll_wait只需要观察rdlist是否为空即可,从而达到高效的事件处理。

1630320004625_685511fe0676ae38288f9adf5b12b584.png

LT和ET

对于事件触发的机制,epoll默认为LT(level trigger,水平触发),同时提供ET(edge trigger,边缘触发)。

模式说明读事件写事件
LT满足条件则一直触发事件读缓冲区非空,一直触发写缓冲区可写,一直触发
ET满足条件时触发一次读缓冲区有新数据来临时触发一次写缓冲区由不可写变为可写触发一次

可以看到LT和ET各有优缺点,LT相对ET来说更符合逻辑思维,因为不会担心会漏事件,但会有很多的无效事件产生需要处理(如当缓冲区可写时一直触发写事件)。而ET则更高效,事件触发更少,但需要在事件触发时完成IO的读取,不然会丢事件。

实际上两种模式都有大量的使用,LT和ET应用举例:

模式网络框架
LTredis
ETnginx,netty,golang net(kite)