触发模式和EPOLLONESHOT
1. 基本概念
水平触发: LT
缺省的工作模式,当被监控的文件描述符上有可读写的事件发生时,epoll_wait() 就会给用户通知,如果用户没有一次的将数据读完(可能是读写缓冲区太小),那么每次调用epoll_wait(),都会给用户通知。
读缓冲区有数据 - > epoll检测到了会给用户通知
a.用户不读数据,数据一直在缓冲区,epoll 会一直通知
b.用户只读了一部分数据,epoll会通知
c.缓冲区的数据读完了,不通知
边缘触发: ET
当被监控的文件描述符上有可读写的事件发生时,epoll_wait() 只会给用户通知一次。哪怕数据没有一次性读完,再次调用epll_wait()也不会给用户通知。只有当再有新的读写请求到来时,调用epoll_wait()才会给用户通知,但此时读到的数据将包含是上一次没读完的遗留数据,因此边缘触发模式下一定要一次性将数据读完,否则可能出现数据不同步的情况。
读缓冲区有数据 - > epoll检测到了会给用户通知
a.用户不读数据,数据一致在缓冲区中,epoll下次检测的时候就不通知了
b.用户只读了一部分数据,epoll不通知
c.缓冲区的数据读完了,不通知
当用户只读了一部分数据后,又有新的请求数据到来,epoll检测会通知,但此时读的数据将会包含上一次没读完的遗留数据
EPOLLONESHOT:
即使可以使用 ET 模式,一个socket 上的某个事件还是可能被触发多次。这在并发程序中就会引起一个问题。比如一个线程在读取完某个 socket 上的数据后开始处理这些数据,而在数据的处理过程中该socket 上又有新数据可读(EPOLLIN 再次被触发),此时另外一个线程被唤醒来读取这些新的数据。于是就出现了两个线程同时操作一个 socket 的局面。
一个socket连接在任一时刻都只被一个线程处理,可以使用 epoll 的 EPOLLONESHOT 事件实现。对于注册了 EPOLLONESHOT 事件的文件描述符,操作系统最多触发其上注册的一个可读、可写或者异常事件,且只触发一次,除非我们使用 epoll_ctl 函数重置该文件描述符上注册的 EPOLLONESHOT 事件。这样,当一个线程在处理某个 socket 时,其他线程是不可能有机会操作该 socket 的。但反过来思考,注册了EPOLLONESHOT 事件的 socket 一旦被某个线程处理完毕, 该线程就应该立即重置这个socket 上的 EPOLLONESHOT 事件,以确保这个 socket 下一次可读时,其 EPOLLIN 事件能被触发,进而让其他工作线程有机会继续处理这个 socket。
就是在线程回调函数process()的最后重置EPOLLONESHOT
阻塞和非阻塞:
阻塞和非阻塞是文件描述符的属性,而不是系统调用的性质,可以通过fcntl()来改变文件描述符是否阻塞
阻塞IO: 当去读一个阻塞的fd后,如果fd没有数据可读,就会一直阻塞在这里;去写一个阻塞的fd,没有空间可写,也会阻塞在那里。此时CPU无法得到释放去做其他事情
非阻塞IO: 当读一个非阻塞的fd,不论有没有数据,都会立即返回。返回成功说明读写操作完成,返回失败会设置相应的errno,根据errno做相应的处理。在没有数据读写的时候,CPU可以得到释放去做其他事情
2. 为什么边缘触发必须使用非阻塞IO
前面提到的,使用边缘触发如果不一次性读取一个事件上的数据,会干扰下一个事件;造成信息不同步比如接受窗口大小是5,第一次发送了12345678,则客户端只会收到12345,当服务端再次发送一个a,则客户端就会收到678\na。
那如果加入while循环,让其可以一次性读完数据,在while里面一直读,读完就发送,那么由于是阻塞的,读到没有数据可读,recv就会一直阻塞在那里。无法处理客户端来的信息
借用一下别人对几种IO模型的触发模式的总结:
总结:
- 对于监听的sockfd,最好使用水平触发模式,边缘触发模式会导致高并发情况下,有的客户端会连接不上。如果非要使用边缘触发,网上有的方案是用while来循环accept()。
- 对于读写的connfd,水平触发模式下,阻塞和非阻塞效果都一样,不过为了防止特殊情况,还是建议设置非阻塞。【若是要while一次性读完,也不能用阻塞的吧】
- 对于读写的connfd,边缘触发模式下,必须使用非阻塞IO,并要一次性全部读写完数据。
参考链接:
https://blog.csdn.net/Jiangtagong/article/details/116356621
标签:触发,socket,epoll,阻塞,模式,EPOLLONESHOT,数据 From: https://www.cnblogs.com/Yuqi0/p/17146847.html