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


18

因此,我已经阅读了很多有关unix流辅助数据的信息,但是所有文档中缺少的一件事是,如果部分读取,该怎么办?

假设我正在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等,以及它如何可以预期在未来发生。


不要混淆流和数据包,在流中,不能保证数据将以与发送时相同的块传送,为此,您将需要基于数据包的协议,而不是基于流的协议。
ctrl-alt-delor

这就是我问这个问题的原因
M Conrad

订单应保留。这就是流的作用。如果阻塞读取返回0,则它是流的结尾。如果返回另一个数字,则可能还有更多,您必须至少再读一次才能找出。没有诸如message1,message2等之类的东西。没有消息定界符被发送。如果需要,您必须将其添加到协议中。
ctrl-alt-delor

1
具体来说,我有一个文本流协议,并且要添加一条命令,该命令将文件描述符与一行文本一起传递。我需要知道该辅助数据相对于消息文本的接收顺序,以便正确编写代码。
M康拉德

1
@MConrad:我会尝试获取POSIX.1g规范的副本。如果未在此处明确编写,则您可能期望实现特定的行为。
Laszlo Valko

Answers:


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的行为及其预期用途,那么我认为这是将其视为Linux上的“未测试的黑暗角落”。


感谢您的深入审查!我认为这里的要点是,我可以使用两个缓冲区(每个都有数据部分和辅助部分)安全地处理它;如果我在第一次读取时收到文件描述符,但它们不属于该消息,但是又有一条消息开始,则如果下一次读取还包含辅助数据,则意味着我肯定会发现拥有第一个辅助有效载荷的数据消息的结尾在第二读中。来回交替,我应该始终能够根据第一个字节的位置将消息与有效负载进行匹配。
M康拉德
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.