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.