在C或C++中同时在同一套接字上进行读写

49
我正在实现一个简单的服务器,它接受一个连接,然后使用该套接字同时从读取和写入线程中读取和写入消息。 在Linux的C/C++中,同时从同一套接字描述符中读取和写入的安全且简单的方法是什么? 由于只有一个专用的读线程和一个专用的写线程将写入套接字,因此我不需要担心多个线程从同一套接字读取和写入。 在上述情况下,是否需要任何类型的锁定? 上述情况是否需要非阻塞套接字? 是否有任何开源库可以帮助处理上述情况?
3个回答

42
在上述情况下,是否需要任何类型的锁定?
不需要。
上述场景是否需要非阻塞套接字?
需要。

你可能担心的是已建立连接上的read/recvwrite/send线程,如果你愿意让这些线程等待完成,它们不需要是非阻塞的。这通常是你使用线程而不是selectepoll异步操作io_uring的原因之一 - 这也使代码更简单。

如果线程接受新客户端并愿意在调用accept()时阻塞,那么你也不需要担心。

然而,对于TCP服务器,你可能需要注意一个微妙的问题......如果你的程序增长到处理多个客户端并且有一些定期的维护工作要做。自然而然地,你会想使用带有超时的selectepoll调用来检查监听套接字的可读性 - 这表示客户端连接尝试 - 然后accept连接。这里存在竞争条件:客户端连接尝试可能在select()accept()之间断开,此时如果监听套接字不是non-blockingaccept()将会阻塞,这可能会阻止及时返回到select()循环并停止周期性的超时处理,直到另一个客户端连接。

是否有任何开源库可以在上述情况下提供帮助?

有数百种编写基本服务器的库(询问第三方库建议不属于SO的讨论范畴,因此我不会深入介绍),但最终您所要求的可以轻松地在操作系统提供的BSD sockets API或Windows的翻版("winsock")上实现。


7
+1 是指提到竞态条件和设计选项,使用线程与选择或轮询。 - Jimm
1
@Tony D 好建议。从增强的角度来看,如果他将来计划使用OpenSSL,则他的架构可能会发生变化。他不能同时在同一个SSL*上进行读写操作。 - enthusiasticgeek
@enthusiasticgeek:有趣 - 我还没有做过SSL编程,所以不知道那个,但绝对值得记住。干杯。 - Tony Delroy
那里存在竞态条件:在select()和accept()之间,客户端连接尝试可能已经中断,即使监听套接字不是非阻塞的,accept()也会被阻塞,这可能会阻止及时返回到select()循环并停止定期超时处理,直到另一个客户端连接。 - avernus
@AydinÖzcan:不行——那个建议有问题。 “即使”暗示着问题存在于阻塞和非阻塞的监听套接字中(但其中一个可能更令人惊讶)。这是不正确的——问题只存在于阻塞套接字中。 - Tony Delroy
1
@TonyDelroy 我刚意识到我误读了你的原始回答,感谢你的帮助。 - avernus

27

套接字是双向的。如果你曾经解剖过以太网或串行电缆,或者看过它们的低级硬件接线图,你实际上可以看到“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套接字,您必须从根本上使整个系统成为异步的(即“回调洋葱”)。唯一同时读写套接字的方法是使用线程,因此您仍然可以在“写入器”和“读取器”线程之间进行同步,但是如果协议或其他考虑因素迫使我这样做,我只会这样做。好消息是,您可以使用高度异步处理来实现套接字的出色性能,坏消息是,以强大的方式构建这样的系统是一个严肃的努力。


7
“双向传输”并不足够。半双工也是双向的,但不能同时进行。为了满足OP的要求,需要全双工。TCP/IP既是全双工也是双向的。 - user207421
感谢您特别提到了分离的物理线路。这帮助我理解了为什么它是双向和全双工的概念。 - Captain Man

26

你不必担心。一个线程读取和一个线程写入将按照你的期望工作。套接字是全双工的,因此您可以在写入时读取和反之亦然。如果有多个写入者,那么您就必须担心了,但这不是这种情况。


简短回答,我喜欢它。你所说的“担心多个写入者”是指在同一通道上并发写入会弄乱事情,对吗?因此,在多个写入线程中,必须应用同步方法。 - LppEdd

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接