部分读取时 unix 流辅助数据会发生什么情况?

部分读取时 unix 流辅助数据会发生什么情况?

因此,我已经阅读了大量有关 unix-stream 辅助数据的信息,但所有文档中缺少的一件事是,当部分读取时应该发生什么?

假设我将以下消息接收到 24 字节缓冲区中

msg1 [20 byes]   (no ancillary data)
msg2 [7 bytes]   (2 file descriptors)
msg3 [7 bytes]   (1 file descriptor)
msg4 [10 bytes]  (no ancillary data)
msg5 [7 bytes]   (5 file descriptors)

第一次调用recvmsg,我得到了所有的msg1(和部分msg2?操作系统会这样做吗?)如果我得到了msg2的一部分,我是否立即得到辅助数据,并且需要保存它以供下次读取当我知道该消息实际上告诉我如何处理数据时?如果我从 msg1 中释放 20 个字节,然后再次调用 recvmsg,它会同时传递 msg3 和 msg4 吗?来自 msg3 和 msg4 的辅助数据是否在控制消息结构中连接?

虽然我可以编写测试程序来通过实验找出这一点,但我正在寻找文档关于辅助数据在流上下文中的行为方式。奇怪的是我找不到任何官方的信息。


我将在这里添加我从这个测试程序中获得的实验结果:

https://github.com/nrdvana/daemonproxy/blob/master/src/ancillary_test.c

Linux 3.2.59、3.17.6

看起来,只要在调用 recvmsg 期间不需要传递先前的辅助有效负载,Linux 就会将辅助承载消息的一部分附加到其他消息的末尾。一旦一条消息的辅助数据被传送,它将返回一个简短的读取而不是开始下一条辅助数据消息。因此,在上面的示例中,我得到的读数是:

recv1: [24 bytes] (msg1 + partial msg2 with msg2's 2 file descriptors)
recv2: [10 bytes] (remainder of msg2 + msg3 with msg3's 1 file descriptor)
recv3: [17 bytes] (msg4 + msg5 with msg5's 5 file descriptors)
recv4: [0 bytes]

BSD 4.4、10.0

BSD 比 Linux 提供更多的对齐方式,并且可以立即进行简短的读取带有辅助数据的消息的开始。但是,它很乐意将非辅助承载消息附加到辅助承载消息的末尾。因此,对于 BSD 来说,如果您的缓冲区大于辅助承载消息,您就会得到几乎类似于数据包的行为。我得到的读数是:

recv1: [20 bytes] (msg1)
recv2: [7 bytes]  (msg2, with msg2's 2 file descriptors)
recv3: [17 bytes] (msg3, and msg4, with msg3's 1 file descriptor)
recv4: [7 bytes]  (msg5 with 5 file descriptors)
recv5: [0 bytes]

去做:

仍然想知道它在旧版 Linux、iOS、Solaris 等上是如何发生的,以及它是如何发生的可以预计将来会发生。

答案1

接收辅助数据就好像它与段中的第一个正常数据八位字节(如果有)一起排队。

--POSIX.1-2017

对于你问题的其余部分,事情变得有点棘手。

...就本节而言,数据报被视为终止记录的数据段,并且包括作为特殊类型的辅助数据的源地址。

当数据通过协议传送到套接字时,数据段被放入队列中。普通数据段在传送时被放置在队列的末尾。如果新段包含与前一个段相同类型的数据并且不包含辅助数据,并且如果前一个段不终止记录,则这些段将在逻辑上合并为单个段...

接收操作绝不能从多个段返回数据或辅助数据。

所以现代 BSD 套接字与此摘录完全匹配。这并不奇怪:-)。

请记住,POSIX 标准是在 UNIX 之后以及 BSD 与 System V 等分裂之后编写的。主要目标之一是帮助理解现有的行为范围,并防止现有功能出现更多分裂。

Linux 的实现并未参考 BSD 代码。它在这里的行为似乎有所不同。

  1. 如果我没听错的话,听起来 Linux 在新段出现时还会合并“段”包括辅助数据,但前一段没有。

  2. 您的观点是“只要在调用 recvmsg 期间不需要传递任何先前的辅助有效负载,Linux 就会将辅助承载消息的一部分附加到其他消息的末尾”,这一点似乎没有完全由标准解释。一种可能的解释涉及竞争条件。如果您阅读“片段”的一部分,您将收到辅助数据。也许 Linux 将此解释为意味着该段的其余部分不再算作包括辅助数据!因此,当收到新段时,它会被合并 - 或者按照标准,或者按照上面的差异 1。

如果你想编写一个最大程度可移植的程序,你应该完全避免这个区域。使用辅助数据时,更常见的是使用数据报插座。如果您想在所有奇怪的平台上工作,而这些平台在技术上渴望提供主要类似于 POSIX 的东西,那么您的问题似乎是在冒险进入一个黑暗且未经测试的角落。


你可能会说 Linux 仍然遵循几个重要的原则:

  1. “辅助数据的接收就好像它与段中的第一个正常数据八位字节一起排队一样”。
  2. 正如您所说,辅助数据永远不会“连接”。

然而,我不相信 Linux 的行为是特别的有用,当你将它与 BSD 行为进行比较时。您描述的程序似乎需要添加特定于 Linux 的解决方法。我不知道为什么 Linux 会期望你这样做。

在编写 Linux 内核代码时,它可能看起来很明智,但从未经过任何程序的测试或运用。

或者它可能由某些程序代码执行大多在此子集下工作,但原则上可能存在边缘情况“错误”或竞争条件。

如果您无法理解 Linux 的行为及其预期用途,我认为您可以将其视为 Linux 上的“黑暗的、未经测试的角落”。

相关内容