Що відбувається з допоміжними даними потоку 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 vs System V. Однією з головних цілей було допомогти зрозуміти існуючий діапазон поведінки та запобігти ще більше розщеплень у існуючих функціях.

Linux був реалізований без посилання на BSD-код. Здається, тут поводяться інакше.

  1. Якщо я правильно читаю вас, це звучить як Linux додатково об'єднання «сегментів» , коли новий сегмент робить включати допоміжні дані, але попередній сегмент не робить.

  2. Ваша думка про те, що "Linux додасть частини допоміжних повідомлень до кінця інших повідомлень до тих пір, доки під час цього виклику до recvmsg не потрібно буде передавати попередню допоміжну корисну навантаження", стандартним здається не повністю пояснений. Одне з можливих пояснень передбачає стан перегонів. Якщо ви прочитаєте частину "сегмента", ви отримаєте допоміжні дані. Можливо, Linux інтерпретував це як сенс, що решта сегменту вже не зараховується як допоміжні дані! Отже, коли отриманий новий сегмент, він об'єднується - або за стандартом, або за різницею 1 вище.

Якщо ви хочете написати максимально портативну програму, вам слід взагалі уникати цієї області. Під час використання допоміжних даних набагато частіше використовується розетки дейтаграми . Якщо ви хочете попрацювати над усіма дивними платформами, які технічно прагнуть забезпечити щось здебільшого на зразок POSIX, ваше питання, схоже, заходить у темний і неперевірений куточок.


Ви можете стверджувати, що Linux все ще дотримується кількох важливих принципів:

  1. "Допоміжні дані приймаються так, як ніби вони стояли в черзі разом з першим нормальним октетом даних у сегменті".
  2. Допоміжні дані ніколи не "з'єднуються", як ви це сказали.

Однак я не переконаний, що поведінка Linux є особливо корисною , якщо порівнювати її з поведінкою BSD. Схоже, що програмі, яку ви описуєте, потрібно було б додати специфічне для Linux рішення. І я не знаю виправдання, чому Linux очікував би, що ви це зробите.

Можливо, це виглядало розумним під час написання коду ядра Linux, але без того, щоб будь-яка програма була протестована або використана.

Або це може використовуватися якийсь програмний код, який здебільшого працює в цьому підмножині, але в принципі може мати крайні "помилки" або умови перегонів.

Якщо ви не можете зрозуміти поведінку Linux та її передбачуване використання, я вважаю, що це стверджує, що трактувати це як "темний, неперевірений куточок" в Linux.


Дякуємо за поглиблений огляд! Я думаю, що тут є те, що я можу безпечно впоратися з цим двома буферами (кожен з частиною даних та додатковою частиною); Якщо я отримаю дескриптори файлів під час першого читання, і вони не належать до цього повідомлення, але починається інше повідомлення, то якщо наступне читання також містить допоміжні дані, це означає, що я обов'язково знайду кінець свого повідомлення даних, що володіє першим допоміжним корисним навантаженням у тому другому читанні. Поперемінно вперед і назад, я завжди повинен мати можливість зіставити повідомлення з корисним навантаженням на основі розташування першого байта.
М Конрад
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.