【Linux网络编程】epoll进阶之水平模式和边沿模式
1.epoll的事件模型
EPOLL事件有两种模型:
Edge Triggered (ET) 边缘触发
:==只有数据到来才触发,不管缓存区中是否还有数据==。ET是高速工作方式,只支持no-block socket
。在这种模式下,当描述符从未就绪变为就绪时,内核通过epoll告诉你。然后它会假设你知道文件描述符已经就绪,并且不会再为那个文件描述符发送更多的就绪通知。请注意,如果一直不对这个fd作IO操作(从而导致它再次变成未就绪),内核不会发送更多的通知(only once)。
Level Triggered (LT) 水平触发
:==只要缓存区有数据都会触发==。LT是缺省的工作方式,并且同时支持block
和no-block socket
。在这种做法中,内核告诉你一个文件描述符是否就绪了,然后你可以对这个就绪的fd进行IO操作。如果你不作任何操作,内核还是会继续通知你的,所以,这种模式编程出错误可能性要小一点。传统的select/poll
都是这种模型的代表。
1.1 ET(边沿模式)的设置
边沿模式不是默认的 epoll
模式,需要额外进行设置。epoll
设置边沿模式是非常简单的,epoll
管理的红黑树示例中每个节点都是 struct epoll_event
类型,只需要将 EPOLLET
添加到结构体的 events
成员中即可:
1 | struct epoll_event ev; |
1.2 基于管道epoll ET触发模式
1 | /************************************************************************* |
1.3 基于网络C/S模型的epoll ET触发模式
server端:
1 | /************************************************************************* |
client端:
1 | /************************************************************************* |
server边沿触发,编译运行,如下所示
运行后,每过5秒钟服务器才输出一组字符,这是就是边沿触发的效果。
更改服务器为水平触发模式,运行程序,如下:
运行后,每5秒输出两组字符串,这是因为只写入了两组,这个模式的服务器,缓冲区有多少读多少。
1.4 基于网络C/S非阻塞模型的epoll ET触发模式
1.4.1 设置非阻塞
在使用epoll ET触发模式
进行读事件的检测时,有新数据达到只会通知一次,那么必须要保证得到通知后将数据全部从读缓冲区中读出。那么,应该如何读这些数据呢?
我们可以循环读取数据,如下所示:
1 | int len = 0; |
但这样做还有一个问题,因为套接字操作默认是阻塞的,当读缓冲区数据被读完之后,读操作就阻塞了也就是调用的 read()/recv() 函数被阻塞了,如果是单线程/进程程序的话,程序就不能往下执行了。
要解决阻塞问题,就需要将套接字默认的阻塞行为修改为非阻塞,需要使用fcntl()
函数进行处理:
1 | // 设置完成之后, 读写都变成了非阻塞模式 |
通过上述分析就可以得出一个结论:epoll 在边沿模式下,必须要将套接字设置为非阻塞模式,但是,这样就会引发另外的一个 bug,在非阻塞模式下,循环地将读缓冲区数据读到本地内存中,当缓冲区数据被读完了,调用的 read()/recv() 函数还会继续从缓冲区中读数据,此时函数调用就失败了,返回 - 1,对应的全局变量 errno 值为 EAGAIN 或者 EWOULDBLOCK 如果打印错误信息会得到如下的信息:Resource temporarily unavailable
演示代码:
server端:
1 | /************************************************************************* |
client端:
1 | /************************************************************************* |
1.5 基于多线程的边沿非阻塞处理
直接上代码吧:
server端:
1 | /************************************************************************* |
client端:
1 | /************************************************************************* |
编译运行,结果如下:
服务器端:
线程1:
线程2: