2

我遇到一些无法解释的行为,该应用程序使用 sendto() 将 UDP 数据写入多个端口(全部使用socket(PF_INET, SOCK_DGRAM, 0)打开),以利于一组客户端读取进程。这些 sendto()s 偶尔且不可预测地触发ECONNREFUSED错误。这发生在 macOS Sierra (10.12) 系统上,其 sendto(2) 手册页甚至没有将ECONNREFUSED列为可能的错误。有趣的是,我有一个 CentOS7 系统(这些错误永远不会发生),它的 sendto(2) 手册页引用了 udp(7) 手册页上记录的其他 sendto() 错误,而 CentOS7 udp(7) 页面说:

被拒绝

 No receiver was associated with the destination address. This
 might be caused by a previous packet sent over the socket.

( macOS Sierra udp(4) 页面上的任何地方都没有提到ECONNREFUSED。)我不知道 CentOS7 手册页是否与 macOS 有任何相关性,但暂时假设它们确实如此,上述关于 sendto的ECONNREFUSED解释() 在以下几点上令人困惑:

首先,我听说过的关于 UDP 的一切都强调它的无连接性。那么,为什么 sendto() 会失败,因为没有连接接收器(或“关联”,正如手册页所说,我认为这是同一件事)?UDP 的重点不就是如果你是一个说话者,你只是喋喋不休,不在乎其他人是否在听吗?这些 CentOS7 udp(7) 注释似乎确实适用于我的 Sierra 系统,但是,因为当我运行绑定到这些端口并从这些端口读取的客户端进程时,我永远不会遇到问题,但是如果我在读者之前启动 UDP 编写器运行我会经常(但不总是)看到这些错误。

其次,任何人都可以向我解释为什么根据 CentOS7 udp(7) 文档,先前通过套接字发送的数据包可能导致没有接收器与目标地址相关联?这对我来说毫无意义。某些数据报是否有毒,以至于它们会杀死阅读它们的人?

我还应该注意到,除了在 CentOS7 上实际(如果模糊地)记录了这个问题之外,我也从未在 Sierra 之前的任何 MacOS 版本上遇到过这个问题,而且这段代码对我来说已经运行了很多年. 我仍然有一个 El Capitan 系统,并且无法复制那里的错误。

以下是关于我的应用程序的更多信息——请随时评论上述关于 PF_INET UDP、sendto() 和ECONNREFUSED的一般性问题,或者如下所述我的应用程序的更具体细节。我已经有一个可用的解决方法(见下文),但想更好地了解发生了什么。

我的应用程序正在从各种来源(串行线路和/或 UDP 端口)读取数据,将其按摩成各种类型的重新格式化输出消息,然后将这些消息写入多个预定义的连续编号(例如,3000 到 3004)UDP 端口少数可变数量的客户端(限制为 5 个,但通常不超过 3 或 4 个)读取相同的 IP 地址。每个客户端扫描我的应用程序的 UDP 输出端口的预定义列表,绑定到第一个可用端口,然后从该端口进行所有读取。无法保证我的编写器应用程序和多个读取器进程的启动顺序(这里是我的问题的核心部分)。我的应用程序正在向每个输出端口写入大约每秒一次的消息,每个输出端口通常不超过 80 个字节(所有 ASCII 文本)。

这些阅读器客户端可能在 (i) 与我的应用程序相同的本地主机上运行,​​(ii) 单个远程主机,或 (iii) 本地网络上的不同远程主机,因此我的编写器应用程序接受任意 IPv4 目标地址作为命令参数。假设我的 writer 在主机 192.168.1.LLL(本地主机)上运行,最常用的目标地址将是:

  • 127.0.0.1
  • 192.168.1.LLL(localhost的实际外部地址)
  • 192.168.1.RRR(同一局域网上的某个远程主机)
  • 192.168.1.255(多个远程主机上的读者本地广播)

请注意,仅当将输出发送到 127.0.0.1 或 192.168.1.LLL(本地主机的实际外部地址)时,我才会看到这些错误。当我写入特定的远程主机 192.168.1.RRR 或 LAN 的广播地址 192.168.1.255 时,这些错误永远不会发生。本地 PF_INET 与远程 PF_INET UDP 写入之间应该有区别吗?也许本地写入必须在某个受各种约束的本地缓冲区内以特定方式处理,而从主机发送的数据包只是散落在风中,并且发生的任何事情都被认为超出了本地 sendto() 的报告能力?虽然我在使用广播地址 192.168.1 时从未看到这些错误。

现在我通过忽略所有ECONNREFUSED来解决这个问题sendto() 错误。似乎我倾向于在启动我的应用程序的几秒钟内得到它们,尽管从来没有在每个端口上的第一个 sendto() 上,通常只在我的 5 个输出端口中的一个上(尽管产生错误的端口并不总是相同的) )。而且,在最初的错误之后,接下来几分钟的输出(我看过的最长的)是没有错误的,即使仍然没有阅读器在运行。然而,这些错误令人费解,我希望对它们有更好的理解,以使我的代码尽可能健壮。我没有在这篇文章中包含我的实际代码,因为后者已经太长了,据我所知,代码没有什么不寻常的地方,但如果有用的话,我可以单独发布它。

谢谢!

罗杰戴维斯,大学。夏威夷的

4

1 回答 1

3

而在 UDP 层,您可以使用任何 IP 进行通信。RFC1122第 4.1.3.3 节指出,IP 层的任何错误(导致 ICMP 错误发生)必须将错误传播回应用层。正如您在RFC792第 3 页中所见,代码 3 消息是Port Unreachable.

因此,无法将 IP 数据包发送到 127.0.0.1 端口将导致 icmp 错误,在应用层表现为 ECONNREFUSED。报告异步(因为 icmp 有回复超时),那时您可能已经发送了另一个 udp 数据包。

为什么它更多地发生在本地连接上?数据包实际上并没有离开内核,因此它可以在发送下一个 udp 数据包之前回复 ICMP 错误。在其他地址上,它实际上必须放在电线上。因此,您仍然可以收到错误,但根据您的 UDP 发送速率,错误的频率会降低。此外,如果您通过网关发送,网关可能只会丢弃 udp 数据包。如果您的主机和远程主机之间有防火墙,它也可能会丢弃 icmp 回复,或限制回复的返回率。

解决该错误,如果您确实收到 ECONNREFUSED,则您知道没有具有该 IP 的主机或没有在该端口上侦听。无论哪种方式,发送仍然毫无意义。

于 2016-12-19T22:15:36.007 回答