12.5. Niezawodność – sekwencje, ACK, retransmisje#
Warstwa ramkowania wykrywa uszkodzenia za pomocą swoich sum CRC. Warstwa niezawodności zamienia „wykryte uszkodzenie” w „aplikacja nigdy nie widzi uszkodzonych danych”, negocjując retransmisje za każdym razem, gdy pakiet nie dotrze w całości.
12.5.1. Numery sekwencyjne#
Każdy nagłówek pakietu zawiera jednobajtowy numer sekwencyjny, osobny dla każdego kierunku przesyłania. Nadawca zwiększa licznik przed transmisją; odbiorca sprawdza, czy numer sekwencyjny każdego odebranego pakietu jest równy poprzedniemu plus jeden (modulo 256).
Zamiast czystego pakietu w prawidłowej kolejności, u odbiorcy mogą pojawić się trzy rzeczy:
Oczekiwany numer sekwencyjny z prawidłowym CRC. Pakiet jest przekazywany do następnej warstwy.
Oczekiwany numer sekwencyjny z błędnym CRC. Odbiorca odrzuca pakiet i (jeśli wynegocjowano ACK) wysyła NAK z prośbą o retransmisję.
Numer sekwencyjny o jeden wyższy niż oczekiwany, z prawidłowym CRC. Odbiorca wie, że poprzedni pakiet zaginął; wysyła NAK odwołujący się do brakującego numeru sekwencyjnego i odkłada na bok nowy pakiet.
Przypadek duplikatu (retransmisja docierająca po tym, jak oryginał w końcu się przedostał) jest obsługiwany przez porównanie z oczekiwanym licznikiem: jeśli numer sekwencyjny jest za oczekiwanym, pakiet jest duplikatem i odbiorca odrzuca go po wysłaniu ACK, którego nadawca najwyraźniej nie otrzymał za pierwszym razem.
12.5.2. ACK i NAK#
Dwa bity flag w nagłówku pakietu przenoszą sam ruch związany z niezawodnością:
ACK_REQustawiony na wychodzącym pakiecie oznacza „chcę otrzymać potwierdzenie”. Pakiety danych zwykle to ustawiają; pingi statusu i jednorazowe zdarzenia mogą tego nie robić.ACKustawiony na pakiecie oznacza „ten pakiet jest potwierdzeniem dla numeru sekwencyjnego z nagłówka”. Nie przenosi własnego ładunku.NAKustawiony oznacza „ten pakiet odrzuca poprzedni” – zwykle z powodu błędnego CRC lub luki w numerach sekwencyjnych. Nagłówek wskazuje nadawcy, który numer sekwencyjny należy retransmitować.
Nadawca działa w pętli stop-and-wait: transmituje jeden pakiet wymagający potwierdzenia, a następnie czeka na pasujący ACK (lub NAK) przed wysłaniem kolejnego. Model z jednym pakietem w locie utrzymuje stan nadawcy w ograniczonych ramach – kilkaset bajtów na najmniejszych kamerach – i odpowiada roli protokołu jako kanału sterującego między dwoma punktami końcowymi, a nie potoku zoptymalizowanego pod kątem przepustowości. Po otrzymaniu NAK nadawca retransmituje ten sam pakiet z ustawioną flagą RTX, aby odbiorca wiedział, że to ponowna próba.
12.5.3. Czas retransmisji#
Jeśli ani ACK, ani NAK nie dotrze w czasie retransmit timeout, nadawca samodzielnie retransmituje pakiet znajdujący się w locie. Limit czasu domyślnie wynosi 500 ms i podwaja się przy każdej kolejnej próbie (1 s, 2 s, …). Po skonfigurowanej liczbie prób – domyślnie trzech – nadawca poddaje się i zgłasza aplikacji błąd transportu.
Podwajanie limitu czasu to standardowy wzorzec exponential backoff. Krótki pierwszy limit czasu szybko wychwytuje utracone pakiety; podwajanie sprawia, że host zajęty przez kilkaset milisekund nie wywołuje lawiny duplikatów potęgujących obciążenie.
12.5.4. Konfigurowanie niezawodności#
Oba końce mogą za zgodą wyłączyć fragmenty warstwy niezawodności, gdy aplikacja może sobie pozwolić na utratę danych:
protocol.init(ack=False)wyłącza ACK dla poszczególnych pakietów. Nadawca wysyła i zapomina; odbiorca dostarcza to, co dotrze. Dobre do strumieniowego przesyłania danych z sensora, gdzie nieaktualna próbka jest akceptowalna.protocol.init(seq=False)wyłącza śledzenie numerów sekwencyjnych, co implikuje również wyłączenie ACK. Przydatne tylko na całkowicie niezawodnych transportach.protocol.init(crc=False)wyłącza walidację CRC, ale pozostawia resztę ramkowania nienaruszoną. Warto to robić tylko wtedy, gdy sam transport jest na tyle solidny, że błędy CRC nie występują.
Domyślne ustawienia – wszystko włączone – są właściwym punktem wyjścia dla każdej sesji debugowania host-kamera. Gdy aplikacja trafi do produkcji, kompromisy stają się specyficzne dla jej danych i transportu.
12.5.5. Kody statusu#
Gdy błąd transportu propaguje się do kodu aplikacji, dociera jako kod statusu. Biblioteka protokołu definiuje ich dziesięć:
SUCCESS– operacja zakończona pomyślnie.FAILED– polecenie nie powiodło się z nieokreślonego powodu.INVALID– odbiorca odrzucił polecenie lub jeden z jego argumentów.TIMEOUT– upłynął czas licznika ponownych prób.BUSY– kamera jest zajęta (zwykle zablokowany kanał).CHECKSUM– CRC nagłówka lub ładunku nie pasowało.SEQUENCE– numer sekwencyjny był poza kolejnością w stopniu, z którego warstwa nie może się odzyskać.OVERFLOW– ładunek przekroczył wynegocjowane maksimum.FRAGMENT– wieloczęściowa wiadomość dotarła z brakującymi fragmentami.UNKNOWN– defensywny przypadek ogólny dla naprawdę nieoczekiwanych sytuacji.
Kod hosta wywołujący channel_read() widzi je jako wyjątki Pythona; kod aplikacji po stronie kamery, który zdecydował się na niestandardową obsługę błędów, widzi je jako wartości zwracane z wywołań zwrotnych backendu. Większość aplikacji kamery w ogóle nie musi patrzeć na kody statusu – biblioteka obsługuje ponawianie prób, a do aplikacji docierają tylko naprawdę nieodwracalne awarie (np. sam transport zniknął).
Mając ramkowanie wykrywające uszkodzenia i niezawodność umożliwiającą odzyskanie sprawności, praca na poziomie łącza jest zakończona. Kod aplikacji widzi pakiety w ramkach, uporządkowane i nienaruszone; bajty wewnątrz nich mogą oznaczać cokolwiek, czego zażąda kanał znajdujący się wyżej.