Co dzieje się z dodatkowymi danymi strumienia Unix przy częściowych odczytach?

18

Czytałem więc wiele informacji na temat danych pomocniczych strumienia Unix, ale brakuje jednej rzeczy w całej dokumentacji, co powinno się stać, gdy nastąpi częściowy odczyt?

Załóżmy, że otrzymuję następujące komunikaty do 24-bajtowego bufora

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)

Przy pierwszym wywołaniu recvmsg otrzymuję cały msg1 (i część msg2? Czy system operacyjny kiedykolwiek to zrobi?) Jeśli otrzymam część msg2, czy od razu otrzymam dane pomocnicze i muszę je zapisać do następnego odczytu kiedy wiem, co faktycznie mówi mi wiadomość, aby zrobić z danymi? Jeśli zwolnię 20 bajtów z msg1, a następnie ponownie wywołam recvmsg, czy kiedykolwiek dostarczy on msg3 i msg4 w tym samym czasie? Czy dane pomocnicze z msg3 i msg4 są konkatenowane w strukturze komunikatu kontrolnego?

Podczas gdybym mógł pisać programy testowe, aby eksperymentalnie to odkryć, szukam dokumentacji na temat tego, jak zachowują się dane pomocnicze w kontekście transmisji strumieniowej. Dziwne, że nie mogę znaleźć na nim niczego oficjalnego.


Dodam tutaj moje wyniki eksperymentalne, które otrzymałem z tego programu testowego:

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

Linux 3.2.59, 3.17.6

Wygląda na to, że Linux dołącza fragmenty komunikatów pomocniczych na końcu innych komunikatów, o ile nie będzie potrzeby dostarczenia wcześniejszej dodatkowej zawartości podczas tego wezwania do recvmsg. Po dostarczeniu danych pomocniczych jednej wiadomości zwróci ona krótki odczyt, a nie rozpocznie następnej wiadomości danych pomocniczych. Tak więc w powyższym przykładzie otrzymane odczyty to:

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 zapewnia więcej wyrównania niż Linux i daje krótki odczyt bezpośrednio przed rozpoczęciem wiadomości z danymi pomocniczymi. Ale z radością dołączy wiadomość o charakterze pomocniczym na końcu komunikatu o charakterze pomocniczym. Tak więc w przypadku BSD wygląda na to, że jeśli twój bufor jest większy niż komunikat zawierający informacje pomocnicze, możesz uzyskać zachowanie podobne do pakietu. Otrzymane odczyty to:

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]

DO ZROBIENIA:

Chciałbym nadal wiedzieć, jak to się dzieje na starszych systemach Linux, iOS, Solaris itp. I jak można się spodziewać w przyszłości.

M. Conrad
źródło
Nie należy mylić strumieni i pakietów, w strumieniu nie ma gwarancji, że dane zostaną dostarczone w tych samych częściach, w których zostały wysłane, do tego potrzebny byłby protokół oparty na pakietach, a nie strumień.
ctrl-alt-delor
właśnie dlatego zadaję to pytanie
M Conrad,
Kolejność powinna zostać zachowana. Tak właśnie działają strumienie. Jeśli odczyt blokujący zwraca 0, oznacza to koniec strumienia. Jeśli zwróci inną liczbę, może być więcej, musisz dowiedzieć się co najmniej o jeszcze jeden. Nie ma czegoś takiego jak wiadomość1, wiadomość2 itd. Ogranicznik wiadomości nie jest przesyłany. Musisz to dodać do protokołu, jeśli potrzebujesz.
ctrl-alt-delor
1
W szczególności mam protokół strumienia tekstu i dodaję polecenie, które przekazuje deskryptor pliku z linią tekstu. Muszę wiedzieć, w jakiej kolejności otrzymywane są te dane pomocnicze w stosunku do tekstu wiadomości, aby poprawnie napisać kod.
M Conrad,
1
@MConrad: Chciałbym uzyskać kopię specyfikacji POSIX.1g. Jeśli nie jest tam wyraźnie napisane, możesz oczekiwać zachowania specyficznego dla implementacji.
Laszlo Valko

Odpowiedzi:

1

Dane pomocnicze są odbierane tak, jakby były umieszczone w kolejce wraz z pierwszym normalnym oktetem danych w segmencie (jeśli taki istnieje).

- POSIX.1-2017

W pozostałej części pytania sprawy stają się nieco owłosione.

... Do celów niniejszej sekcji datagram uważa się za segment danych, który kończy rekord i który zawiera adres źródłowy jako specjalny typ danych pomocniczych.

