水平触发LT和边沿触发ET
内核中有一个与socket关联的读写缓冲区。
水平触发的意思是只要缓冲区不为空就触发可读事件,只要缓冲区不满就触发可写事件。
而边缘触发的意思是当缓冲区从满到不满的时间点触发一次可写事件,当缓冲区从空到非空的时间点触发一次可读事件。
水平触发只要缓冲区不空就持续触发可读事件,只要缓冲组不满就持续触发可写事件。
而边缘触发只在缓冲区从满到不满的时间点触发一次可写事件,后续不会再次触发可写事件,只在缓冲区从空到不空的时间点触发一次可读事件,后续不会再次触发可读事件。
所谓的边缘就是状态发生改变的点,从空到不空(可读),从满到不满(可写)。只在状态发生改变的时候触发一次的触发方式叫边缘触发,只要可读或者可写,就持续发送可读或者可写事件的触发模式叫水平触发。
水平触发可读,
水平触发可写,
边沿触发可读,
边沿触发可写,
阻塞模式和非阻塞
socket分为监听socket和连接socket。
阻塞和非阻塞主要是针对连接socket的读写来说的。即阻塞读写还是非阻塞读写。
监听socket阻塞和非阻塞效果都一样,水平触发和边缘触发效果也一样,数据固定,一次读取完毕。
连接socket如果是阻塞的模式,无数据可读或者无空间可写的情况下,都会阻塞当前的连接socket。
连接socket如果是非阻塞模式的,无论有没有数据可读,也无论有没有空间可写,都不会阻塞当前的连接socket。
IO多路复用
IO多路复用,又叫做事件驱动,即reactor响应器模式。
所谓的复用指的复用同一个线程。
多路复用的实现是通过系统调用(select/poll和epoll)来提供的,这些系统调用可以同时监视多个socket的事件,主要是指连接socket上的读写事件。
服务端在监听socket上接受新的连接并创建新的连接socket之后,会将新创建的连接socket加入到系统调用的监视列表中。
只有当读写事件发生时,系统调用才会返回,然后去调用read/write操作,避免了遍历socket的操作。
select和poll仍然需要遍历所有的socket列表,找到发生事件的那个socket,然后进行读写操作。
epoll可以直接处理确定产生事件的socket列表。
IO多路复用-触发方式
select和poll使用水平触发模式。
epoll使用水平和边缘触发模式。
redis使用LT水平触发模式。
nginx使用ET边沿触发模式。
IO多路复用-socket使用非阻塞模式
当系统调用返回有可读或者可写的连接socket之后,如果socket是阻塞的,调用read/write操作可能会被阻塞,从而block当前进程或者线程,无法去处理别的任务或者接受新的客户端连接。
所以多路复用情况下,一般使用非阻塞模式的socket。
select和socket的send/recv系统调用是两个独立的系统调用,select返回可读,send/recv不一定可读,两个系统调用之间存在一定的时间窗口。另外,当有多个进程或者线程同时用select等待一个socket的时间,譬如等待监听socket的连接事件,当一个连接成功建立三次握手(tcp)之后,多个进程或者线程都会被唤醒,但是只有一个能够accept到这个连接,其他没有accept到这个连接的进程或者线程就会被accept系统调用阻塞住。(惊群现象),还有就是,select/poll或者epoll返回时表示读已经就绪,但是随后的协议栈校验和如果不正确的话,可能会被内核丢弃,此时无数据可读,导致read操作阻塞。
select返回某个描述符读写就绪,并不意味着接下来的读写操作全过程就一定不会阻塞。
以上
------------------------------
虽然(写就绪呢 ?)
(边缘触发的socket要一次性处理完所有的accept,会有多个监听socket吗?一个监听socket可以同时接受多个客户端连接吗?)。
1. 对于监听的 sockfd,最好使用水平触发模式,边缘触发模式会导致高并发情况下,有的客户端会连接不上。如果非要使用边缘触发,可以用 while 来循环 accept()。
2. 对于读写的 connfd,水平触发模式下,阻塞和非阻塞效果都一样,建议设置非阻塞。
3. 对于读写的 connfd,边缘触发模式下,必须使用非阻塞 IO,并要求一次性地完整读写全部数据。
1. sockfd 的边缘触发,高并发时,如果没有一次处理全部请求,则会出现客户端连接不上的问题。不需要讨论 sockfd 是否阻塞,因为 epoll_wait() 返回的必定是已经就绪的连接,所以不管是阻塞还是非阻塞,accept() 都会立即返回。
2. 阻塞 connfd 的边缘触发,如果不一次性读取一个事件上的数据,会干扰下一个事件,所以必须在读取数据的外部套一层循环,这样才能完整的处理数据。但是外层套循环之后会导致另外一个问题:处理完数据之后,程序会一直卡在 recv() 函数上,因为是阻塞 IO,如果没数据可读,它会一直等在那里,直到有数据可读。但是这个时候,如果用另一个客户端去连接服务器,服务器就不能受理这个新的客户端了。
3. 非阻塞 connfd 的边缘触发,和阻塞版本一样,必须在读取数据的外部套一层循环,这样才能完整的处理数据。因为非阻塞 IO 如果没有数据可读时,会立即返回,并设置 errno。这里我们根据 EAGAIN 和 EWOULDBLOCK 来判断数据是否全部读取完毕了,如果读取完毕,就会正常退出循环了。
使用非阻塞模式,只要有数据就会返回。
边缘触发需要设置socket为非阻塞模式,否则read或者write后会被阻塞?
select怎么检查到有事件发生的?
边沿触发必须的不可阻塞的吗?
如果server的响应通常较小,不会触发EPOLLOUT,那么适合使用LT,例如redis等。、
而nginx作为高性能的通用服务器,网络流量可以跑满达到1G,这种情况下很容易触发EPOLLOUT,则使用ET。
当条件不满足时等系统告诉我条件满足
来源:oschina
链接:https://my.oschina.net/u/1585028/blog/4650849