【问题标题】:How to handle multiple clients from one UDP Socket?如何从一个 UDP Socket 处理多个客户端?
【发布时间】:2014-05-23 01:17:54
【问题描述】:

我现在正在处理一个我不知道正确/最佳解决方案的问题。

考虑以下示例:

假设你有一个 Socket,像这样:

SOCKET s = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);

在这个我将称为“ServerSocket”的套接字上,有许多来自许多不同 ip 和端口(客户端)的 udp 数据包传入。

由于在此套接字上的 recvfrom() 中创建多个线程阻塞似乎不是一个好主意,我想到(可能)一个专用线程,它只是阻塞 recvfrom() 将那些 ip+port+ msg 组合成某种“全局队列”(std::queue,由互斥锁保护)。

到目前为止,一切顺利。

我知道 IOCP,关于它的第一个问题是:将 IOCP 用于此类问题/在一个套接字上是否有意义?我遇到的问题是,即使UDP数据包(我们都知道协议本身不能保证)以正确的顺序进入套接字,也会存在线程排序的问题。 例如,如果我将 IOCP 与四个线程和四个未完成的重叠 wsarecvfrom() 一起使用,则包 1 2 3 4 可能会被线程调度程序重新排序,例如到 3 4 1 2。 如果一个人只使用一个未完成的 wsarecvfrom(),一切都会按预期工作,因为一次只有一个线程处理 wsarecvfrom(),将该消息放入客户端队列并发布下一个重叠的 wsarecvfrom()。

此外,我想在阻塞模式下模拟 recvmsg() 和 sendmsg() 等函数,但这里的问题是,例如如果您有数千个客户端,则无法打开 1000 个线程,这些线程都有其专用的 recvmsg() 阻塞,例如客户端消息队列的条件变量。 这也是一个问题,因为客户端可能会被删除,通过接收可能包含类似“CLOSE_CONNECTION”的包来模拟 TCP 使用它的 closesocket()。

我需要使用UDP,因为用户发送的数据是时间关键的,但它不一定是可靠的;只有状态消息应该尽可能可靠,例如“CONNECT_REQUEST”,如果客户端“连接”(就像 tcp 会这样做,我们都知道,udp 不会这样做,所以如果需要,我们必须自己编写)。 也需要按顺序接收客户端消息。

总而言之,应给出以下标准: - 需要客户端消息部分的有序消息 - 客户端消息的可靠性不是必需的(仅对于状态包,如“ACK_PACKAGE”等......我们正在谈论最新消息>比接收消息的可靠性更重要) - 必须管理许多客户端,并且必须检测断开连接(软/硬,例如客户端插入网线或其他东西......)之类的事情(定时器线程池?)

所以我的最后一个问题是:实现这样的目标的最佳方法是什么?使用 TCP 会更容易,因为一个 IOCP 线程可以侦听一个接受()处理的 TCP 套接字,因此不会出现线程重新排序问题。使用一个 UDP 套接字,你不能那样做,所以也许必须有类似重叠请求之类的东西,但只是为了一个......好吧,“自定义”事件。

【问题讨论】:

  • 你一开始就丢弃的多线程解决方案没有任何问题。这不是唯一的方法,但它是一种非常简单的方法。
  • void thread_one() { recvfrom(serverSocket....); } void thread_two() { recvfrom(serverSocket....); } 这样吗??
  • 当然可以。每条消息只有一个线程接收,您也可以通过同一个套接字从多个线程发送数据报。 send() 和 recv() 调用是系统调用,因此是原子调用。

标签: c++ multithreading sockets udp iocp


【解决方案1】:

您是正确的,因为使用多个线程来服务 IOCP 的基于 IOCP 的服务器可以并且将需要显式排序,以确保以正确的顺序处理来自多个并发读取的结果。这同样适用于 TCP 连接(请参阅here)。

我通常用 TCP 处理这个问题的方法是有一个每个连接计数器,它是作为元数据添加到用于该连接上的接收的每个缓冲区的值。然后,您只需确保按顺序处理缓冲区,因为发出的读取序列是 IOCP 的读取完成序列(只是从 IOCP 读取的多个线程的调度导致了问题)。

如果您有一个所有对等方都发送到的“众所周知的端口”,因为您的序列号没有要关联的“连接”,则您不能对 UDP 采用这种方法。

此外,UDP 的另一个复杂之处在于,您和您的对等方之间的路由器可能会设法在数据报到达您之前重新排序或复制任何数据报。这不太可能,但如果你不考虑它,那么当你向重要的人展示它时,这肯定是首先发生的事情......

这导致了这样一个事实,即要对 UDP 进行排序,您需要在数据报的数据部分中包含一个序列号。然后您会遇到 UDP 数据报可能丢失的问题,因此序列号对于确保按顺序处理所有入站数据的用处不大,而仅在确保您永远不会处理任何乱序的数据报时有用。也就是说,如果你的数据报中有一个序列号,你所能做的就是确保你永远不会处理来自该对等方的数据报,其序列号小于或等于你上次处理的序列号(实际上你需要丢弃潜在有效数据)。

这实际上与单线程系统和一个对等点的问题相同,尽管在重要的演示之前,当你得到一个恰好导致的网络配置时,你可能会在没有这么严格的情况下侥幸逃脱在重复的数据报或乱序的数据报中(都非常合法)。

要提高系统的可靠性,您需要在 UDP 之上构建更多协议。也许看看this question 及其答案。然后注意不要构建比 TCP 更慢、不太好、对其他网络用户不太礼貌的东西……

【讨论】:

  • 好的,所以诀窍似乎类似于......“你可以使用 IOCP,但你应该在”sequenceNumber++ 周围放置一个关键部分; wsarecvfrom(...);"。使用这种方式保证了我们有多个wsarecvfrom的优秀,这对性能来说是好的。它进一步保证了下一个缓冲区将被按顺序处理。对吗?
  • 是的,这可能会有所帮助,这将确保 UDP 流按顺序为所有连接的对等方处理。它将允许您拥有多个 IOCP 线程,然后您需要某种方式来同步和排序来自特定对等方的数据报。使用 GetQueuedCompletionStatusEx() 一次使用单个线程将多个完成出列可能也值得......由于跨对等同步,这对于整体性能来说并不理想。最好在数据报本身中嵌入一个序列号,然后像我上面解释的那样简单地丢弃“陈旧”数据。
  • 所以最好的解决方案可能是数据报中的序列号?映射或无序映射如何按每个客户端的序列号插入包?您如何处理在多个线程之间共享的客户端列表?在这种情况下你会使用什么?
  • 适用于 TCP,您知道您将获得所有数据报。但是对于 UDP,当您已经拥有 N-1 数据报时,您要等待多长时间才能看到数据报 N-1 是否到达?
  • 我明白你的意思。由于在许多情况下 udp 的不可靠性质,可能会发生类似情况:DGRAM 1234 Arrives,DGRAM 1234 Arrives(再次,因为可能会发生 dgram 的重复) DGRAM 1236(因为包可能会丢失)。如果我在这里没有完全错,如果我不关心包的可靠性,只关心包到达时缓冲区的正确顺序,那么必须有某种同步,其中 ip + 端口组合的第一个包到达必须放入该客户端的第一个缓冲区......等等?
猜你喜欢
  • 1970-01-01
  • 2012-04-20
  • 1970-01-01
  • 2013-11-17
  • 1970-01-01
  • 2013-04-21
  • 2021-04-09
  • 1970-01-01
  • 2013-10-18
相关资源
最近更新 更多