【问题标题】:Why does sending consecutive UDP messages cause messages to arrive late?为什么发送连续的 UDP 消息会导致消息延迟到达?
【发布时间】:2018-07-04 04:15:31
【问题描述】:

我在 Windows 7 中编写了一个服务器 python 脚本,用于将以太网 UDP 数据包发送到运行 C 客户端接收程序的 UNIX 系统,该程序将消息发送回服务器。但是,有时(并非总是)python 发送到的最后一个端口(并且始终是最后一个端口)中的消息要等到下一批 4 条消息被发送后才会到达。这会导致接收到最后一个端口的消息的时间与发送时不正确,并且我不能在同一个端口上背靠背发送两条消息

我已经能够在Wireshark 中验证这一点,方法是查找大约同时到达的两条消息,因为未收到的一条已与另一条一起处理。我还在recv() 函数之后检查了时间,它显示了一个很长的延迟,然后是一个很短的延迟,因为它基本上收到了两个数据包。

我已尝试解决此问题,但有助于我解释问题或如何解决问题:我可以在每个 sendto() 之间添加延迟,我将成功发送和以正确的时间接收所有消息,但我希望测试按照我在下面编写的方式进行;我增加了接收线程的优先级,认为我的以太网接收没有收到信号来接收包或者某些过程花费了太长时间,但这没有用,20ms 应该比处理所需的时间多得多数据;我删除了端口 C 和 D,然后端口 B 丢失消息(只有一个端口不会引起问题),我认为减少端口数量会改善时序;在 PORTD 之后立即发送到虚拟 PORTE 可以让我以正确的时间接收所有消息(我假设问题已转移到 PORTE);我还在 UNIX 环境和 C 代码中重现了 python 脚本,并且遇到了同样的问题,指出了接收问题;我还将我的 recv 函数设置为每 1ms 超时一次,希望它能够以某种方式恢复,即使时间会有点偏离,但我仍然看到背靠背的消息。我还检查了是否没有丢弃任何 UDP 数据包,并且缓冲区是否足够大以容纳这 4 条消息。任何新想法都会有所帮助。

这是代码的核心,python脚本会发送4个数据包。一条 20 字节的消息发送到 C 中相应的等待线程并延迟 20ms

python 代码的表示形式类似于

msg_cnt = 5000 
while cnt < msg_cnt:
   UDPsocket.sendto(data, (IP, PORTA))
   UDPsocket.sendto(data, (IP, PORTB))
   UDPsocket.sendto(data, (IP, PORTC))
   UDPsocket.sendto(data, (IP, PORTD))

   time.sleep(.02)
   cnt++

C 代码有 4 个线程在其相应端口上等待接收。本质上,每个线程都应该接收其数据包,对其进行处理,然后将其发送回服务器。在下一组消息到达之前,这个过程应该不到 20 毫秒

void * receiveEthernetThread(){
     uint8_t ethRxBuff[1024];
     if((byteCnt = recv(socketForPort, ethRxBuff, 1024, 0)) < 0){
         perror("recv")
     }else{
        //Process Data, cannot have back to back messages on the same port
        //Send back to the server
     }
}

【问题讨论】:

  • 所以如果我没看错的话,你在wireshark上看到的是来自Python的消息总是间隔适当的。这是真的吗?
  • 就发送而言,是的。 Wireshark 将显示 4 条消息突发之间的纳秒延迟,然后完美显示 20ms 延迟。但是,在接收过程中,有时来自最后一个端口的消息似乎是背靠背的。在 recv() 之后立即测试时间表明,假设我发送一组四个,然后发送另一个。如果第一组错过了最后一个端口,我会看到一个很大的延迟(大约 40 毫秒显示下一条消息),因为它最后一次进入该线程,然后是一个快速延迟,这意味着它基本上进入了两次线程。
  • UDP 不保证数据包的顺序,甚至不保证所有数据包都已交付。如果您需要这些属性,您需要自己构建它们,使用在 UDP 之上提供它们的库(例如,QUIC),或者使用不同的协议(例如,TCP)。至于时间问题,可能会有一些缓冲(参见en.wikipedia.org/wiki/Bufferbloat)。
  • 谢谢!我将研究缓冲膨胀,似乎它可能是问题的一部分,听起来与我对该问题的初步结论相似。速度和时间肯定有关系。不幸的是,我被 UDP 困住了。我也尝试了长达 1 秒的延迟,但仍然遇到同样的问题。
  • 你能检查下处理器亲和性对接收端的影响吗?这将是 taskset 命令,基本上将服务器进程固定到特定的 cpu/core。以下是可能相关的手册页的摘录:出于性能原因,调度程序尝试将进程保持在同一个 CPU 上,只要可行。因此,强制使用特定的 CPU 亲和性仅在某些应用程序中有用。

标签: python c linux multithreading sockets


【解决方案1】:

我不久前发现了我丢失消息的原因并想回答我的问题。我在 Zynq-7000 上运行该程序,并没有意识到这会是一个问题。

在 Xilinx Zynq-7000-TRM 中,有一个已知问题描述:

" 最后一帧可能会卡在 RX FIFO 中,而软件无法从那里取出最后一帧。GEM 仅在接收到另一帧时才发起描述符请求. 因此,当一个帧在 FIFO 中并且一个描述符在以后才可用并且没有新帧到达时,没有办法取出该帧,甚至不知道它存在。

在典型操作条件下不会出现此问题。典型的操作条件是系统总是有传入的以太网帧。当 MAC 停止接收以太网帧时,会出现上述问题。

解决方法:软件中没有解决方法,除了确保以太网帧的连续流动。”

基本上是通过持续不断的以太网流量来修复的,很抱歉错过了关键信息。

【讨论】:

    【解决方案2】:

    这会导致最后一个端口收到消息的时间 发送时间不正确,我无法回复两条消息 回到同一个端口。

    简短的解释是您使用的是 UDP,并且该协议不保证交付或订单。

    除此之外,您所描述的最绝对听起来像是一个缓冲问题。不幸的是,没有真正的方法来“刷新”套接字。

    您将需要使用能够保证所需内容的协议 (TCP),或者在 UDP 之上实现您的需求。

    我相信你真正的问题是如何在服务器端解析数据。如果您的应用程序完全依赖于来自网络的四个独立数据包的 20 毫秒间隔,那只是自找麻烦。如果可能的话,我会解决这个问题,而不是尝试修复(正常)套接字缓冲问题。

    不过,一个 hacky 解决方案,因为我喜欢 hacky 的东西:

    在服务器上设置第五个套接字。发送四个时间敏感数据包后,向第五个端口发送“足够”的数据包以强制任何剩余的时间敏感数据包通过。什么是“足够”的数据包取决于您。您可以发送一个静态号码,假设它可以工作,或者让第五个端口在它开始接收时向您发送一条消息。

    【讨论】:

    • 仅仅因为没有保证,并不意味着数据包被无缘无故丢弃。是的,在互联网上,您可能不知道原因。但在快速 LAN 上,它应该只是由拥塞引起的。
    • 我不是在谈论丢包或拥塞。当我说交付时,我想到的是 TCP 有 TCP_NODELAY 标志,它可以帮助将数据包推过缓冲区(但仍然不能保证这一点)。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2013-07-01
    • 1970-01-01
    • 2016-09-28
    • 1970-01-01
    • 2014-02-15
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多