【发布时间】:2020-12-08 19:01:05
【问题描述】:
在使用 poll(2) 或 epoll(2) 等 I/O 多路复用 API 时,我应该使用非阻塞还是阻塞 TCP 套接字?
有些人建议在这里使用非阻塞套接字,但如果有数据要读取,I/O 多路复用 API 会通知您,那么阻塞套接字有什么问题?
【问题讨论】:
标签: c sockets networking nonblocking multiplexing
在使用 poll(2) 或 epoll(2) 等 I/O 多路复用 API 时,我应该使用非阻塞还是阻塞 TCP 套接字?
有些人建议在这里使用非阻塞套接字,但如果有数据要读取,I/O 多路复用 API 会通知您,那么阻塞套接字有什么问题?
【问题讨论】:
标签: c sockets networking nonblocking multiplexing
当您更喜欢错误响应(EWOULDBLOCK/EAGAIN)而不是线程等待(阻塞)直到可以进行 IO 操作时,使用非阻塞 IO。
这就引出了IO复用是如何实现的?
的问题如果您使用 thread-per-connection 模型(或 process-per-connection),则使用阻塞 IO 可能更舒适。
但是,如果同一个线程正在为多个 IO 对象提供服务,阻塞 IO 将是危险并可能导致整个应用程序停止.
单线程服务多个IO对象时最好使用非阻塞IO。
请注意,在轮询(使用select / poll 或epoll/kqueue)时,该问题一开始可能并不明显。
由于 IO 操作仅由已经“知道”IO 操作不会阻塞的代码路径执行(它被轮询并且已知是可用操作)。
这掩盖了代码中某处可能直接调用 IO 操作而无需先轮询的问题,从而导致阻塞 IO 调用,从而使应用程序停止运行。
【讨论】:
recv() 可以阻塞,即使 select() 之前指示套接字已准备好读取。请参阅select() 的 Linux 手册页“BUGS”部分的第四段:man7.org/linux/man-pages/man2/select.2.html
如果您的 TCP 服务器是单线程的并且使用阻塞 I/O,那么任何连接到它的客户端都可能通过仅发送部分消息来拒绝所有其他客户端的服务,或者或者在服务器发送数据后拒绝从其 TCP 套接字读取任何数据。在前一种情况下,服务器可能会阻塞很长时间(也许永远)等待从客户端接收到整个消息;在此期间,服务器将无法响应其他客户端。在后一种情况下,服务器将阻塞很长时间(可能永远)等待客户端读取一些 TCP 数据,以便服务器套接字的发送缓冲区可以被耗尽,以容纳更多的传出数据到该客户端。
避免该问题的一种方法是将服务器的所有套接字设置为非阻塞 I/O 模式;这样,服务器就知道它永远不会“卡在”recv() 或 send() 调用中,因此无论任何特定客户端是否表现良好,都可以保持对所有客户端的响应。在非阻塞设计中,服务器阻塞的唯一位置是select() 或poll() 或类似的内部,因为这些调用旨在在任何客户端需要服务时返回,而不是仅阻塞单个客户端。 (权衡是,使用非阻塞 I/O,您的服务器的缓冲/排队逻辑需要更精细一些,因为您不能再假设在任何给定的发送或接收期间将发送或接收任何特定的固定字节数接收操作)
另一种避免该问题的方法是制作多线程服务器;这样做的好处是每个客户端都有自己的线程,因此行为不端的客户端只会阻塞自己的线程,而不是为其他客户端服务的线程。缺点是现在您的服务器是多线程的,并且存在多线程引入的所有额外缺陷。
(并且,为了完整起见,第三种方法只是忽略行为不良/连接不良的客户端的可能性,并使用单线程/阻塞模型。这对于期望客户端的玩具示例非常有效非敌对,并且他们连接的网络是可靠的,但在现实生活中效果不佳)
【讨论】: