A protokollok témakörébe való bevezetésként három, egyre növekvő bonyolultságú protokollt fogunk megnézni. Az érdeklődő olvasók számára ezekhez és további protokollokhoz egy szimulátor áll rendelkezésre a weben (lásd a bevezetőt). Mielőtt megnéznénk a protokollokat, hasznos nyilvánvalóvá tenni néhány, a kommunikáció modelljét megalapozó feltételezést.
Először is feltételezzük, hogy a fizikai réteg, az adatkapcsolati réteg és a hálózati réteg független folyamatok, melyek üzenetek oda-vissza küldözgetésével kommunikálnak egymással. Egy gyakori megvalósítás látható a 3.10. ábrán. A fizikai réteg folyamatai és az adatkapcsolati réteg néhány folyamata egy hozzájuk rendelt (dedikált) hardveren fut, melyet hálózati csatoló kártyának (Network Interface Card, NIC) hívnak. Az adatkapcsolati réteg folyamatának és a hálózati réteg folyamatának többi része a fő CPU-n fut, az operációs rendszer részeként. Az adatkapcsolati réteg szoftvere gyakran eszközillesztő vagy eszközmeghajtó program (device driver) formájában található meg. Persze más megvalósítások is lehetségesek (például három folyamat egyetlen kitüntetett chipen belül, melyet hálózati gyorsítónak (network accelerator) hívnak, vagy a három folyamat a fő CPU-n fut szoftverrel definiált sebességgel). Az implementáció lényegében évtizedről évtizedre változik a technikai haladásnak megfelelően. Bármelyik esetben a három réteg különálló folyamatnak tekintése fogalmilag világosabbá teszi a tárgyalást, és a rétegek függetlenségének hangsúlyozását is szolgálja.
Egy másik kulcsfontosságú feltételezés az, hogy az A gép megbízható, összeköttetés-alapú szolgáltatás alkalmazásával akar a B gépnek egy hosszú adatfolyamot küldeni. Később azt az esetet is átgondoljuk, amikor egyidejűleg B is akar adatot küldeni A-nak. A-ról feltételezzük, hogy végtelen utánpótlása van elküldendő adatokból, és sohasem kell várakoznia az adatok előállítására. Amikor A adatkapcsolati rétege adatot kér, a hálózati réteg mindig azonnal teljesíteni tudja a kérést. (Ezt a megkötést később szintén elvetjük.)
Azt is feltesszük, hogy a gépek nem fagynak le. Ez azt jelenti, hogy ezek a protokollok a kommunikációs hibákkal foglalkoznak, nem pedig a lefagyott és újraindított számítógépek által okozott problémákkal.
Ami az adatkapcsolati réteget illeti, a hálózati réteg által az interfészen keresztül hozzá eljuttatott csomag tiszta adat, aminek minden bitjét továbbítani kell a célgép hálózati rétegéhez. Az, hogy a hálózati réteg a csomag egy részét fejrésznek értelmezi, nem tartozik az adatkapcsolati rétegre.
Amikor az adatkapcsolati réteg megkap egy csomagot, azt egy adatkapcsolati fejrésszel és farokrésszel kiegészítve ágyazza be egy keretbe (lásd 3.1. ábra). Így egy keret egy beágyazott csomagból, némi vezérlési információból (a fejrészben) és egy ellenőrző összegből áll (a farokrészben). A keretet az adatkapcsolati réteg ezután továbbítja a másik gép adatkapcsolati rétegének. A továbbiakban fel fogjuk tenni, hogy léteznek megfelelő könyvtári eljárások a keretek átadására a fizikai rétegnek (küldés: to_physical_layer), valamint azok átvételére a fizikai rétegtől (fogadás: from_physical_layer). Ezek az eljárások számítják ki, ellenőrzik és illesztik a keretbe az ellenőrző összeget (melyet jórészt egyébként hardverben valósítanak meg), így az általunk ebben a fejezetben fejlesztett algoritmusoknak nem kell bajlódniuk vele. Használható például az ebben a fejezetben korábban ismertetett CRC-algoritmus.
Kezdetben a vevőnek nincs mit tennie. Csak üldögél, várva, hogy valami történjen. A fejezet példaprotokolljaiban a wait_for_event(&event) eljáráshívással jelezzük, hogy az adatkapcsolati réteg vár valamire. Ez az eljárás csak akkor tér vissza, ha valami történt (például egy keret érkezett). A visszatéréskor az event változó mondja meg, hogy mi volt az esemény. A lehetséges események halmaza a különféle ismertetett protokolloknál különböző, és mindegyik protokollhoz külön fogjuk meghatározni. Megjegyezzük, hogy egy valóságosabb helyzetben az adatkapcsolati réteg nem ücsörög egy végtelen ciklusban eseményre várva, ahogyan javasoltuk, hanem egy megszakítást kezel, aminek a beérkezésekor félbeszakítja addigi tevékenységét, és a beérkező keret feldolgozását kezdi el. Ennek ellenére az egyszerűség kedvéért nem veszünk tudomást az adatkapcsolati rétegben folyó párhuzamos tevékenységekről, és feltételezzük, hogy a szoftver dolga kizárólag a mi egyetlen csatornánk kezelése.
Amikor egy keret érkezik a vevőhöz, a hardver kiszámítja az ellenőrző összeget. Ha az helytelen (azaz átviteli hiba történt), az adatkapcsolati réteget tájékoztatja erről (event = cksum_err). Ha a keret sértetlenül érkezik meg, az adatkapcsolati réteg erről szintén tájékoztatást kap (event = frame_arrival), így az megkaphatja a keretet a fizikai rétegtől a from_physical_layer eljárás használatával. Amint a vevő adatkapcsolati rétege megkapta a sértetlen keretet, ellenőrzi a vezérlőinformációkat a fejrészben, és ha minden rendben van, a csomag részt továbbadja a hálózati rétegnek. A keret fejrésze semmilyen körülmények között sem kerül a hálózati réteghez.
Jó oka van annak, hogy a hálózati rétegnek sohasem szabad átadni a keret fejrészének semmilyen részét: a hálózati és az adatkapcsolati protokollt teljesen szét kell választani. Amíg a hálózati réteg semmit sem tud az adatkapcsolati protokollról vagy a keretformátumról, ezek anélkül változhatnak meg, hogy bármilyen változtatást kellene tenni a hálózati réteg szoftverében. Pontosan ez történik, amikor egy új hálózati csatoló kártyát telepítünk egy számítógépre. A hálózati és az adatkapcsolati réteg között merev csatolást biztosítva, nagyon leegyszerűsödik a szoftver tervezése, mert így a különböző rétegekben levő kommunikációs protokollok egymástól függetlenül fejleszthetők.
A 3.11. ábra néhány deklarációt mutat be (C nyelven), amelyek több, később ismertetendő protokollban is megtalálhatók. Öt adatstruktúrát definiáltunk: boolean, seq_nr, packet, frame_kind és frame. A boolean egy felsorolt típus, és a true és a false értékeket veheti fel. A seq_nr egy kis egész szám, melyet a keretek megszámozására használunk, hogy meg tudjuk őket különböztetni. Ezek a sorszámok 0 és MAX_SEQ közötti értéket vehetnek fel, MAX_SEQ-t is beleértve, mely minden egyes protokollban definiálva van, amelyiknek szüksége van rá. A packet az az információegység, amit az azonos gépen levő hálózati és adatkapcsolati réteg között kerül átadásra, vagy a kommunikáló hálózati réteg párok cserélnek ki egymással. A mi modellünkben ez mindig MAX_PKT számú bájtot tartalmaz, de a valóságnak jobban megfelelne, ha változó hosszúságú lenne.
3.11. ábra - Néhány definíció, amely a későbbi protokollokhoz szükséges. Ezek a definíciók a protocol.h fájlban találhatók
A frame négy mezőből áll: kind, seq, ack és info, melyekből az első három vezérlőinformációt tartalmaz, az utolsó pedig a valódi továbbítandó adatot. Ezeket a vezérlőmezőket együttesen a keret fejrészének (frame header) nevezzük.
A kind mező azt mondja meg, hogy van-e adat a keretben, néhány protokoll ugyanis különbséget tesz azok között a keretek között, amelyek kizárólag vezérlőinformációt tartalmaznak, illetve amelyek adatot is. A seq és az ack mezőket sorszámozáshoz, illetve nyugtákhoz használjuk; később részletesebben ismertetjük a használatukat. A keret info mezője egyetlen csomagot tartalmaz; egy vezérlőkeret az info mezőt nem használja. Egy a valóságnak jobban megfelelő megvalósításban változó hosszúságú info mezőt kellene használnunk, és a vezérlőkereteknél ezt a mezőt el is kellene hagynunk.
Fontos, hogy tisztában legyünk a csomag és a keret egymáshoz való viszonyával. A hálózati réteg úgy építi fel a csomagot, hogy vesz egy üzenetet a szállítási rétegtől és hozzáteszi a hálózati réteg fejrészét. Ezt a csomagot átadja az adatkapcsolati rétegnek, hogy az beletegye egy kimenő keret info mezőjébe. Amikor a keret megérkezik a célhoz, az adatkapcsolati réteg kiveszi a csomagot a keretből és átadja a hálózati rétegnek. Ilyen módon a hálózati réteg úgy működhet, mintha a gépek közvetlenül csomagokat tudnának cserélni.
Számos eljárást is felsoroltunk a 3.11. ábrán. Ezek könyvtári rutinok, melyek részletei megvalósításfüggők, és belső működésükkel itt nem foglalkozunk a továbbiakban. A wait_for_event eljárás egy végtelen ciklusban ücsörög, várva, hogy valami történjen, mint ahogyan korábban már említettük. A to_network_layer és a from_network_layer eljárásokat az adatkapcsolati réteg arra használja, hogy csomagokat adjon át a hálózati rétegnek, illetve vegyen át a hálózati rétegtől. Vegyük észre, hogy a from_physical_layer és a to_physical_layer eljárásokat keretek átadására használjuk az adatkapcsolati és a fizikai réteg között, míg a to_network_layer és a from_network_layer eljárásokat csomagok átadására az adatkapcsolati réteg és a hálózati réteg között. Más szóval, a to_network_layer és a from_network_layer a 2. és 3. rétegek közötti interfésszel foglalkozik, míg a from_physical_layer és a to_physical_layer az 1. és 2. réteg közöttivel.
A legtöbb protokollban megbízhatatlan csatornát tételezünk fel, amely alkalomadtán egész kereteket elveszíthet. Ahhoz, hogy ilyen csapásokból képes legyen felépülni, a küldő adatkapcsolati rétegnek minden keret elküldésekor el kell indítania egy belső időzítőt vagy órát. Ha bizonyos, előre meghatározott időn belül nem érkezik válasz, az óra lejár, és az adatkapcsolati réteg egy megszakítás jelzést kap.
A protokolljainkban úgy oldjuk ezt meg, hogy a wait_for_event eljárás visszatérhet event = timeout eseménnyel. A start_timer és stop_timer eljárásokat az időzítő be-, illetve kikapcsolására használjuk. Az időtúllépés csak akkor következhet be, ha az időzítő jár, és mielőtt a stop_timer-t meghívnánk. A start_timer meghívható akkor is, ha az időzítő jár; egy ilyen hívás egyszerűen lenullázza az órát, hogy a következő időtúllépés egy teljes időzítési intervallum után következzen be (hacsak közben az időzítőt nem nullázzuk le újra, vagy nem kapcsoljuk ki).
A start_ack_timer és a stop_ack_timer eljárásokat a segéd időzítő vezérlésére használjuk. Ez az időzítő állít elő nyugtákat bizonyos körülmények között.
Az enable_network_layer és a disable_network_layer eljárásokat azok a kifinomultabb protokollok használják, melyekben már nem tételezzük fel, hogy a hálózati rétegnek mindig van küldeni való csomagja. Amikor az adatkapcsolati réteg engedélyezi a hálózati réteget, a hálózati réteg megszakítást küldhet, ha van elküldeni való csomagja. Ezt az event = network_layer_ready-vel jelezzük. Amikor a hálózati réteg le van tiltva, nem okozhat ilyen eseményt. Ügyelve arra, hogy mikor engedélyezi és tiltja le a hálózati rétegét, az adatkapcsolati réteg megakadályozhatja, hogy a hálózati réteg elárassza csomagokkal, amelyek tárolására nincs puffer területe.
A keretsorszámok mindig a 0–MAX_SEQ (zárt) intervallumon belül vannak, ahol MAX_SEQ a különböző protokollokhoz különböző. Gyakran szükséges, hogy egy sorszámot 1-gyel továbbléptessünk körkörösen (azaz MAX_SEQ-t 0 követi). Az inc makró ezt a léptetést végzi. Azért definiáltuk makróként, mert a kritikus végrehajtási útvonalon belül soron belül kifejtve (in-line) használjuk. Ahogyan a könyv későbbi részében látni fogjuk, az a tényező, mely korlátozza a hálózat teljesítőképességét, gyakran a protokoll végrehajtása, így ha az olyan egyszerű műveleteket, mint ez, makróként definiáljuk, ez nem befolyásolja a kód olvashatóságát, de javítja a teljesítőképességét.
A 3.11. ábrán levő deklarációk közül mindegyik a következőkben ismertetendő protokollban megtalálható. Helytakarékosságból és a kényelmesebb hivatkozás miatt, kiemeltük őket és együtt soroltuk fel, de fogalmilag magukkal a protokollokkal összevonva kell érteni őket. A C nyelvben ezt az összevonást a definíciók speciális fejrész fájlba (ebben az esetben a protocol.h) helyezésével, és a C előfeldolgozó #include lehetőségének használatával érjük el, így a definíciók a fordításkor a protokoll fájlokban megjelennek.
Bevezető példaként egy olyan protokollt veszünk, amelyik annyira egyszerű, amennyire csak lehet, ugyanis nem aggódik amiatt, hogy bármi gond felmerülhet. Az adatokat csak egy irányba továbbítjuk. Mind az adó, mind a vevő hálózati rétege mindig készen áll. A feldolgozási időtől eltekinthetünk. Végtelen pufferterület áll rendelkezésre. És a legjobb: az adatkapcsolati rétegek közötti kommunikációs csatorna sohasem rontja vagy veszíti el a kereteket. Ezt a gondosan valószerűtlen protokollt, melyre építkezünk, „utópiá”-nak fogjuk becézni, megvalósítása a 3.12. ábrán látható.
A protokoll két különálló eljárásból épül fel: egy küldő eljárásból és egy vevő eljárásból. A küldő a forrásgép adatkapcsolati rétegében fut, a vevő pedig a címzett gépében. Semmilyen sorszámozást vagy nyugtázást nem használ, így a MAX_SEQ-ra nincs szükség. Az egyetlen lehetséges eseményfajta a frame_arrival (azaz egy sértetlen keret érkezése).
A küldő egy végtelen while ciklus, amely olyan gyorsan pumpálja kifelé a vonalra az adatokat, ahogyan csak tudja. A ciklusmag három teendőből áll: szerezni egy csomagot a (mindig szolgálatkész) hálózati rétegtől, összerakni egy keretet az s változó segítségével, és útjára indítani a keretet. Ez a protokoll csak a keret info mezőjét használja, mivel a többi mező a hibajavításhoz és a forgalomszabályozáshoz kell, és itt nincsenek hibák vagy forgalomszabályozási megkötések.
A vevő ugyanilyen egyszerű. Kezdetben vár, hogy valami történjen. Az egyetlen lehetőség egy sértetlen keret érkezése. Végül is megérkezik a keret, és a wait_for_event eljárás visszatér az event-et frame_arrival-re állítva (melyet egyébként nem vesz figyelembe). A from_physical_layer meghívása eltávolítja a keretet a hardver pufferéből, és beleteszi az r változóba. Végül az adatrészt továbbadja a hálózati rétegnek, és az adatkapcsolati réteg dolga végeztével várakozni kezd a következő keretre: felfüggeszti magát, amíg a keret meg nem érkezik.
Az „utópia” protokoll a valóságtól elrugaszkodott, hiszen sem forgalomszabályozást, sem hibakezelést nem tartalmaz. A feldolgozás menete alapján nagyon hasonlít a nyugtázatlan, összeköttetés nélküli szolgáltatásra, mely e problémák megoldását felsőbb rétegekre bízza. Ennek ellenére egy nyugtázatlan, összeköttetés nélküli szolgáltatás is tipikusan tartalmaz legalább hibajelzést.
A következőkben azt a problémát fogjuk megoldani, hogy az adó a vevőt olyan sebességgel árassza el keretekkel, hogy az képtelen feldolgozni azokat. Mivel ez a probléma a gyakorlatban rendszerint jelen van, ezért nagyon fontos, hogy megelőzzük. A kommunikációs csatornát azonban továbbra is hibamentesnek feltételezzük, és az adatforgalom is még egyirányú.
Az egyik megoldás, hogy a vevőt olyan gyorsra tervezzük, hogy képes legyen szünet nélkül, szorosan egymást követő kereteket feldolgozni (vagy ami ezzel egyenértékű, hogy az adatkapcsolati réteget megfelelően lassúra tervezzük, hogy a vevő bírja a tempót). Ebben az esetben a vevőnek megfelelő méretű pufferekkel és feldolgozási kapacitással kell rendelkeznie, hogy vonali sebességgel tudjon futni, és a beérkező kereteket megfelelő sebességgel tudja a hálózati rétegnek továbbadni. Ez nyilvánvalóan egy „legrosszabb eset” típusú megoldás. A megoldás hozzárendelt célhardvert igényel, és meglehetősen erőforrás pazarló lehet, ha az adatkapcsolat kihasználtsága többnyire kicsi. Ráadásul a túl gyors adóval való foglalkozás problémáját áttolja máshová, jelen esetben a hálózati rétegre.
Egy sokkal általánosabb megoldás erre a problémára az, hogy a vevő visszacsatolást biztosít a küldő állomás felé. Miután a vevő átadta a csomagot a hálózati rétegének, visszaküld egy kis álkeretet, amely valójában engedély a küldő állomásnak arra, hogy továbbítsa a következő keretet. Miután az állomás egy keretet elküldött, a protokoll szerint várakoznia kell addig, amíg a kis ál- (azaz nyugta-) keret meg nem érkezik. A késleltetés ilyen módon történő megvalósítása jó példa egy forgalomszabályozási protokollra.
Azokat a protokollokat, melyekben a küldő egy keret elküldése után nyugtára vár, mielőtt továbbmenne, megáll-és-vár (stop-and-wait) protokollnak nevezik. A 3.13. ábrán a szimplex megáll-és-vár protokollra látható példa.
Bár ebben a példában az adatáramlás egyirányú: csak a küldőtől a vevő felé folyik, keretek mindkét irányban utaznak. Következésképpen a két adatkapcsolati réteg közötti kommunikációs csatornának alkalmasnak kell lennie kétirányú információátvitelre. A protokollból következik az áramlás irányának szigorú váltakozása: először a küldő állomás küld egy keretet, azután a vevő küld egy keretet, azután a küldő küld még egy keretet, azután a vevő még egyet és így tovább. Ehhez egy fél-duplex fizikai csatorna is megfelelő.
Úgy, mint az 1. protokollban, a küldő állomás itt is azzal kezdi, hogy lekér egy csomagot a hálózati rétegtől, ennek segítségével összeállít egy keretet, és útjára küldi. Csakhogy most – nem úgy, mint az 1. protokollban – a küldőnek várakoznia kell, amíg egy nyugtakeret meg nem érkezik, mielőtt újabb ciklusba kezdene, és lekérné a következő csomagot a hálózati rétegtől. A küldő adatkapcsolati rétegnek meg sem kell néznie az érkező keretet, mivel úgyis csak egy lehetőség van, hiszen a bejövő keret mindig egy nyugta.
Az egyetlen különbség a receiver1 és a receiver2 között az, hogy miután a csomagot kézbesítette a hálózati rétegnek, a receiver2 egy nyugtakeretet küld vissza a küldő állomásnak a várakozó hurokba való újbóli belépés előtt. Mivel a küldőnek csak a keret visszaérkezése fontos, az nem, hogy mit tartalmaz, a vevőnek nem szükséges semmilyen különleges információt beletenni.
Most vegyük figyelembe azt a normális esetet, hogy a kommunikációs csatorna hibázhat. A keretek megsérülhetnek vagy teljesen elveszhetnek. Azt azért feltételezzük, hogy ha egy keret megsérül az átvitel során, a vevő hardvere ezt felismeri, amikor kiszámítja az ellenőrző összeget. Ha a keret úgy sérül meg, hogy az ellenőrző összeg ennek ellenére helyes – ami rendkívül ritkán fordul elő –, ez a protokoll (és az összes többi is) hibázhat (azaz egy hibás keret kézbesítődik a hálózati rétegnek).
Első pillantásra úgy tűnhet, hogy a 2. protokoll egy kis változtatással jó lenne: be kellene építeni egy időzítőt. A küldő állomás küldhetne egy keretet, de a vevő csak akkor küldene nyugtakeretet, ha az adat helyesen érkezett meg. Ha sérült keret érkezik a vevőhöz, el kellene dobni. Egy idő után a küldőnek lejárna az időzítője, és újra elküldené a keretet. Ezt a folyamatot addig ismételnénk, míg végül a keret sértetlenül megérkezik.
A fenti sémának van egy végzetesen gyenge pontja. Gondolkodjunk a problémáról, és mielőtt továbbmegyünk, próbáljuk kitalálni, hogy mi a baj!
Ahhoz, hogy lássuk, mi lehet a baj, emlékezzünk rá, hogy az adatkapcsolati réteg folyamatainak a kötelessége, hogy hibamentes, átlátszó kommunikációt biztosítsanak a hálózati rétegek folyamatai között. Az A gép hálózati rétege egy csomagsorozatot ad az adatkapcsolati rétegnek, amelynek biztosítania kell, hogy megegyező csomagsorozatot kapjon a hálózati réteg a B gépen az adatkapcsolati rétegtől. Különös tekintettel arra, hogy a B gép hálózati rétegének nincs módja tudomást szerezni arról, hogy egy csomag elveszett vagy megkettőződött, az adatkapcsolati rétegnek kell garantálnia, hogy az átviteli hibák semmilyen kombinációja – tekintet nélkül arra, hogy milyen ritkán fordulnak elő – se okozhassa, hogy egy keret duplán érkezzen meg a hálózati réteghez.
Gondoljuk át a következő eseménysort:
Az A hálózati rétege átadja az 1. csomagot az adatkapcsolati rétegnek. A csomag rendben megérkezik B-hez, és átadódik B hálózati rétegének. B visszaküld egy nyugtakeretet A-nak.
A nyugtakeret teljesen elveszik. Egyáltalán nem érkezik meg soha. Az élet sokkal egyszerűbb lenne, ha a csatorna csak az adatkereteket tenné tönkre és veszítené el, a vezérlőkereteket nem, de szomorúan azt kell mondanunk, hogy a csatorna nem nagyon tud különbséget tenni.
A adatkapcsolati rétegének végül lejár az időzítője. Mivel nem érkezett nyugta, azt feltételezi (hibásan), hogy az adatkerete elveszett vagy megsérült, így újra elküldi az 1. csomagot tartalmazó keretet.
A megkettőzött keret szintén tökéletesen megérkezik B adatkapcsolati rétegéhez, az pedig – minden rossz szándék nélkül – továbbadja az ottani hálózati rétegnek. Ha A egy állományt küld B-nek, az állomány egy része megkettőződik (azaz B által az állományról készített másolat hibás lesz, és a hibát senki nem jelezte ki). Egyszóval, a protokoll hibázni fog.
Nyilvánvalóan arra van szükség, hogy valahogyan képessé tegyük a vevőt arra, hogy meg tudja különböztetni az először látott kereteket az újraküldöttektől. Ennek kézenfekvő módja az, hogy az adó egy sorszámot tesz minden elküldött keret fejlécébe. Ekkor a vevő ellenőrizheti minden érkező keret sorszámát, hogy megállapítsa, hogy új keret érkezett-e, vagy csak egy megkettőzött, amit el kell dobni.
Mivel a protokollnak hibamentesnek kell lennie, és a sorszámmezőnek pedig a fejlécben elegendően kicsinek kell lennie ahhoz, hogy az adatkapcsolat hatékonyan működjön, felmerül a kérdés: hány bit szükséges a fejlécben a sorszám tárolására? A sorszám részére a fejlécben a protokolltól függően lehet 1 bit, néhány bit, 1 bájt vagy akár több bájt. A lényeg mindenesetre az, hogy a sorszámmezőnek elegendően nagynak kell lennie ahhoz, hogy a protokoll hibátlanul működjön.
A protokollban az egyetlen nem egyértelmű helyzet az m. és a közvetlenül azt követő, . keret között áll fenn. Ha az m. keret elveszett vagy megsérült, a vevő nem fogja nyugtázni, így a küldő tovább próbálkozik az elküldésével. Ha egyszer hibátlanul megérkezik, a vevő visszaküld egy nyugtát a küldő állomásnak. Ez az a pont, ahol a potenciális hiba fellép. Attól függően, hogy a nyugtakeret rendben visszajut a küldő állomáshoz vagy sem, az adó az
. vagy az m. keretet próbálja meg elküldeni.
Az esemény, mely kiváltja az . keret elküldését, az m. keretre vonatkozó nyugta megérkezése. Ez azonban azt is magában foglalja, hogy az
. keret is rendben megérkezett, továbbá az
. keret nyugtája is megérkezett a küldő állomáshoz, másképp a küldő nem is fogott volna hozzá az m. keret adásához, és pláne békén hagyta volna az
. keretet is. Következésképpen az egyetlen bizonytalanság egy keret és az azt közvetlenül megelőző vagy azt követő keret között van, nem pedig a keretet megelőző és a keretet követő között.
A fentiek miatt egy 1 bites (0 vagy 1) sorszám elegendő. A vevő minden pillanatban pontosan tudja, hogy milyen sorszámot vár. Amikor egy helyes sorszámot tartalmazó keret érkezik, azt elfogadja, és átadja a hálózati rétegnek, majd a várt sorszámot modulo 2 szerint lépteti (azaz a 0-ból 1 lesz, az 1-ből pedig 0). A vevő minden hibás sorszámot tartalmazó beérkező keretet – másolatnak tekintve – eldob. Az utolsó érvényes nyugtát azonban újra elküldi a vevő, így az adó tudni fogja, hogy a keret megérkezett.
A 3.14. ábrán egy példa látható ilyenfajta protokollra. Azokat a protokollokat, amelyekben a küldő állomás pozitív nyugtára vár, mielőtt továbblépne a következő adategységre, gyakran PAR-nak (Positive Acknowledgement with Retransmission – pozitív nyugtázás újraküldéssel) vagy ARQ-nak (Automatic Repeat reQuest – automatikus ismétléskérés) nevezik. A 2. protokollhoz hasonlóan, ez is csak egy irányba továbbítja az adatokat.
A 3. protokoll abban különbözik elődeitől, hogy az adónak és a vevőnek is van olyan változója, amelynek emlékszik az értékére, mialatt az adatkapcsolati réteg várakozási állapotban van. Az adó a következő elküldendő keret sorszámát tárolja a next_frame_to_send-ben; a vevő a következő venni kívánt keret sorszámát a frame_expected-ben. Mindkét eljárásnak van egy rövid inicializációs fázisa, mielőtt belép a végtelen ciklusba.
Az adóállomás egy keret továbbítása után elindít egy időzítőt. Ha már ment az időzítő, akkor nullázódik, hogy megint egy teljes időzítési intervallum álljon rendelkezésre. Az időzítési intervallumot úgy kell megválasztani, hogy elég ideje legyen a keretnek eljutni a vevőhöz, a vevőnek feldolgozni azt a legrosszabb esetben is, és a nyugtakeretnek visszaérni az adóállomáshoz. Csak ha ez az idő eltelt, akkor feltételezhetjük biztonsággal, hogy vagy a továbbított keret vagy annak nyugtája elveszett, és csak ekkor küldhetünk egy másik példányt belőle. Ha az időzítési intervallumok túl rövidek, akkor a küldő felesleges kereteket is elküld. Ezek a többletforgalmat jelentő keretek nem javítanak a protokoll helyességén, viszont rontják teljesítményét.
Egy keret továbbítása és az időzítő elindítása után az adóállomás várakozni kezd, hátha történik valami izgalmas. Három lehetőség van: egy nyugtakeret érkezik sértetlenül, egy sérült nyugtakeret támolyog be vagy az időzítő lejár. Ha egy érvényes nyugta jön be, az adó lekéri a következő csomagot a hálózati rétegétől, és beleteszi a pufferbe, felülírva az előző csomagot, valamint lépteti a sorszámot is. Ha sérült keret vagy egyáltalán semmi sem érkezik, sem a puffer, sem a sorszám nem változik, így a következő ciklusban egy újabb példányát tudjuk elküldeni az előbbi keretnek. Mindegyik esetben a puffer tartalmát (mind a következő csomagnál, mind a másolatnál) küldjük el.
Amikor egy érvényes keret érkezik a vevőhöz, megvizsgáljuk a sorszámát, hogy meglássuk, duplikátum-e. Ha nem az, akkor elfogadjuk, továbbadjuk a hálózati rétegnek, és egy nyugtát küldünk. A duplikátumokat és a sérült kereteket nem adjuk át a hálózati rétegnek, viszont ezek után is küldünk nyugtát a legutoljára beérkezett hibátlan keretről, hogy az adó mihamarabb lehetőséget kapjon a továbbhaladásra, a következő csomag továbbítására, vagy a másolat elküldésére.