Что происходит с вспомогательными данными потока Unix при частичном чтении?

18

Итак, я прочитал много информации о вспомогательных данных 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, получу ли я вспомогательные данные сразу же, и мне нужно сохранить их для следующего чтения? когда я узнаю, что сообщение на самом деле говорит мне делать с данными? Если я освобожу 20 байтов от msg1 и затем снова вызываю recvmsg, будут ли они одновременно доставлять msg3 и msg4? Вспомогательные данные из msg3 и msg4 объединяются в структуре управляющего сообщения?

Хотя я мог бы написать тестовые программы для экспериментального выяснения этого, я ищу документацию о том, как вспомогательные данные ведут себя в контексте потоковой передачи. Кажется странным, что я не могу найти ничего официального на этом.


Я собираюсь добавить свои экспериментальные результаты, которые я получил из этой тестовой программы:

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

Linux 3.2.59, 3.17.6

Похоже, что Linux будет добавлять части вспомогательных сообщений в конец других сообщений, если во время этого вызова recvmsg не нужно было доставлять какую-либо предварительную полезную нагрузку. Как только вспомогательные данные одного сообщения доставляются, он вернет короткое чтение, а не запустит следующее сообщение вспомогательных данных. Итак, в приведенном выше примере чтения я получаю:

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
именно поэтому я задаю этот вопрос
М Конрад
Порядок должен быть сохранен. Это то, что делают потоки. Если блокирующее чтение возвращает 0, то это конец потока. Если он возвращает другое число, то может быть больше, вам нужно сделать хотя бы еще одно чтение, чтобы узнать. Нет такого понятия, как message1, message2 и т. Д. Разделитель сообщений не передается. Вы должны добавить это в свой протокол, если вам это нужно.
Ctrl-Alt-Delor
1
В частности, у меня есть протокол текстового потока, и я добавляю команду, которая передает дескриптор файла со строкой текста. Мне нужно знать, в каком порядке эти вспомогательные данные получены относительно текста сообщения, чтобы правильно написать код.
М Конрад
1
@MConrad: я бы попробовал получить копию спецификации POSIX.1g. Если это явно не написано, вы можете ожидать поведения, специфичного для реализации.
Ласло Валко,

Ответы:

1

Вспомогательные данные принимаются так, как если бы они были поставлены в очередь вместе с первым октетом нормальных данных в сегменте (если есть).

- POSIX.1-2017

Для остальной части вашего вопроса вещи становятся немного волосатыми.

... Для целей этого раздела дейтаграмма считается сегментом данных, который завершает запись, и который включает адрес источника в качестве специального типа вспомогательных данных.

Сегменты данных помещаются в очередь по мере доставки данных в сокет по протоколу. Обычные сегменты данных помещаются в конец очереди по мере их доставки. Если новый сегмент содержит данные того же типа, что и предыдущий сегмент, и не содержит вспомогательных данных, и если предыдущий сегмент не завершает запись, сегменты логически объединяются в один сегмент ...

Операция приема никогда не должна возвращать данные или вспомогательные данные из более чем одного сегмента.

Поэтому современные разъемы BSD точно соответствуют этому экстракту. Это не удивительно :-).

Помните, стандарт POSIX был написан после UNIX и после разделений, таких как BSD против System V. Одна из основных целей состояла в том, чтобы помочь понять существующий диапазон поведения и предотвратить еще больше разделений в существующих функциях.

Linux был реализован без ссылки на код BSD. Кажется, здесь ведут себя по-другому.

  1. Если я правильно читаю вас, это звучит как Linux дополнительно объединение «сегментов» , когда новый сегмент делает включать вспомогательные данные, но предыдущий сегмент не делает.

  2. Ваше утверждение о том, что «Linux будет добавлять части вспомогательных сообщений в конец других сообщений, если во время этого вызова recvmsg не требовалось доставлять дополнительную вспомогательную нагрузку», кажется, полностью не объясняется стандартом. Одним из возможных объяснений может быть состояние гонки. Если вы прочитаете часть «сегмента», вы получите вспомогательные данные. Возможно, Linux интерпретировал это как означающее, что оставшаяся часть сегмента больше не считается включением вспомогательных данных! Поэтому при получении нового сегмента он объединяется - либо в соответствии со стандартом, либо согласно разнице 1 выше.

Если вы хотите написать максимально переносимую программу, вам следует вообще избегать этой области. При использовании вспомогательных данных гораздо чаще используются гнезда дейтаграмм . Если вы хотите работать на всех странных платформах, которые технически стремятся предоставить что-то, в основном подобное POSIX, ваш вопрос, похоже, уходит в темный и непроверенный угол.


Можно утверждать, что Linux по-прежнему следует нескольким важным принципам:

  1. «Вспомогательные данные принимаются так, как если бы они были поставлены в очередь вместе с первым октетом нормальных данных в сегменте».
  2. Вспомогательные данные никогда не «объединяются», как вы выразились.

Однако я не уверен, что поведение Linux особенно полезно , если сравнивать его с поведением BSD. Кажется, что программа, которую вы описываете, должна добавить специфический для Linux обходной путь. И я не знаю оправдания того, почему Linux ожидает, что вы это сделаете.

Это могло бы показаться разумным при написании кода ядра Linux, но без какой-либо проверки или выполнения какой-либо программой.

Или это может быть реализовано некоторым программным кодом, который в основном работает в этом подмножестве, но в принципе может иметь «ошибки» в крайнем случае или условия гонки.

Если вы не можете разобраться в поведении Linux и его предполагаемом использовании, я думаю, что это приводит к трактовке этого «темного, непроверенного угла» в Linux.

sourcejedi
источник
Спасибо за углубленный обзор! Я думаю, что вывод здесь заключается в том, что я могу безопасно справиться с этим с двумя буферами (каждый с порцией данных и вспомогательной порцией); Если я получаю файловые дескрипторы при первом чтении, и они не принадлежат сообщению, но начинается другое сообщение, тогда, если следующее чтение также содержит вспомогательные данные, это означает, что я определенно найду конец моего сообщения данных, владеющего первой вспомогательной полезной нагрузкой. во втором чтении. Чередуя взад и вперед, я всегда должен быть в состоянии сопоставить сообщение с полезной нагрузкой, основываясь на расположении первого байта.
М Конрад