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

5
我正在编写一个支持最多1000个客户端的套接字服务器,这个服务器是为了我的游戏而设计的。我使用非阻塞套接字和大约10个线程同时从不同的套接字接收数据(第一个线程从0-100接收,第二个从101-200接收,以此类推...)。
但是如果第一个线程想要向所有1000个客户端发送数据,而第二个线程也想在同一时间向所有1000个客户端发送数据,这样安全吗?有没有可能导致数据在客户端那边出现混乱?
如果是,我猜唯一可能发生的问题就是有时客户端会将2或10个数据包看作一个数据包,对吗?如果是的话,是否有任何解决方案呢 :(

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

2
通常处理多个套接字的模式是使用专门的线程通过 select(2)poll(2) 或更好的kqueue(2)epoll(4)(取决于平台)作为套接字事件分发器来轮询 I/O 事件。通常以非阻塞模式处理套接字,然后可能有一组线程响应事件并直接进行读写或通过更低级别的缓冲区/队列进行读写。
所有这些技术都可适用 - 从队列到事件订阅白板。在 I/O 级别上,对 I/O、接受、读取、写入和 EOF 进行多路复用会变得棘手,在应用程序级别上进行事件仲裁也很棘手。几个库,例如libeventboost::asio帮助结构化低层(ACE 库也在此空间中,但我不会向任何人推荐它)。您必须自己设计应用程序级协议和状态机(再次使用boost::statechart可能会有所帮助)。
以下是一些好的链接,以更好地了解您要面对的问题(这可能是在这里提到这些的第一百万次): 很抱歉没有提供具体的解决方案,但这是一个非常广泛的设计问题,大多数决策严重依赖于上下文(尽管非常有趣)。希望这可以有所帮助。

1

由于您正在使用不同的套接字发送数据,因此不应该出现任何问题。相反,当这些不同的线程访问相同的数据时,您必须确保数据的完整性。


不完全是这样,每个线程在不同的套接字上接收数据,但它可以向所有套接字发送数据。 - Tenev
1
抱歉,我不理解你的评论。你能否更清楚地表达或编辑你的问题以使其更加明确? - ckv
线程可以向所有套接字发送,但只能从某些套接字接收。 - Tenev

0

在大多数实现中,send()不是原子的,因此从多个线程向1000个不同的套接字发送可能会导致混乱的消息到达客户端,并出现各种奇怪的问题。 (我什么也不知道,请参见Nicolai和Robert在我的评论下面的评论,但我的评论仍然适用于解决您的问题)

我会像使用线程接收一样使用线程发送。一个线程管理向一个(或多个)套接字发送,确保您不会同时从多个线程写入一个套接字。

此外,这里还有一些额外的讨论和更有趣的链接。 如果您在Windows上,winsock程序员常见问题解答是一个宝贵的资源,有关您的问题请参见这里


嗯,我正在考虑这个问题,如果我知道如何做,我会这样做。我正在使用fd_set来接收select()的数据,但是如果我使用select()进行写入/发送,我不知道如何在fd_set中触发事件。 - Tenev
1
你确定吗?send(2)是一个系统调用 - 它必须是原子的。然而,关于每个send(2)实际消耗多少字节的线程安全性则是另一个问题。 - Nikolai Fetissov
@tenev,请看一下libevent:http://monkey.org/~provos/libevent/ - 让编写协议状态机变得更容易。 - Nikolai Fetissov
1
@jilles de wit:-1 对不起,您是错误的。 Send是100%原子操作。 如果数据太大而无法适应输出缓冲区,则调用将被阻塞。 如果对send的调用被中断,则不发送任何数据并返回错误。 发送调用始终完全成功或完全失败。 - Robert S. Barnes
1
请看这篇优秀的论文:http://pl.atyp.us/content/tech/servers.html "高性能服务器架构"。 - Nikolai Fetissov
显示剩余4条评论

0
你是使用UDP还是TCP套接字?
如果使用UDP,则每个写操作应该封装在单独的数据包中,并完整地传输到另一端。顺序可能会被交换(就像任何UDP数据包一样),但它们应该是完整的。
如果使用TCP,则在传输层上没有数据包的概念,一个端点上的任何10个写操作都可能在另一端被捆绑成一个读操作。TCP写操作也可能只接受缓冲区的一部分,因此即使send()函数是原子的,你的写操作也不一定是原子的。在这种情况下,你需要进行同步。

TCP,同步……我必须设置非阻塞或阻塞套接字吗?如果它们是非阻塞的,我认为我将无法同步它们,因为在SEND()之前我使用Select(),我在select SEND(players[socket].socket)之后使用pthread_rwlock_wrlock(&players[socket].islocked);最后解锁(&players[socket].islocked),这是一个好的解决方案吗^^?在我看来,这似乎是最快的解决方案。 - Tenev
同步化指的是您必须手动将字节流分成数据包。无论您以何种方式发送它(异步/非阻塞都无所谓),您的客户端都会接收到字节流。尝试短时间内连续写入5个字节10次 - 另一侧很可能会接收到一个50字节的块。TCP是一个字节流。如果您想要数据包,您需要自己制作它们。 - dascandy

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