阻塞和非阻塞套接字有什么区别?(真实版)

3

在每个人都将此标记为重复之前,请允许我声明,我了解我的网络编程知识,并且这个问题是我尝试解决的,即使我已经找到了“解决方案”。

设置

过去几周中,我花费大量时间编写代码来将一个大型工业系统整合到我们当前的设置中。该系统由控制Windows XP计算机(PC A)控制,通过向Ubuntu 14.04系统(PC B)发送2000 Hz的UDP数据包进行控制。它会响应包含系统当前状态的UDP数据包。

要确保保持每秒2000次的速率,需要仔细处理,因为超过3ms后就会出现故障并返回到安全状态。这涉及测量和考虑std::this_thread::sleep_for 的不准确性。测量结果显示,与目标速率相比仅有0.1%偏差。

观察结果

当我开始从系统接收状态响应时,问题就开始出现了。在PC B上的控制端看起来大致如下:

forever at 2000Hz {
  send current command;
  if ( socket.available() >= 0 ) {
    receive response;
  }
}

编辑 2:或者在实际代码中:

auto cmd_buf = ...
auto rsp_buf = ...

while (true) {
   // prepare and send command buffer
   cmd_buf = ...
   socket.send(cmd_buf, endpoint);

   if (socket.available() >= 0) {
      socket.receive(rsp_buf);
      // the results are then parsed and stored, nothing fancy
   }

   // time keeping
}

问题在于,只要代码的接收部分存在于PC B上,PC A在尝试分配接收缓冲区时就会在几秒钟内耗尽内存。此外,它还会引发错误,指出超时已经错过,这可能是由于数据包未到达控制软件引起的。
仅为突出奇怪之处:在这种情况下,PC A是发送UDP数据包的电脑。 针对EJP的编辑: 这是(现在)工作的设置。 它最初是这样的:
forever at 2000Hz {
  send current command;
  receive response;
}

但是当收到响应(阻塞)时,已经错过了截止日期。因此需要进行可用性检查。

尝试的另一件事是在单独的线程中接收:

// thread A
forever at 2000Hz {
  send current command;
}

// thread B 
forever {
  receive response;
}

这个版本的行为与第一个版本相同。

解决方案

解决方案是将PC B上的套接字设置为非阻塞模式。只需要一行代码,所有问题就都解决了。

即使在阻塞模式下,我很确定也可以满足截止时间。当只涉及一个套接字时,阻塞模式和非阻塞模式之间应该没有性能差异。 即使在阻塞模式下,检查套接字是否有可用数据需要比在非阻塞模式下多花费几微秒,但当整体截止时间得到精确满足时,这并不会造成影响。

现在...这是怎么回事?


1
你在这里走错了方向。检查阻塞模式套接字的 available() > 0 是完全浪费时间的,你应该删除那个测试并将其保留在阻塞模式下。接下来的 read()recv() 将一直阻塞,直到有数据可用。如果需要读取超时,请设置读取超时。接收传入数据的缓冲区应该在循环之前已经分配好。 - user207421
我认为我们需要看到一个MCVE。但是2000Hz对于非实时操作系统来说听起来非常高。 - Harry Johnston
你能澄清一下你所说的“在这种情况下,PC A是发送UDP数据包的计算机”是什么意思吗?你展示给我们的PC B的代码似乎在发送UDP数据包,并且在此之前你的描述也说B在发送数据包。 - Harry Johnston
@HarryJohnston 我可以仅使用网络代码进行复制,因为我可以访问PC A上的专有系统。两台计算机都在发送数据,PC B正在发送位置命令,PC A正在响应当前位置。尝试在PC B上接收它们会导致PC A中的内存分配错误。我不确定是否允许在我们的网络上运行数据包嗅探器,我会打听一下。 - fho
哦,我明白了 - 在PC A上有别人的代码,所以你甚至不能发布它的一部分。原则上,你应该能够编写一些自己的代码来演示相同的行为,但在这种情况下,我可以理解为什么你不想做那么多工作!如果你不被允许在主网络上运行数据包嗅探器,你可能需要考虑是否有可能将这两台电脑暂时放到一个隔离的网络中进行嗅探 - 可能只需使用交叉线缆直接连接两台电脑,并在PC B上运行数据包嗅探器即可。 - Harry Johnston
显示剩余6条评论
1个回答

0
如果我正确地阅读了您的代码并参考了这段代码:
forever at 2000Hz {
  send current command;
  receive response;
}

研究阻塞和非阻塞套接字之间的区别。使用阻塞套接字,您发送当前命令,然后被卡在等待响应的状态中。到那时,我猜您已经错过了2kHz的目标。

现在,在非阻塞套接字中,您发送当前命令,尝试接收接收缓冲区中的任何内容,但如果没有内容,您立即返回并继续紧密的2kHz发送循环。这就解释了为什么您的工业控制系统在非阻塞代码中工作正常。


这就是第一个代码片段中可用性检查的作用。 - fho

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