Segmenty danych są umieszczane w kolejce, gdy dane są dostarczane do gniazda przez protokół. Normalne segmenty danych są umieszczane na końcu kolejki podczas ich dostarczania. Jeśli nowy segment zawiera ten sam typ danych, co poprzedni segment i nie zawiera danych pomocniczych, a jeśli poprzedni segment nie kończy zapisu, segmenty są logicznie łączone w jeden segment ...

Operacja odbioru nigdy nie zwraca danych ani danych pomocniczych z więcej niż jednego segmentu.

Nowoczesne gniazda BSD dokładnie pasują do tego wyciągu. Nie jest to zaskakujące :-).

Pamiętaj, że standard POSIX został napisany po UNIX i po podziale, takim jak BSD vs. System V. Jednym z głównych celów było zrozumienie istniejącego zakresu zachowań i zapobieganie jeszcze większemu podziałowi w istniejących funkcjach.

Linux został zaimplementowany bez odniesienia do kodu BSD. Wygląda na to, że zachowuje się tutaj inaczej.

  1. Jeśli czytam cię prawidłowo, to brzmi jak Linux jest dodatkowo łączenie „segmentów”, kiedy nowy segment ma zawierać dane pomocnicze, ale poprzedni segment nie.

  2. Twój punkt, w którym „Linux dołącza części komunikatów pomocniczych na końcu innych komunikatów, o ile nie było potrzeby dostarczenia wcześniejszej dodatkowej zawartości podczas tego wezwania do recvmsg”, nie wydaje się być całkowicie wyjaśniony przez standard. Jednym z możliwych wyjaśnień byłby warunek wyścigu. Jeśli przeczytasz część „segmentu”, otrzymasz dane pomocnicze. Być może Linux zinterpretował to jako oznaczające, że pozostała część segmentu nie liczy się już jako zawierająca dane pomocnicze! Tak więc po otrzymaniu nowego segmentu jest on scalany - zgodnie ze standardem lub zgodnie z różnicą 1 powyżej.

Jeśli chcesz napisać maksymalnie przenośny program, powinieneś całkowicie unikać tego obszaru. Podczas korzystania z danych pomocniczych znacznie częściej używa się gniazd datagramów . Jeśli chcesz pracować na wszystkich dziwnych platformach, które technicznie dążą do zapewnienia czegoś w rodzaju POSIX, twoje pytanie wydaje się zapuszczać w ciemny i niesprawdzony zakątek.


Można argumentować, że Linux nadal przestrzega kilku istotnych zasad:

  1. „Dane pomocnicze są odbierane tak, jakby były umieszczone w kolejce wraz z pierwszym normalnym oktetem danych w segmencie”.
  2. Dane pomocnicze nigdy nie są „konkatenowane”, jak to ująłeś.

Nie jestem jednak przekonany, że zachowanie Linuksa jest szczególnie przydatne , gdy porównasz je z zachowaniem BSD. Wygląda na to, że opisany program musiałby dodać obejście specyficzne dla systemu Linux. I nie znam uzasadnienia, dlaczego Linux miałby się tego spodziewać.

Pisanie kodu jądra Linuksa mogło wydawać się rozsądne, ale bez testowania lub wykonywania przez jakikolwiek program.

Lub może być wykonywany przez jakiś kod programu, który w większości działa w tym podzbiorze, ale w zasadzie może mieć „błędy” lub warunki wyścigu.

Jeśli nie możesz zrozumieć zachowania Linuksa i jego zamierzonego użycia, myślę, że przemawia to za potraktowaniem tego jako „ciemnego, niesprawdzonego kąta” w Linuksie.

sourcejedi
źródło
Dzięki za szczegółową recenzję! Myślę, że na wynos jest to, że mogę bezpiecznie sobie z tym poradzić za pomocą dwóch buforów (każdy z częścią danych i częścią pomocniczą); Jeśli otrzymam deskryptory plików przy pierwszym czytaniu i nie należą one do wiadomości, ale zaczyna się kolejna wiadomość, to jeśli następny odczyt zawiera również dane pomocnicze, oznacza to, że na pewno znajdę koniec mojego komunikatu danych, który jest właścicielem pierwszego ładunku pomocniczego w tym drugim czytaniu. Na przemian w przód i w tył zawsze powinienem być w stanie dopasować komunikat do ładunku na podstawie lokalizacji pierwszego bajtu.
M Conrad,