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是否为空即可,从而达到高效的事件处理。
LT和ET
对于事件触发的机制,epoll默认为LT(level trigger,水平触发),同时提供ET(edge trigger,边缘触发)。
| 模式 | 说明 | 读事件 | 写事件 |
|---|---|---|---|
| LT | 满足条件则一直触发事件 | 读缓冲区非空,一直触发 | 写缓冲区可写,一直触发 |
| ET | 满足条件时触发一次 | 读缓冲区有新数据来临时触发一次 | 写缓冲区由不可写变为可写触发一次 |
可以看到LT和ET各有优缺点,LT相对ET来说更符合逻辑思维,因为不会担心会漏事件,但会有很多的无效事件产生需要处理(如当缓冲区可写时一直触发写事件)。而ET则更高效,事件触发更少,但需要在事件触发时完成IO的读取,不然会丢事件。
实际上两种模式都有大量的使用,LT和ET应用举例:
| 模式 | 网络框架 |
|---|---|
| LT | redis |
| ET | nginx,netty,golang net(kite) |