C++ 套接字 Send() 线程安全

Posted

技术标签:

【中文标题】C++ 套接字 Send() 线程安全【英文标题】:C++ Sockets Send() Thread-Safety 【发布时间】:2011-03-15 04:57:50 【问题描述】:

我正在为最多 1000 个客户端编写套接字服务器,服务器是关于我的游戏的,我正在使用非阻塞套接字和大约 10 个线程同时从不同的套接字接收数据(第一个线程从 0-100 接收,第二个从101-200 等等..)

但是如果线程 1 想向所有 1000 个客户端发送数据,而线程 2 也想同时向所有 1000 个客户端发送数据,那安全吗?数据是否有可能在另一端(客户端)被弄乱?

如果是,我想唯一可能发生的问题是有时客户端会收到 2 个或 10 个数据包作为 1 个数据包,对吗?如果是的话,有什么解决办法吗:(

【问题讨论】:

同意ckv提供的答案,但我建议重新考虑这种方法。拥有 10 个线程以不同的时间间隔从不同的套接字读取有什么好处?在架构中让线程从套接字和异步处理组件中读取可能会更容易。 @Daniel 我猜你的意思是每个套接字都有一个线程。对吗? 当其中一个线程正在读取时,大多数线程将处于等待状态。使用select() 之类的东西可能是更好的选择,并且可以降低复杂性。事实上,它甚至可能更快。 是的,我对 10 个线程使用 select() 和 fd_set,但我不知道如何让新的 10 个线程使用 select() 进行发送.. 我将如何触发事件fd_set..? 【参考方案1】:

由于您使用不同的套接字发送数据,因此一定没有任何问题。相反,当这些不同的线程访问相同的数据时,您必须确保数据完整性。

【讨论】:

并非如此,每个线程在不同的套接字上接收数据,但它可以向所有套接字发送数据.. 对不起,我不明白你的评论。您能否更清楚,或者您可以编辑您的问题以使其更清晰。 线程可以发送到所有套接字,但可以从某些套接字接收【参考方案2】:

send() 在大多数实现中不是原子的,因此从多个线程发送到 1000 个不同的套接字可能会导致到达客户端的混合消息以及各种奇怪的情况。 (我什么都不知道,请参阅 Nicolai 和 Robert 的 cmets 下面我的其余评论仍然有效(就解决您的问题而言))

我要做的是使用线程进行发送,就像您使用线程进行接收一样。一个线程来管理发送到一个(或多个)套接字,以确保您不会同时从多个线程写入一个套接字。

还可以查看here 以获得更多讨论和更有趣的链接。 如果您使用的是 Windows,winsock programmers faq 是非常宝贵的资源,有关您的问题,请参阅 here。

【讨论】:

hmm 我正在考虑这个问题,如果我知道怎么做,我会这样做,我使用 fd_set 来接收 select(),但如果我使用 select(),我不知道如何触发 fd_set 中的事件用于写/发送 你确定吗? send(2) 是一个系统调用 - 它必须是原子的。线程安全是一个不同的问题,尽管每个 send(2) 实际消耗了多少字节。 @tenev,看看 libevent:monkey.org/~provos/libevent - 让编码协议状态机变得如此简单。 @jilles de wit:-1 对不起,你错了。发送是 100% 的原子操作。如果数据太大并且不适合输出缓冲区,则调用将阻塞。如果对 send 的调用被中断,则不发送任何数据并返回错误。调用发送总是完全成功,或者完全失败。 看看这篇优秀的论文:pl.atyp.us/content/tech/servers.html“高性能服务器架构”。【参考方案3】:

您使用的是 UDP 还是 TCP 套接字?

如果是 UDP,每次写入都应该封装在一个单独的数据包中,并且应该原封不动地传送到另一端。顺序可以交换(就像任何 UDP 数据包一样),但它们应该是完整的。

如果是 TCP,则传输层没有数据包的概念,并且一侧的任何 10 次写入都可能在一次读取中捆绑在另一侧。 TCP 写入也可能只接受缓冲区的一部分,因此即使 send() 函数是原子的,您的写入也不一定。在这种情况下,您需要同步它。

【讨论】:

TCP,同步...我必须设置非阻塞还是阻塞套接字?如果它们是非阻塞的,我想我将无法同步它们,因为我在 SEND() 之前使用 Select(),所以我正在使用 pthread_rwlock_wrlock(&players[socket].islocked); select SEND(players[socket].socket) finally unlocking(&players[socket].islocked) 之后是一个好的解决方案吗^^?在我看来是最快的一个 同步我的意思是你必须手动将字节流分成数据包。无论您如何发送,您的客户端都会收到一个字节流(异步/非阻塞无关紧要)。尝试在短时间内连续写入 5 个字节 10 次 - 另一端最有可能收到一个 50 字节的块。 TCP 是一个字节流。如果你想要数据包,你将不得不制作它们。【参考方案4】:

处理多个套接字的通常模式是使用select(2)poll(2) 或更好的kqueue(2)epoll(4)(取决于平台)作为套接字,为 I/O 事件提供专用线程轮询事件分发器。套接字通常以非阻塞模式处理。然后可能会有线程池对事件做出反应,并直接或通过较低级别的缓冲区/队列进行读取和写入。

各种技术都适用于此 - 从队列到事件订阅白板。在 I/O 级别上多路复用接受/读取/写入/EOF 以及在应用程序级别上进行事件仲裁会变得很棘手。像<b>libevent</b><b>boost::asio</b> 这样的几个库有助于构建较低级别的结构(ACE 库也在这个领域,但我不想向任何人推荐它)。您必须自己提出应用程序级协议和状态机(同样<b>boost::statechart</b> 可能会有所帮助)。

一些很好的链接可以更好地了解您所面临的问题(这可能是他们在 SO 上被提及的第 100 万次):

The C10K problem High-Performance Server Architecture

很抱歉没有提供具体的解决方案,但这是一个非常广泛的设计问题,大多数决策很大程度上取决于上下文(虽然很有趣)。希望这会有所帮助。

【讨论】:

以上是关于C++ 套接字 Send() 线程安全的主要内容,如果未能解决你的问题,请参考以下文章

DatagramSocket.send 线程安全吗?

异步写入套接字线程是不是安全?

Java套接字多线程安全吗?

c++ string线程安全吗

boost::asio::socket 线程安全

如何安全地终止线程? (使用指针)C++