你可能担心的是已建立连接上的read
/recv
和write
/send
线程,如果你愿意让这些线程等待完成,它们不需要是非阻塞的。这通常是你使用线程而不是select
、epoll
、异步操作或io_uring的原因之一 - 这也使代码更简单。
accept()
时阻塞,那么你也不需要担心。
然而,对于TCP服务器,你可能需要注意一个微妙的问题......如果你的程序增长到处理多个客户端并且有一些定期的维护工作要做。自然而然地,你会想使用带有超时的select
或epoll
调用来检查监听套接字的可读性 - 这表示客户端连接尝试 - 然后accept
连接。这里存在竞争条件:客户端连接尝试可能在select()
和accept()
之间断开,此时如果监听套接字不是non-blocking,accept()
将会阻塞,这可能会阻止及时返回到select()
循环并停止周期性的超时处理,直到另一个客户端连接。
是否有任何开源库可以在上述情况下提供帮助?
有数百种编写基本服务器的库(询问第三方库建议不属于SO的讨论范畴,因此我不会深入介绍),但最终您所要求的可以轻松地在操作系统提供的BSD sockets API或Windows的翻版("winsock")上实现。
套接字是双向的。如果你曾经解剖过以太网或串行电缆,或者看过它们的低级硬件接线图,你实际上可以看到“TX”(发送)和“RX”(接收)线路上的不同铜线。从设备控制器到大多数操作系统API的信号发送软件,在一个“套接字”上反映了这一点,并且这是套接字和大多数系统上的普通管道之间的关键区别(例如Linux)。
为了充分利用套接字,您需要:
1)异步IO支持,使用IO完成端口、epoll()或某些类似的异步回调或事件系统来“唤醒”,每当数据在套接字上进入时。然后必须调用最底层的“ReadData”API来读取套接字连接中的消息。
2)第二个支持低级写入的API,“WriteData”(传输),将字节推送到套接字上,并且不依赖于“ReadData”逻辑所需的任何内容。记住,即使在硬件级别上,您的发送和接收也是独立的,因此不要在此级别引入锁定或其他同步。
3)套接字IO线程池,盲目地执行从套接字读取或将写入套接字的任何数据处理。
4)协议回调:套接字线程具有智能指针的回调对象。它处理位于基本套接字连接顶部的任何协议层,例如将数据块解析为实际的HTTP请求。记住,套接字只是计算机之间的数据管道,发送到其上的数据通常会作为一系列片段-分组-到达。在UDP等协议中,分组甚至不按顺序排序。低级“ReadData”和“WriteData”将从其线程向此处回调,因为它是内容感知数据处理实际开始的地方。
5)协议处理程序本身所需的任何回调。对于HTTP,您可以将原始请求缓冲区打包成漂亮的对象,然后移交给真正的servlet,后者应该返回一个漂亮的响应对象,可被序列化为符合HTTP规范的响应。
注意基本的模式:如果您希望充分利用双向异步IO套接字,您必须从根本上使整个系统成为异步的(即“回调洋葱”)。唯一同时读写套接字的方法是使用线程,因此您仍然可以在“写入器”和“读取器”线程之间进行同步,但是如果协议或其他考虑因素迫使我这样做,我只会这样做。好消息是,您可以使用高度异步处理来实现套接字的出色性能,坏消息是,以强大的方式构建这样的系统是一个严肃的努力。
你不必担心。一个线程读取和一个线程写入将按照你的期望工作。套接字是全双工的,因此您可以在写入时读取和反之亦然。如果有多个写入者,那么您就必须担心了,但这不是这种情况。