一、EPOLLIN、EPOLLOUT
1.0 缓冲区
1. 缓冲区机制
在网络通信中,每个套接字都有两个缓冲区:
- 发送缓冲区:存储待发送的数据,位于发送方
- 接受缓冲区:存储接受到的数据,位于接收方
对于TCP连接,数据发送过程如下:
- 应用程序将数据写入发送缓冲区
- TCP协议栈从发送缓冲区取出数据,通过网络发送给接收方
- 接收方的TCP协议栈接受数据(由OS完成),并存在接受缓冲区中
- 应用程序从接受缓冲区中读取数据
2. 事件和缓冲区关系
EPOLLOUT
事件的触发调试是本地发送缓冲区有空间可用。原因:
- 操作系统对发送缓冲区的管理
- 本地资源的管理: 操作系统直接管理每个套接字的发送缓冲区。通过
EPOLLOUT
事件,操作系统通知应用程序本地发送缓冲区是否有空间可以写入。 - 发送缓冲区的状态易于监控: 操作系统实时跟踪和管理发送缓冲区的状态。当发送缓冲区有空间时,
EPOLLOUT
事件就会被触发,通知应用程序可以继续写入数据。
- 本地资源的管理: 操作系统直接管理每个套接字的发送缓冲区。通过
- 客户端接收缓冲区的状态不可直接监控
- 远程资源的不可控性: 客户端的接收缓冲区是位于远程系统中的资源,服务器无法直接监控和管理它的状态。
- TCP 协议的流量控制机制: TCP 协议有内置的流量控制机制,接收方会通过滑动窗口(sliding window)机制告知发送方接收缓冲区的可用大小。如果接收缓冲区满了,接收方会减少窗口大小或完全关闭窗口,通知发送方停止发送数据。这是 TCP 协议层面处理的,不需要应用层直接监控接收方的缓冲区状态。
EPOLLIN
事件同理。
1.1 触发条件
EPOLLIN
:当文件描述符上有数据可读时触发。通常在以下情况:- 有新的客户端连接到服务器(对于监听套接字)
- 套接字上有新数据可读
- 套接字接收到EOF,意味着连接被关闭或终止
EPOLLOUT
: 当文件描述符可以写数据时触发。通常:- 套接字缓冲区有可用的空间可写入数据
- 首次连接建立后,如果套接字的连接操作(
connect
)完成,触发EPOLLOUT事件 - 在非阻塞模式下,当先前的发送操作因缓冲区满而失败后,一旦缓冲区重新可用,会触发
1.2 EPOLLOUT的具体场景
-
初始连接完成
当你使用非阻塞模式调用
connect
函数来连接远程主机,如果connect
返回-1
并且errno
是EINPROGRESS
,这意味着连接正在进行中。此时,可以通过监视EPOLLOUT
事件来检测连接何时完成。当连接完成时,套接字会触发EPOLLOUT
事件。 -
写缓冲区可用
在非阻塞套接字上,如果你之前的
send
操作因为写缓冲区已满(EAGAIN
或EWOULDBLOCK
错误)而无法完成,你可以监视EPOLLOUT
事件。当缓冲区有空间可写时,EPOLLOUT
事件会被触发,这时你可以继续写数据。
1.3 客户端断开的情况
-
客户端主动断开连接
- 如果客户端主动断开连接,服务器端的套接字会触发
EPOLLIN
事件。当你在服务器端调用recv
函数时,会收到 0 字节,这表示对方已经关闭了连接。 EPOLLOUT
:通常不会因为客户端主动断开连接而直接触发。客户端断开连接后,服务器端的套接字上往往会出现可读事件(EPOLLIN
),提示你连接已经关闭。
- 如果客户端主动断开连接,服务器端的套接字会触发
-
客户端意外断开连接
- 服务器端仍然触发
EPOLLIN
事件。但是recv
函数返回-1,并且errno
不是EAGAIN
或EWOULDBLOCK
这表示发生了错误。此时则需要根据错误码处理连接错误。
- 服务器端仍然触发
-
服务器发送数据时发现连接关闭:
- 如果服务器尝试向已经断开的连接发送数据,这可能会导致
send
函数返回 -1,并设置errno
为EPIPE
或ECONNRESET
。这时,你可以通过处理错误来得知连接已经关闭,但这并不是通过 EPOLLOUT 事件检测到的。
- 如果服务器尝试向已经断开的连接发送数据,这可能会导致
-
注意事项
-
EPOLLOUT 触发频率:
EPOLLOUT 事件非常频繁地触发,因为只要有可用的写缓冲区空间,它就会触发。因此,通常在你需要发送数据时才添加对 EPOLLOUT 事件的监控,并在写入操作完成后尽快移除该监控,以避免处理过多的无效事件。
-
处理连接断开的方式:
对于服务器,检测客户端断开的最可靠方式是通过
EPOLLIN
事件,接收 0 字节的数据。不要依赖EPOLLOUT
来判断连接状态,因为它主要用于管理写操作的可用性。
-