12.5. Fiabilitate – secvențe, ACK-uri, retransmisii

Stratul de încadrare detectează coruperea datelor cu ajutorul CRC-urilor sale. Stratul de fiabilitate transformă „coruperea detectată” în „aplicația nu vede niciodată date defecte” prin negocierea retransmisiilor ori de câte ori un pachet nu ajunge intact.

12.5.1. Numere de secvență

Fiecare antet de pachet poartă un număr de secvență de un octet, separat pentru fiecare direcție de transmisie. Expeditorul incrementează contorul înainte de transmisie; receptorul verifică dacă secvența fiecărui pachet primit este cea anterioară plus unu (modulo 256).

La receptor pot apărea trei situații în locul unui pachet curat, în ordine:

  • Numărul de secvență așteptat, cu un CRC valid. Pachetul este livrat către stratul următor.

  • Numărul de secvență așteptat, cu un CRC invalid. Receptorul renunță la pachet și (dacă ACK-urile sunt negociate) trimite un NAK prin care cere o retransmisie.

  • Un număr de secvență cu o unitate mai mare decât cel așteptat, cu un CRC valid. Receptorul știe că pachetul anterior s-a pierdut; trimite un NAK care face referire la secvența pierdută și păstrează deoparte pachetul nou.

Cazul duplicatului (o retransmisie care sosește după ce originalul a ajuns în cele din urmă) este tratat prin verificarea față de contorul așteptat: dacă secvența este în urma celei așteptate, pachetul este un duplicat, iar receptorul îl elimină după ce trimite ACK-ul pe care expeditorul evident nu l-a primit prima dată.

12.5.2. ACK și NAK

Doi biți de indicator din antetul pachetului poartă traficul de fiabilitate propriu-zis:

  • ACK_REQ setat pe un pachet de ieșire înseamnă „vreau o confirmare înapoi.” Pachetele de date setează în mod normal acest bit; ping-urile de stare și evenimentele unice este posibil să nu îl seteze.

  • ACK setat pe un pachet înseamnă „acest pachet este confirmarea pentru numărul de secvență din antet.” Nu poartă nicio sarcină utilă proprie.

  • NAK setat înseamnă „acest pachet respinge unul anterior” – de obicei din cauza unui CRC invalid sau a unei lacune în numerele de secvență. Antetul indică expeditorului care secvență trebuie retransmisă.

Expeditorul rulează o buclă stop-and-wait: transmite un pachet care necesită confirmare, apoi așteaptă ACK-ul (sau NAK-ul) corespunzător înainte de a-l trimite pe următorul. Modelul cu un singur pachet în zbor menține starea expeditorului limitată – câteva sute de octeți pe cele mai mici camere – și se potrivește cu rolul protocolului ca un canal de control între două capete, mai degrabă decât o conductă optimizată pentru debit. La un NAK, expeditorul retransmite același pachet cu indicatorul RTX setat, astfel încât receptorul să știe că este o reîncercare.

12.5.3. Sincronizarea retransmisiilor

Dacă nici ACK-ul, nici NAK-ul nu sosesc în limita timpului de expirare pentru retransmisie, expeditorul retransmite pachetul în zbor din proprie inițiativă. Timpul de expirare are valoarea implicită 500 ms și se dublează la fiecare reîncercare consecutivă (1 s, 2 s, …). După numărul configurat de reîncercări – implicit trei – expeditorul renunță și raportează o eroare de transport aplicației.

Dublarea timpului de expirare este tiparul standard de backoff exponențial. Un prim timp de expirare scurt prinde rapid pachetele pierdute; dublarea înseamnă că o gazdă ocupată câteva sute de milisecunde nu declanșează o avalanșă de duplicate care să sporească încărcarea.

12.5.4. Configurarea fiabilității

Ambele capete pot dezactiva, de comun acord, părți ale stratului de fiabilitate, atunci când aplicația își poate permite să piardă date:

  • protocol.init(ack=False) dezactivează ACK-urile per pachet. Expeditorul trimite și uită; receptorul livrează tot ce sosește. Bun pentru transmiterea în flux a datelor de la senzori, unde un eșantion învechit este acceptabil.

  • protocol.init(seq=False) dezactivează urmărirea numerelor de secvență, ceea ce implică și dezactivarea ACK-urilor. Util doar pe transporturi perfect fiabile.

  • protocol.init(crc=False) dezactivează validarea CRC, dar lasă restul încadrării intact. Merită făcut doar atunci când transportul în sine este suficient de robust încât erorile CRC să nu apară.

Valorile implicite – totul activat – sunt punctul de pornire corect pentru orice sesiune de depanare gazdă-cameră. Odată ce aplicația ajunge în producție, compromisurile devin specifice datelor sale și transportului său.

12.5.5. Codurile de stare

Atunci când o eroare de transport se propagă în sus, până la codul aplicației, aceasta sosește sub forma unui cod de stare. Biblioteca protocolului definește zece:

  • SUCCESS – operațiunea s-a finalizat.

  • FAILED – comanda a eșuat dintr-un motiv nespecificat.

  • INVALID – receptorul a respins comanda sau unul dintre argumentele acesteia.

  • TIMEOUT – un temporizator de reîncercare a expirat.

  • BUSY – camera este ocupată (de obicei un canal blocat).

  • CHECKSUM – CRC-ul antetului sau al sarcinii utile nu s-a potrivit.

  • SEQUENCE – numărul de secvență a fost în afara ordinii dincolo de ceea ce stratul poate recupera.

  • OVERFLOW – o sarcină utilă a depășit maximul negociat.

  • FRAGMENT – un mesaj cu mai multe fragmente a sosit cu părți lipsă.

  • UNKNOWN – o categorie defensivă pentru condiții cu adevărat neașteptate.

Codul de gazdă care apelează channel_read() le vede ca excepții Python; codul de aplicație de pe partea camerei care a optat pentru o gestionare personalizată a erorilor le vede ca valori returnate de funcțiile de retroapelare (callback) ale backendului. Majoritatea aplicațiilor de cameră nu au nevoie deloc să se uite la codurile de stare – biblioteca gestionează reîncercarea, iar la aplicație ajung doar eșecurile cu adevărat nerecuperabile (de exemplu, transportul în sine a dispărut).

Cu încadrarea pregătită pentru a detecta coruperea și fiabilitatea pregătită pentru a o recupera, munca la nivelul firului este încheiată. Codul de aplicație vede pachete încadrate, ordonate și intacte; octeții din interiorul lor sunt liberi să însemne orice dorește canalul de deasupra.