A szállítási szolgáltatást egy, a szállítási entitások között használt szállítási protokoll valósítja meg. Némely tekintetben a szállítási protokollok az adatkapcsolati protokollokra emlékeztetnek, amelyeket a 3. fejezetben tanulmányoztuk részletesen. Mindkettőnek többek között hibakezelést, sorszámozást és forgalomszabályozást kell végeznie.
Ugyanakkor jelentős eltérések is vannak a kettő között. A különbségek fő oka abban az alapvetően eltérő működési környezetben rejlik, melyben a két protokoll működik. Ezt a 6.7. ábrán láthatjuk. Az adatkapcsolati rétegben a két csomópont közvetlenül egy fizikai csatornán keresztül kommunikál, míg a szállítási rétegben a fizikai csatorna helyett egy egész hálózat szerepel. Ez a különbség fontos hatással van a protokollokra.
Egyrészt kétpontos (point-to-point) adatkapcsolatokon, mint amilyen a rézvezeték vagy az optikai szál, az útválasztónak nem kell kijelölni, hogy melyik másik útválasztóval kíván kommunikálni – minden kimenő vonal egyértelműen azonosít egy adott útválasztót. A szállítási rétegben kötelező a cél explicit címzése.
Másrészt, amikor egy folyamat a 6.7.(a) ábrán látható vezetéken összeköttetést akar létesíteni, egyszerű dolga van: a másik végpont mindig jelen van (hacsak el nem romlott, amikor is nincs jelen). Akármelyik eset következik is be, nincs sok tennivaló. Még vezeték nélküli adatkapcsolatokon sincs komolyabb különbség: elég az üzenet egyszerű elküldése is ahhoz, hogy minden címzett állomás megkapja. Ha az üzenetet nem nyugtázták valamilyen hiba miatt, akkor is újra lehet küldeni. A szállítási rétegben, mint látni fogjuk, a kezdeti összeköttetés-létesítés jóval bonyolultabb.
Egy másik nagyon bosszantó különbség az adatkapcsolati és a szállítási réteg között a hálózat potenciális adattároló képessége. Ha egy útválasztó elküld egy csomagot egy adatkapcsolaton, az vagy megérkezik, vagy elvész, de nem fog bolyongani egy darabig, elrejtőzni a világ egy távoli sarkába, majd hirtelen, egy váratlan pillanatban, a jóval később küldött csomagok után felbukkanni. Ha a hálózat a belső forgalmat datagramokkal valósítja meg, amelyek forgalomirányítása külön-külön történik, nem elhanyagolható annak a valószínűsége, hogy a csomag tesz egy festői körutazást, majd késve megérkezik, az elvárt sorrendből kilógva, vagy akár többszöröződve. A hálózat csomagkésleltető és többszöröző képességének következménye néha katasztrofális lehet, és speciális protokollok használatát teszi szükségessé, hogy helyesen tudjunk információt továbbítani.
Az utolsó különbség az adatkapcsolati és a szállítási réteg között inkább mennyiségi, mint minőségi eltérés. Pufferelés és forgalomszabályozás mindkét esetben szükséges, de a szállítási rétegben jelenlevő nagy és változó számú összeköttetés, miközben ezek egymással versengenek, hullámzó sávszélességet igényelnek, s ez eltér attól, mint amit az adatkapcsolati rétegben használtunk. Néhány, a 3. fejezetben tárgyalt protokoll rögzített számú puffert rendel minden vonalhoz, így a beérkező keretek számára mindig van szabad puffer. A szállítási rétegben kezelendő nagyszámú összeköttetés és változó sávszélesség láttán máris kevésbé vonzó ötlet mindegyiknek számos saját puffert lefoglalni. A következő alfejezetekben többek között ezekkel és más fontos problémákkal fogunk foglalkozni.
Amikor egy alkalmazási folyamat (például egy felhasználói folyamat) egy távoli alkalmazási folyamattal akar összeköttetést létrehozni, meg kell jelölnie, hogy melyik folyamattal akar kapcsolatba lépni. (Az összeköttetés nélküli szállítási szolgáltatásban is megvan ugyanez a probléma: kinek kell elküldeni az egyes üzeneteket?) Az általánosan használt módszer az, hogy külön szállítási címeket definiálunk az egyes folyamatok részére, amelyeken várhatják az összeköttetési kéréseket. Az interneten ezeket a végpontokat általában portoknak hívják. Mi a legáltalánosabb kifejezést, a TSAP-t (Transport Service Access Point – szállítási szolgáltatás hozzáférési pont) fogjuk használni, hogy egy kitüntetett végpontra utaljunk a szállítási rétegben. Az ezekkel rokon végpontokat a hálózati rétegben (vagyis a hálózati rétegbeli címeket) ugyanígy NSAP-nak (Network SAP – hálózati szolgáltatás hozzáférési pont) hívják. Az IP-címek például NSAP-k.
A 6.8. ábra az NSAP, a TSAP és a szállítási összeköttetés összefüggéseit mutatja be. Az alkalmazási folyamatoknak mind a kliens, mind a szervergépen egy helyi TSAP-re kell rákapcsolódniuk ahhoz, hogy egy távoli TSAP-vel összeköttetést tudjanak létrehozni. Ezek az összeköttetések az ábrán is látható módon mindkét hoszt NSAP-jén is keresztülhaladnak. Egyes hálózatokban minden számítógép csak egyetlen NSAP-vel rendelkezik, így szükség van egy olyan módszerre, amellyel több szállítási végpontot lehet megkülönböztetni egy NSAP-n belül. Erre a célra alkalmazzák a TSAP-ket.
A szállítási összeköttetés egy lehetséges forgatókönyve:
A 2. hoszton található levelezőszerver folyamat a 25-ös TSAP-hez kapcsolódik és bejövő hívásokra várakozik. Az, hogy egy folyamat hogyan tud egy TSAP-re rákapcsolódni, teljesen kívül esik a hálózat modelljén és kizárólag a helyi operációs rendszertől függ. A kapcsolódás történhet például egy a mi listen-ünkhöz hasonló hívással.
Az 1. hoszt egyik alkalmazási folyamata szeretne egy elektronikus levelet küldeni, ezért saját magát az 1208-as TSAP-hez kapcsolja, és kiad egy connect kérést. A kérés tartalmazza forrásként az 1208-as TSAP-t az 1. hoszton és célként az 25-ös TSAP-t a 2. hoszton. Ez végül egy szállítási összeköttetés kiépüléséhez vezet az alkalmazási folyamat és a szerver között.
Az alkalmazási folyamat ezután elküldi az elektronikus levelet.
A levelezőszerver válaszban közli, hogy a levelet kézbesíteni fogja.
A szállítási összeköttetést ezután lebontják.
Eközben persze a 2. hoszton más szerverek is várhatnak bejövő összeköttetési kéréseket. Ezek más TSAP-kre csatlakoznak, de az ezek számára érkező kérések is ugyanazon az NSAP-n keresztül érkeznek meg.
Az itt lefestett kép nagyszerű, azonban egyetlen apró kérdést elegánsan a szőnyeg alá söpörtünk: Honnan tudja az 1. hoszt alkalmazási folyamata, hogy a levelezőszerver a 25-ös TSAP-hoz kapcsolódik? Az egyik lehetőség az, hogy a levelezőszerver már évek óta a 25-ös TSAP-hoz csatlakozik, és ezt szép lassan a hálózat minden felhasználója megtanulta. Ebben a modellben a szolgáltatásoknak stabil TSAP-címük van, amelyeket közismert helyeken megtalálható állományokban sorolnak fel. Ilyen például a UNIX-rendszerek /etc/services állománya, amely felsorolja, hogy mely szerverek mely portokhoz vannak állandó jelleggel hozzárendelve, beleértve azt az egyszerű tényt is, hogy a levelezőszerver a 25-ös TCP-porton található.
Habár a stabil TSAP-címek jól működnek kisszámú olyan szolgáltatás esetén, amelyek sohasem változnak (például webszerverek), a felhasználói folyamatok (általános értelemben) legtöbbször más olyan felhasználói folyamatokkal akarnak beszélgetni, amelyek nem rendelkeznek előre ismert TSAP-címmel, vagy csak rövid ideig létezik címük.
Az ilyen esetek kezelésére gyakran egy alternatív módszert alkalmaznak. Ebben az esetben létezik egy speciális folyamat, az ún. portszolgáltató (portmapper). Ha a felhasználó egy szolgáltatás nevéhez (például „BitTorrent”) keresi a TSAP címét, akkor összeköttetést létesít a portszolgáltató felé (ami egy jól ismert TSAP-címen található). A felhasználó aztán elküld egy üzenetet, ami a szolgáltatás nevét tartalmazza, és a portszolgáltató visszaküldi a szolgáltatás TSAP-címét. A felhasználó végül megszakítja a kapcsolatot a portszolgáltatóval, és kiépít egy újat a kapott TSAP-cím felé.
Ebben a modellben egy új szolgáltatás létesítésekor a szolgáltatás nevével (rendszerint egy ASCII-füzér) és TSAP-címével be kell jelentkezni a portszolgáltatónál. A portszolgáltató a kapott információt feljegyzi belső adatbázisába, hogy a későbbi lekéréseknél már tudja a választ.
A portszolgáltató feladata analóg a tudakozóéval a távbeszélőrendszerekben – nevekről számokra történő leképezést valósít meg. Éppúgy, mint a távbeszélőrendszerekben, lényeges, hogy a portszolgáltató jól ismert TSAP-címe valóban mindenki által ismert legyen. Ha nem tudjuk a tudakozó telefonszámát, nem lehet fölhívni a tudakozót, hogy megkérdezzük. Ha azt gondolnánk, hogy a tudakozó telefonszáma nyilvánvaló, próbáljuk meg valamikor fölhívni egy másik ország tudakozóját.
A szerverfolyamatok nagy részét meglehetősen ritkán használják, ezért pazarló azokhoz folyamatosan egy kitüntetett TSAP-címet rendelni, majd egész nap futtatni. A 6.9. ábrán egy alternatív megoldás leegyszerűsített formája látható, amely kezdeti összeköttetés-protokollként (initial connection protocol) ismert. Ahelyett, hogy az összes lehetséges szerver egy jól ismert TSAP-t figyelne, minden olyan gépnek van egy különleges folyamatszervere (process server), amely szolgáltatásokat akar felkínálni a távoli felhasználóknak. A folyamatszerver a kevésbé sűrűn használt szerverek megbízottjaként (proxy) működik. UNIX-rendszereken az ilyen szerver elnevezése inetd. Több portot figyel egyszerre, összeköttetési kérésekre várva. A szolgáltatásokat használni kívánók azzal kezdik a tevékenységüket, hogy kiadnak egy connect kérést, amelyben megjelölik, hogy melyik TSAP-címen van az általuk igényelt szolgáltatás. Ha itt nem vár rájuk szerver, akkor a folyamatszerverrel kerülnek kapcsolatba, ahogyan az a 6.9.(a) ábrán is látható.
Miután a folyamatszolgáltató megkapja a beérkező kérést, létrehozza a megfelelő szervert, aminek átadja a felhasználóval már fennálló összeköttetését. A szerver elvégzi a kért feladatot, miközben a folyamatszolgáltató visszatér, és továbbra is kérésekre várakozik. Ezt mutatja be a 6.9.(b) ábra. Ez a módszer csak akkor alkalmazható, ha a szervereket elég szükség esetén létrehozni.
6.9. ábra - Az 1. hoszton futó felhasználói folyamat és a 2. gépen futó levelezőszerver közötti összeköttetés felépítése folyamatszerver segítségével
Az összeköttetés felépítése egyszerűnek tűnik, de valójában meglepően trükkös folyamat. Első pillantásra elegendő lenne, hogy az egyik szállítási entitás CONNECTION REQUEST szegmenst küldene a másik félnek, és CONNECTION ACCEPT válaszra várna. A probléma akkor merül fel, ha a hálózatban a csomagok elvesznek, késleltetést szenvednek, megsérülnek, vagy akár megkettőződnek. Ezek a lehetőségek komoly nehézségeket okoznak.
Képzeljük el, hogy egy hálózat annyira zsúfolt, hogy a nyugtázások nemigen érkeznek vissza időben, minden csomag időtúllépéssel érkezik meg, a csomagokat kétszer-háromszor újraküldik. Tegyük föl, hogy a hálózat belül datagramokat használ, és minden csomag különböző útvonalon halad. Néhány csomag dugóba kerülhet a hálózaton, és hosszú ideig nem érkezik meg. A hálózat tehát jelentős ideig tárolja őket, majd jóval később előbukkannak, amikor már a küldő azt gondolná, hogy elvesztek.
A létező legrosszabb rémálom a következő: egy felhasználó összeköttetést létesít egy bankkal, és üzenetet küld azzal a megbízással, hogy a bank utaljon át nagy pénzösszeget egy nem igazán megbízható személy számlájára. Szerencsétlenségére a csomagok éppen úgy döntenek, hogy egy kellemes körutazást tesznek a hálózat legeldugottabb sarkába. A küldő időzítője lejár, és elküldi újra az összes csomagot. Ez esetben a csomagok a legrövidebb úton mennek, és gyors kézbesítés után a küldő bontja az összeköttetést.
Sajnos előbb vagy utóbb az előzőleg küldött csomagok abbahagyják a körutazást, és megérkeznek a célhoz, méghozzá sorrendben, megkérve a bankot, hogy létesítsen egy új összeköttetést, és utalja át a pénzt (megint). A bank semmilyen módon nem tudja megállapítani, hogy ezek másolatok. Azt feltételezi, hogy ez egy második, az előzőtől független tranzakció, és megint átutalja a pénzt.
Bár az előbbi eset elég valószínűtlen, de a lényeg, hogy a protokollokat úgy kell tervezni, hogy minden esetben helyesek legyenek. Jó hálózati teljesítőképesség eléréséhez elegendő csak a gyakori eseteket hatékonyan implementálni, de hiba nélküli működéshez a protokollnak meg kell birkóznia a valószínűtlen esetekkel is. Ha nem, akkor sikerült építenünk egy olyan hálózatot, amely figyelmeztető jelzés nélkül hibázik, ha a körülmények mostohábbra fordulnak.
Jelen alfejezetben a késleltetett kettőzések problémáját fogjuk tanulmányozni. Különös figyelmet szentelünk az összeköttetések megbízható módon történő létesítésére kifejlesztett algoritmusokra, hogy a föntiekhez hasonló rémálmok ne válhassanak valóra. A bökkenő az, hogy a késleltetett kettőződéseket új csomagoknak véljük. A csomagok kettőződését és késleltetését nem tudjuk megakadályozni. Ha azonban ez történik, akkor az ilyen kettőzött csomagokat el kell dobni, és nem szabad új csomagként feldolgozni.
A problémára többféle ellenszer létezik, de egyik sem teljesen kielégítő. Az egyik módszer azt mondja, hogy használjunk eldobható szállítási címeket. Ebben a megközelítésben minden esetben, amikor egy szállítási címre van szükség, újat generálunk. Az összeköttetés lebontása után a régi címet elvetjük, és soha nem használjuk újra. A késéssel érkező kettőződések így soha nem kerülhetnek egy szállítási folyamathoz, és így nem okozhatnak kárt. Ez a megoldás azonban még bonyolultabbá teszi az összeköttetést az előbbi folyamattal.
Egy másik lehetőség minden összeköttetésnek egy olyan egyedi összeköttetés-azonosítót adni (egy sorszámot, ami minden újabb összeköttetés létesítéskor egyesével nő), amelyet a kezdeményező fél generál és minden szegmensbe (beleértve az összeköttetést kérőt is) beletesz. Az összeköttetés lebontása után minden szállítási entitás frissíti a befejeződött összeköttetések tábláját, amelynek bejegyzései (társ szállítási entitás, összeköttetés sorszám) párokból állnak. Minden újabb összeköttetés-kéréskor ellenőrizhető, hogy az nem egy régi összeköttetéshez tartozik-e.
Sajnos ennek a módszernek van egy alapvető hibája: minden szállítási entitásnak határozatlan ideig történeti információt kell tárolnia. Ráadásul ezt az információt mind a forrás-, mind a célgépen tárolni kell, különben ha egy gép összeomlik, és memóriatartalmát elveszti, többé nem tudja, hogy mely összeköttetés-azonosítókat használta már.
Ehelyett más megközelítést kell használnunk a probléma leegyszerűsítésére. Ahelyett, hogy egy csomagot örök időre életben hagynánk a hálózatban, ki kell fejlesztenünk egy olyan mechanizmust, amely az öreg és még mindig bolyongó csomagokat kiirtja. Ezzel a megszorítással a probléma valamivel kezelhetőbbé válik.
A csomagok élettartama ismert maximumra korlátozható az alábbi egyik (vagy több) módszerrel:
Korlátozott hálózat tervezése.
Ugrásszámláló (hop counter) alkalmazása a csomagokban.
A csomagok időbélyeggel való ellátása.
Az első módszerbe minden olyan megoldás beletartozik, amely megelőzi, hogy a csomagok hurokba kerüljenek, és emellett valahogyan a torlódási késleltetést is korlátozni tudja a (pillanatnyilag ismert) leghosszabb lehetséges útvonalon. Ez a módszer meglehetősen nehézkes, hiszen az összekapcsolt hálózatok kiterjedése egyetlen várostól kezdve akár nemzetközi méreteket is ölthet. A második módszer abból áll, hogy az ugrásszámot valamilyen alkalmas értékre állítják be, és minden továbbadás alkalmával csökkentik. A hálózati protokoll minden olyan keretet egyszerűen eldob, amelynek az ugrásszámlálója nullára csökkent. A harmadik módszerhez arra van szükség, hogy minden csomagot ellássanak a keletkezésének idejével, valamint az útválasztóknak meg kell egyezniük abban, hogy eldobnak minden olyan csomagot, amely régebbi a közösen meghatározott legnagyobb lehetséges élettartamnál. Ez utóbbi módszer alkalmazásához az útválasztók óráit szinkronizálni kell, amely maga sem egyszerű feladat, és az ugrásszámláló is gyakorlatilag egy jó közelítése az élettartamnak.
Gyakorlatban nem elég azt biztosítanunk, hogy egy csomag halott, hanem ennek igaznak kell lenni minden rá vonatkozó nyugtára is, ezért bevezetjük a T időtartamot, ami a valódi maximális csomagélettartam kis egész számú többszöröse. A maximális csomagélettartam egy adott hálózatra közel állandó érték; az interneten ezt az értéket önkényesen valahová 120 másodperc köré állították be. A szorzó protokollfüggő, egyszerűen azért kell, hogy a T még hosszabb legyen. A csomag elküldését követően T időnyi várakozás után biztosak lehetünk abban, hogy a csomag már minden nyom nélkül eltűnt, és sem a csomag, sem a nyugtázások nem fognak hirtelen előtűnni a ködből, és további bonyodalmakat okozni.
Korlátozott élettartamú csomagokat felhasználva bolondbiztos eljárást lehet kifejleszteni az összeköttetés biztonságos felépítésére. Az alább ismertetett módszer Tomlinson [1975] nevéhez fűződik, melyet Sunshine és Dalal [1978] tovább finomított. Ennek különböző változatait széles körben alkalmazzák a gyakorlatban, így a TCP-ben is.
Az alapötlet az, hogy a forrás felcímkézi a szegmenseket egy sorszámmal, mely sorszámot nem használjuk újra T másodpercen belül. A T periódus és a másodpercenkénti csomagszám meghatározza a sorszámok méretét. Így egy adott sorszámmal csupán egyetlen csomag lehet kinn akármelyik időpillanatban. Másolatok még mindig létezhetnek, azonban azokat a célállomáson el kell dobni. Így már nem áll fenn az a probléma, hogy egy késleltetett másolat ugyanolyan sorszámmal érkezik, és a cél azt elfogadja.
Annak a problémának megkerülésére, hogy egy gép egy összeomlás után nem tudja megállapítani, hogy hol is tartott, egy lehetséges megoldás, hogy a szállítási entitás a helyreállás után T másodpercig tétlenül várakozzon. Ez idő alatt minden régi szegmens kihal, és a küldő elölről kezdheti tetszőleges sorszámmal. Mindazonáltal egy nagyobb hálózatban a T időtartam olyan nagy lehet, hogy ez a megoldás nem túl vonzó.
Ehelyett Tomlinson azt javasolta, hogy minden hosztot időt mutató órával lássanak el. A különböző hosztokon levő óráknak nem szükséges szinkronban járniuk. Minden óra egy bináris számlálóval valósítható meg, ami egységes időközönként növeli értékét. Ezenkívül a számlálóban levő bitek számának egyenlőnek vagy nagyobbnak kell lennie, mint a sorszámokban levő bitek száma. Végül, ami a legfontosabb, a hoszt meghibásodásakor is tovább kell járnia az órának.
Az összeköttetés felépítésekor az óra értékének alsó k bitje alkotja a kezdeti (szintén k bites) sorszámot. Így, eltérően a 3. fejezetben leírt protokolloktól, minden összeköttetés más sorszámmal kezdi számozni a szegmenseit. A használt tartománynak olyan nagynak kell lennie, hogy mire a sorszámok körbeérnek, az azonos sorszámú, régi szegmensek már rég eltűnjenek. Az idő és a kezdeti sorszámok közti lineáris összefüggést mutatja a 6.10.(a) ábra. A tiltott tartomány (forbidden region) mutatja azokat az időpontokat, amelyekre egy adott szegmenssorszámot nem szabad használni. Ha a tiltott tartományban található sorszámmal küldünk el egy szegmenst, akkor abban az esetben, ha az késleltetést szenved, előfordulhat, hogy a vevő egy másik, valamivel később útjára bocsátott csomagnak fogja hinni. Például ha a hoszt összeomlik, majd újraindul a 70. másodpercben, akkor az óra alapján fog kezdeti sorszámot választani, nem pedig a tiltott tartományban található alacsonyabb sorszámot.
Ha valamikor a szállítási entitások már megegyeztek a kezdeti sorszámban, bármilyen csúszóablakos protokoll használható forgalomszabályozásra. Ez a csúszóablakos protokoll megtalálja és eldobja a már elfogadott csomagok másolatait. Valóságban a kezdeti sorszám időfüggését jelző vastag görbe nem igazán egyenes, hanem lépcsőzött, mivel az óra értéke diszkrét lépésekben növekszik. Az egyszerűség kedvéért ezt a részletet elhanyagoljuk.
Két szempontot is figyelembe kell vennünk, hogy a csomagok sorszámai ne kerülhessenek a tiltott tartományba. A protokoll kétféleképpen kerülhet bajba. Ha egy hoszt túl gyorsan küld túl sok adatot egy frissen létesített összeköttetésen, az aktuális sorszám–idő görbe meredekebben emelkedhet, mint a kezdeti sorszám–idő görbe, és így a sorszámok a tiltott tartományba kerülnek. Ennek elkerülésére a maximális adatsebesség bármelyik összeköttetésen óraütésenként egy szegmens lehet. Így az összeomlás utáni újraindításkor egy új összeköttetés megnyitása előtt a szállítási entitásnak egy óraütésig kell várni, hogy elkerülje egy sorszám ismételt használatát. Mindkét probléma rövidebb óraütem (1 mikroszekundum vagy kevesebb) használatát teszi indokolttá. Az óra azonban nem is lehet túl gyors a sorszámokhoz képest. C óraütem feltételezésével és S maximális sorszámmérettel be kell tartanunk az egyenlőtlenséget, hogy a sorszámok nehogy túl gyorsan átforduljanak.
Sajnos, nemcsak úgy lehet bajba kerülni, hogy túl gyors adással alulról kerül a szállítási entitás a tiltott tartományba. A 6.10.(b) ábrán látszik, hogy tetszőleges, az óra sebességénél kisebb adási sebességnél a tényleges idő–sorszám görbe végül balról fog belépni a tiltott tartományba, amint a sorszámok átfordulnak. Minél meredekebb az említett görbe, annál később következik be ez az esemény. A probléma elkerülése érdekében egy összeköttetés sorszámainak növekedési sebességét meg kell szabnunk (vagy az összeköttetés élettartamát kell korlátoznunk).
Az óraalapú módszer megoldja az adatszegmensek késleltetett kettőzési problémáit, de egy akadály még mindig nehezíti a használatát az összeköttetés felépítése során. Mivel általában egy összeköttetésen belül nem jegyezzük meg a sorszámokat, ezért még mindig nem tudjuk megmondani, hogy egy, a kezdeti sorszámot tartalmazó CONNECTION REQUEST szegmens másolat vagy nem. A probléma az összeköttetés ideje alatt nem jelentkezik, hiszen a csúszóablakos protokoll az aktuális sorszámra emlékszik.
Ennek a problémának megoldására vezette be Tomlinson [1975] a „háromutas kézfogás” (three-way handshake) módszert. Ez az összeköttetés-létesítési protokoll magába foglal egy ellenőrzést, hogy az összeköttetés felépítési kérés érvényes vagy sem. Egy normális összeköttetés-létesítési eljárást, amikor az 1. hoszt kezdeményez, láthatunk a 6.11.(a) ábrán. Az 1. hoszt kiválaszt egy x sorszámot, és egy CONNECTION REQUEST szegmensben elküldi a 2. hosztnak. Az egy ACK szegmenssel nyugtázza x értékét, és bejelenti saját y kezdeti sorszámát. Végül az 1. hoszt jóváhagyja a 2. hoszt által választott kezdeti sorszámot az első általa küldött adatszegmensben.
6.11. ábra - Három forgatókönyv a „háromutas kézfogás” protokollal történő összeköttetés-létesítésre. CR a CONNECTION REQUEST rövidítése. (a) Normális működés. (b) Régi kettőzött CR bukkan elő. (c) Kettőzött CR és kettőzött ACK esete
Lássuk tehát, hogy működik a „háromutas kézfogás” protokollja késleltetett kettőzött vezérlőszegmensek esetén. A 6.11.(b) ábrán az első szegmens egy régebbi összeköttetés késleltetett kettőzött CONNECTION REQUEST üzenete. Ez a 2. hoszthoz anélkül érkezik meg, hogy az 1. hoszt tudna róla. A 2. hoszt válaszul egy ACK szegmenst küld, gyakorlatilag azt ellenőrizve, hogy partnere tényleg új összeköttetést akar-e létesíteni. Mivel az 1. hoszt elutasító választ küld, a 2. hoszt rájön, hogy egy késleltetett kettőzés csapta be, és felhagy az összeköttetés-létesítéssel. Ily módon egy késleltetett kettőzött szegmens nem okoz kárt.
A legrosszabb eset az, amikor mind egy megkésett CONNECTION REQUEST, mind egy ACK kering még valahol a hálózatban. Ezt az esetet mutatja a 6.11.(c) ábra. Az előző példához hasonlóan a 2. hoszt itt is egy megkésett CONNECTION REQUEST-et kap, amelyre válaszol. Itt nagyon fontos azt figyelembe venni, hogy a 2. hoszt kezdetben az y-t javasolta a 2. hoszttól az 1. hoszt felé menő forgalom sorszámának, annak a ténynek a teljesen biztos tudatában, hogy olyan szegmens már nem létezik, amely az y sorszámot tartalmazza, és ezekre vonatkozó nyugták sincsenek már úton. Amikor a második késve érkező szegmens megérkezik a 2. hoszthoz, az a tény, hogy ez a z-t nyugtázza és nem az y-t, elárulja a 2. hoszt számára, hogy ez is egy régi kettőzött szegmens. A dolog legfontosabb tanulsága az, hogy a régi szegmensek semmilyen kombinációja sem okozhatja a protokoll hibázását, vagyis sosem hozhat létre olyan összeköttetéseket véletlenül, amelyeket senki nem kért.
A TCP „háromutas kézfogást” használ az összeköttetések felépítésére. Az összeköttetések egy időbélyeget is használnak a 32 bites sorszám mellett, hogy azok ne forduljanak át a maximális csomagélettartam alatt, még gigabites hálózatok esetén sem. Ez a megoldás egy javítás a TCP-ben, hogy gyorsabb hálózatokon is használni lehessen. A módszert az RFC 1323 írja le, és PAWS (Protection Against Wrapped Sequence numbers – átfordult sorszámok elleni védelem) néven ismert. A PAWS előtt a TCP a kezdeti sorszámok megválasztásánál a fenn leírt óraalapú megoldást használta. Kiderült azonban, hogy a módszer a TCP-ben egy biztonsági rést hagyott. Az óra egy támadó számára könnyen megjósolhatóvá tette a következő kezdeti sorszám értékét, és olyan csomagokat küldhetett, amelyek a „háromutas kézfogást” félrevezetik, és hamis összeköttetést hoznak létre. A sérülékenység befoltozására a gyakorlatban álvéletlen kezdeti sorszámokat használnak. Mindazonáltal még mindig fontos tényező, hogy egy ideig ne forduljanak elő a kezdeti sorszámok még akkor sem, ha a kiválasztásuk egy külső megfigyelő számára véletlennek tűnik. Különben nem kerülhető el a késleltetett kettőzött csomagok károkozása.
Az összeköttetést bontani jóval egyszerűbb, mint felépíteni. Azonban több buktató van itt, mint gondolnánk. Mint korábban említettük, az összeköttetés kétféleképpen bontható: aszimmetrikus és szimmetrikus módon. Aszimmetrikus bontást alkalmaznak például a távbeszélő-hálózatokban: amikor az egyik fél leteszi a kagylót, az összeköttetés megszakad. Szimmetrikus bontás esetén az összeköttetést két független egyirányú összeköttetésként kezelik, ahol mindkettőt külön kell lebontani.
Az aszimmetrikus összeköttetés-bontás váratlanul történik, és adatvesztéssel járhat. Vegyük például a 6.12. ábra forgatókönyvét. Az összeköttetés létrejötte után az 1. hoszt egy szegmenst küld a 2. hosztnak, s az rendben meg is érkezik. Az 1. hoszt újabb szegmenst küld. Sajnos a 2. hoszt kiad egy disconnect-et, mielőtt a második szegmens megérkezik. Az összeköttetés lebomlik, és ezzel együtt az adat elvész.
Világos, hogy kifinomultabb bontási protokoll szükséges az adatvesztés elkerüléséhez. Egyfajta megoldás a szimmetrikus bontás használata, amikor is mindkét irányt a másiktól függetlenül bontjuk le. Itt egy hoszt az után is fogadhat adatot, hogy már elküldött egy DISCONNECTION REQUEST szegmenst.
A szimmetrikus bontás megfelelően működik, ha mindkét félnek rögzített mennyiségű elküldendő adata van, és mindegyik pontosan tudja, hogy mikor küldte el. Más helyzetekben annak eldöntése, hogy végeztek dolgukkal, és a összeköttetés bontható, nem annyira nyilvánvaló. Elképzelhető egy olyan protokoll, melyben az 1. hoszt így szól: „Végeztem. Te is készen vagy?” „Én is elkészültem. Az összeköttetés biztonságosan bontható! Szervusz!” – válaszol a 2. hoszt.
Sajnos, ez a protokoll nem mindig működik. Egy híres, a „két hadsereg” probléma néven ismert feladat éppen erről szól. Képzeljük el, hogy a fehér hadsereg a völgyben táborozik, amint a 6.13. ábra mutatja. Mindkét környező dombot a kék hadsereg birtokolja. A fehér sereg bármelyik kék hadseregnél nagyobb, azonban azok együtt nagyobbak a fehér seregnél. Ha bármelyik kék hadsereg egyedül támad, biztos vereséget szenved, de együttes támadásuk során megsemmisíthetnék a fehér sereget.
A kék seregek össze akarják egyeztetni támadásukat. Azonban az összes kommunikációs lehetőségük a völgyön gyalog átküldött futárokra korlátozódik, akiket persze elfoghatnak, így az üzenet elvész (tehát megbízhatatlan kommunikációs csatornát használnak). Az a kérdés, hogy létezik-e olyan protokoll, ami győzelemre segíti a kék seregeket?
Tegyük fel, hogy az 1. kék hadsereg parancsnoka a következő üzenetet küldi: „Azt javaslom, hogy március 29-én hajnalban támadjunk. Mi a véleményetek?” Tegyük fel továbbá, hogy az üzenet megérkezik, a 2. kék hadsereg parancsnoka egyetért, és válasza szerencsésen megérkezik az 1. kék sereghez. Végrehajtják a támadást? Valószínűleg nem, mert a 2. sereg parancsnoka nem tudja, hogy üzenete átjutott-e a völgyön. Ha nem, az 1. sereg nem fog támadni, így egymaga bolond lenne fölvenni a küzdelmet.
Bővítsük hát a protokollt a „háromutas kézfogás” technikával. Az eredeti javaslat kezdeményezőjének nyugtáznia kell a kapott választ. Feltéve, hogy nem veszett el üzenet, a 2. kék hadsereg megkapja a nyugtát, de most az 1. kék sereg parancsnoka fog habozni. Végül is ő nem tudja, hogy a nyugta átjutott-e vagy sem, és ha nem, tudja, hogy nem számíthat a 2. sereg támadására. Használhatnánk „négyutas kézfogást” is, de az sem segítene.
Valójában könnyen bebizonyítható, hogy nem lehet működő protokollt létrehozni. Tegyük fel mégis, hogy van ilyen. A protokoll utolsó üzenete vagy lényeges, vagy nem. Utóbbi esetben hagyjuk el az összes többi fölösleges üzenettel együtt, amíg olyan protokollhoz nem jutunk, melynek minden üzenete nélkülözhetetlen. Mi történik, ha az utolsó üzenet nem jut át? Mivel erről megállapítottuk, hogy lényeges, így ha elvész, nem kezdődik el a támadás. Mivel az utolsó üzenet küldője sohasem lehet biztos annak célba érkezésében, nem kockáztatja meg a támadást. Sőt ami még rosszabb, ezt a másik kék sereg is tudja, így ők sem támadnak.
Hogy észrevegyük a „két hadsereg” probléma és az összeköttetések bontása között vonható párhuzamot, helyettesítsük a „támadás” szót a „bontás”-sal. Ha egyik fél sem készült föl a bontásra addig, míg meg nem győződött arról, hogy partnere is fölkészült, az összeköttetés bontására sohasem kerül sor.
A gyakorlatban elkerülhetjük ezt a bizonytalanságot azáltal, hogy nem tesszük szükségessé a felek közti megegyezést, és a problémát a szállítási felhasználókra bízzuk, hogy külön-külön döntsenek a bontásról. Ezt már sokkal könnyebb megoldani. A 6.14. ábrán négy forgatókönyvet mutatunk az összeköttetés-bontásra „háromutas kézfogás” használata esetén. Bár ez a protokoll nem sebezhetetlen, mégis kielégítően viselkedik.
6.14. ábra - Négy protokoll forgatókönyv az összeköttetés lebontására. (a) Normális működés a „háromutas kézfogás”-sal. (b) A végső ACK vész el. (c) A válasz vész el. (d) A válasz és a rákövetkező DR-ek vesznek el
A 6.14.(a) ábrán a normális működést láthatjuk, ahol az egyik partner az összeköttetés bontásának kezdeményezéseként DR (DISCONNECTION REQUEST) szegmenst küld. Amikor ez megérkezik, a vevő szintén visszaküld egy DR szegmenst, és elindít egy időzítőt arra az esetre, ha az általa küldött DR elveszne. Amikor ez a DR megérkezik, a kezdeményező visszaküld egy ACK szegmenst, és bontja az összeköttetést. Végül, mikor az ACK szegmens megérkezik, a vevő szintén bontja az összeköttetést. Az összeköttetés bontása azt jelenti, hogy a szállítási entitás az összeköttetésre vonatkozó információt törli a nyitott összeköttetések táblájából, és jelzi az összeköttetés befejezését a tulajdonosnak (a szállítási felhasználónak). Ez a művelet eltér attól, amikor a szállítási felhasználó kiad egy disconnect primitív hívást.
Ha az utolsó ACK szegmens elvész, ahogy az a 6.14.(b) ábrán látható, a helyzetet az időzítő menti meg. Amikor az lejár, az összeköttetés mindenképpen befejeződik.
Most tekintsük azt az esetet, amikor a második DR vész el. Az összeköttetés bontását kezdeményező fél nem kapja meg a várt választ, lejár az időzítője, és az egészet elkezdi elölről. A 6.14.(c) ábrán látható ez a működés, feltételezve, hogy a második próbálkozás során nem vesznek el a szegmensek, és mindegyik időben meg is érkezik.
Az utolsó eset, amit a 6.14.(d) ábrán láthatunk, azonos az előzővel, kivéve, hogy most feltételezésünk szerint minden további kísérlet a DR újraküldésére meghiúsul a szegmensek elvesztése következtében. N próbálkozás után a küldő föladja, és fölbontja az összeköttetést. Eközben a vevő időzítése szintén lejár, és ő is bontja az összeköttetést.
Bár ez a protokoll rendszerint sikeresen működik, elméletileg kudarcot vallhat, ha a kezdeti DR és a további N újraküldött szegmensek mind elvesznek. A küldő feladja és bontja az összeköttetést, míg a másik fél semmit sem tud a bontási kísérletekről, és még mindig teljesen aktív. Ennek a helyzetnek az eredménye egy félig nyitott összeköttetés.
Ez a probléma elkerülhető lenne, ha nem hagynánk, hogy a küldő N próbálkozás után föladja a kísérletezést, hanem kényszerítenénk, hogy örökké folytassa, amíg választ nem kap. Ha viszont a másik fél időzítést figyel, és az lejár, a küldő ténylegesen örökké fog próbálkozni, mert többé nem is kaphat választ. Ha nem engedjük a fogadóoldalt, hogy időtúllépés esetén a várakozást feladja, a 6.14.(d) ábrán látható protokoll elakad.
A félig nyitott összeköttetések kilövésének egyik módja a következő: bevezetünk egy olyan szabályt, miszerint ha adott ideig nem érkezik szegmens, az összeköttetést automatikusan bontjuk. Ily módon, ha valamelyikük befejezi az összeköttetést, a másik észreveszi a forgalom hiányát, és szintén bontja az összeköttetést. Ez a szabály továbbá megoldja azt a helyzetet is, amikor az összeköttetés megszakad (például mert a hálózat nem képes a továbbiakban csomagokat szállítani a hosztok között) anélkül, hogy bármely fél bontást kezdeményezett volna. Természetesen ennek a szabályozásnak a bevezetése szükségessé teszi egy időzítő alkalmazását minden szállítási entitásnál, amit az minden szegmens elküldésekor nulláz és újraindít. Ha lejár, akkor egy üres (adatot nem hordozó) szegmenst küld, hogy visszatartsa a vevőt az összeköttetés bontásától. Ha azonban az automatikus lebontási szabályt alkalmazzuk, és túl sok egymás után küldött üres szegmens elvész az amúgy kihasználatlan összeköttetésen, először az egyik, majd a másik oldal is bontja az összeköttetést.
Nem ragozzuk tovább ezt a kérdést, de mostanra már az olvasó bizonyára megértette, hogy egy összeköttetés adatvesztés nélküli lebontása közel sem annyira egyszerű, mint amilyennek az első ránézésre tűnik. Amit mégis megtanultunk az, hogy a szállítási felhasználónak kell eldönteni, mikor bontsuk az összeköttetést – a problémát a szállítási entitások csupán egymás közt nem tudják megoldani. Ahhoz, hogy lássuk, milyen fontos szerepe van az alkalmazásnak, vegyük figyelembe, hogy míg a TCP általánosan szimmetrikus bontást végez (azaz mindkét fél az adatok elküldése után FIN szegmensekkel külön-külön lezárja a összeköttetés egyik felét), a kliens felé sok webszerver küld RST szegmenst, amelyek azonnal az összeköttetés bontását eredményezik. Ez leginkább az aszimmetrikus bontásra hasonlít. A módszer azért működőképes, mert a webszerver pontosan ismeri az adatcsere módját. Először is a szerver egy kérést kap a klienstől, ami tartalmazza az összes adatot, amit a kliens szeretne megkapni, majd a szerver visszaküldi a választ a kliens felé. Amikor a webszerver elküldte a választ, az adatáramlás mindkét irányban véget ért.[27] A szerver küldhet a kliensnek egy figyelmeztetést, majd azonnal bonthatja az összeköttetést. Ha a kliens megkapja a figyelmeztetést, akkor ott helyben lezárja az összeköttetést, egyéb esetben, ha a figyelmeztetés nem érkezik meg, akkor a kliens végül észreveszi, hogy a szerver nem kommunikál vele, és lezárja az összeköttetést. Az adatokat mindkét irányban sikeresen átvitték.
Az összeköttetés-létesítés és -lebontás többé-kevésbé részletes tárgyalása után vizsgáljuk meg, hogyan kezelik az összeköttetéseket használat közben. A két kulcskérdés a hibakezelés és a forgalomszabályozás. A hibakezelés biztosítja azt, hogy az adatok megfelelő biztonsággal érkezzenek meg, azaz általában hibátlanul. A forgalomszabályozás teszi lehetővé, hogy egy gyors adó nehogy adatokkal túltöltsön egy lassú vevőt.
Mindkét témakör felbukkant már korábban, amikor az adatkapcsolati réteget tanulmányoztuk. Az ott megismert módszereket használják a szállítási rétegben is. Rövid ismétlésként:
A keret egy hibajelző kódot tartalmaz (például CRC-t vagy ellenőrző összeget), amelyet az információ helyességének ellenőrzésére használnak.
A keret tartalmaz egy azonosító sorszámot, és a küldő egészen addig rendszeresen újraküldi a keretet, amíg egy pozitív nyugtát nem kap a vevőtől, jelezve, hogy az sikeresen megkapta a keretet. Ezt ARQ-nak (Automatic Repeat reQuest – automatikus ismétléskérés) nevezik.
A küldő meghatároz egy számot a kint lévő keretek maximális mennyiségére bármilyen tetszőleges időpontban, aminél többet nem küld, és megáll, ha a vevő nem nyugtázza a kereteket elég gyorsan. Ha ez a maximum egyetlen keret, akkor a protokollt megáll-és-vár típusú protokollnak hívják. Nagyobb ablakméretek lehetővé teszik a csővezetékezést és a teljesítőképesség növelését nagy sebességű és hosszú összeköttetéseken.
A csúszóablakos protokoll egyesíti ezeket az eszközöket, és mindemellett használják a kétirányú adatátvitel megvalósítására is.
Mivel ezeket a módszereket az adatkapcsolati rétegben a kereteken már alkalmazták, érthető a kíváncsiság, miért alkalmazzák ezeket a szállítási rétegben a szegmensekre is. Valóban, van egy kevés hasonlóság az adatkapcsolati és szállítási rétegek között a gyakorlatban. Hiába azonosak azonban a módszerek, mind céljukban, mind mértékükben vannak különbségek.
Például nézzük meg, miért különböznek céljukban! Az adatkapcsolati réteg ellenőrző összege megvédi a keretet, miközben áthalad egy összeköttetésen. A szállítási réteg ellenőrző összege a szegmenst védi, miközben egy egész hálózati útvonalon áthalad. Ez utóbbi egy végponttól végpontig tartó ellenőrzés, ami nem azonos az adatkapcsolatonkénti ellenőrzéssel. Saltzer és mások [1984] mutattak példát arra, amikor a csomagok éppen az útválasztón belül sérültek meg. Az adatkapcsolati ellenőrző összeg megvédte a csomagokat az útválasztók közötti összeköttetéseken, de nem magában az útválasztóban. Így végül a csomagok hibásan érkeztek meg annak ellenére, hogy az ellenőrzés egyik összeköttetésen sem mutatott ki hibát.
Ez és egyéb példák késztették Saltzert és társait arra, hogy megfogalmazzák a végponttól végpontig elvüket (end-to-end argument). E szerint a szállítási rétegben történő ellenőrzések nélkülözhetetlenek a hibátlan működéshez, míg az adatkapcsolati rétegben történő ellenőrzések csupán a teljesítőképesség növelésében játszanak szerepet (mivel nélkülük egy hibás csomag teljesen feleslegesen végigjárná a hálózatot).
A mértékbeli különbségek megmutatásához vegyük az újraküldést egy csúszóablakos protokoll esetén. A legtöbb vezeték nélküli kapcsolaton (kivéve a műholdas összeköttetéseket) legfeljebb egy keret lehet kinn, hiszen a sávszélesség-késleltetés szorzat annyira kicsi, hogy még egy keret is alig tárolható a kapcsolaton. Ez esetben kicsi ablakméret is elegendő a jó teljesítményhez. Például a 802.11 megáll-és-vár protokollt használ, tehát minden keretnél elküldi azt, majd vár a nyugtára, esetleg újraküldi a keretet. Egészen addig nem lép a következő keretre, amíg nem érkezik meg a nyugta. Egynél nagyobb ablakméret nem okozná a teljesítmény javulását, azonban jelentősen növelné a protokoll bonyolultságát. Vezetékes és optikai adatkapcsolatok esetén, mint például a (kapcsolt) Ethernetnél vagy az ISP-k gerinchálózatain, a hibaarány elég kicsi ahhoz, hogy mellőzhessük az adatkapcsolati rétegben történő újraküldéseket, mivel a végponttól végpontig történő újraküldés majd kijavítja a maradék keretvesztéseket.
Másrészről, sok TCP-összeköttetés sávszélesség-késleltetés szorzata sokkal nagyobb, mint egyetlen szegmens. Vegyünk például egy olyan összeköttetést, amely adatokat továbbít az USA-n keresztül 1 Mbit/s sebességgel és 100 msec egyirányú terjedési idővel. A vevő még egy ilyen lassú összeköttetésen is 200 kbit mennyiségű adatot fog tárolni annyi idő alatt, amennyi egy szegmens elküldésétől a nyugta megérkezéséig tart. Ilyen esetekben tanácsos nagy ablakméretet használni. A megáll-és-vár protokoll lerontja a teljesítőképességet. A példánkban a teljesítőképességet 200 ms-ként egy szegmensre (vagy 5 szegmens/másodpercre) korlátozná függetlenül a hálózat sebességétől.
Mivel a szállítási protokollok általában nagyobb ablakméretet használnak, ezért részletesebben áttekintjük a pufferelés kérdését. Mivel egy állomásnak több élő összeköttetése is lehet, melyeket elkülönítve kell kezelni, tekintélyes méretű puffernek kell rendelkezésre állni a csúszóablakok számára. A pufferek szükségesek mind a fogadó-, mind a küldőoldalon. A küldőoldalon az elküldött, de nem nyugtázott szegmensek számára kellenek a pufferek, hiszen ezek a szegmensek még elveszhetnek, és ekkor újra kell őket küldeni.
Mivel azonban a küldő elvégzi a pufferelést, a vevő eldöntheti, hogy hozzárendelt (dedikált) speciális puffereket használ a speciális összeköttetésekhez, vagy nem. A vevő például fenntarthat egy pufferkészletet, megosztva az összeköttetések között. Amikor egy szegmens érkezik, megpróbál dinamikusan egy új puffert foglalni; ha sikerül, akkor elfogadja, egyébként eldobja a szegmenst. Mivel a küldő kész újraküldeni a hálózatban elveszett szegmenseket, nem probléma, ha a vevő eldob szegmenseket, bár ezzel erőforrást pazarol. A küldő úgyis addig próbálkozik, míg végül nyugtát nem kap.
A legjobb kompromisszum a forráspufferelés és célpufferelés között az összeköttetés forgalmának típusától függ. Kis sávszélességű, löketes forgalomra, mint amilyet például egy interaktív terminál állít elő, célszerűbb dinamikusan puffert foglalni mindkét oldalon, és bízni a küldő pufferelésében, ha véletlenül egy szegmenst el kell dobni, mint dedikált puffereket használni. Másrészt fájlátvitelhez vagy nagy sávszélességű forgalomhoz jobb dedikált puffereket használni, hogy az adatok teljes sebességgel áramolhassanak. Pontosan ezt a stratégiát alkalmazza a TCP is.
Még mindig marad azonban egy kérdés: hogyan szervezzük a pufferkészletet? Ha a legtöbb szegmens nagyjából azonos méretű, kézenfekvő a pufferterületet azonos méretű pufferek készletébe szervezni úgy, hogy egy szegmensre egy puffer jusson, amint azt a 6.15.(a) ábra mutatja. Ha azonban a szegmensek mérete széles határok közt változhat – egy weboldal lekéréséhez szükséges kéréstől a P2P-állományátvitelkor mozgatott nagy csomagokig –, a rögzített méretű pufferek problémát jelentenek. Ha a pufferméretet a leghosszabb előforduló szegmens méretében állapítjuk meg, rövid szegmens vétele esetén a hely nagy része kihasználatlan marad. Ha a pufferméret kisebb a szegmens maximális hosszánál, egy nagy szegmens több puffert és bonyolultabb kezelést igényel.
6.15. ábra - (a) Láncolt, rögzített méretű pufferek. (b) Láncolt, változó méretű pufferek. (c) Összeköttetésenként egyetlen nagy körpuffer
A pufferméret problémájának másik kezelési módja a változó méretű pufferek használata, mint azt a 6.15.(b) ábra mutatja. Ennek előnye a jobb memóriakihasználtság, ami viszont bonyolultabb pufferkezeléssel jár. A harmadik lehetőség összeköttetésenként egyetlen nagy körpuffer alkalmazása, mint az a 6.15.(c) ábrán látható. Ez a megoldás egyszerű és elegáns, nem függ a szegmensmérettől, de csak akkor használja ki hatékonyan a memóriát, ha az összeköttetés erősen leterhelt.
Ahogy összeköttetések létrejönnek és lebomlanak, és ahogy a forgalom jellege változik, úgy kell az adónak és a vevőnek ehhez dinamikusan hozzáigazítani a pufferfoglalást. Eszerint a szállítási protokollnak lehetővé kell tennie a küldőentitás számára, hogy a másik féltől pufferterület lefoglalását kérje. Puffereket összeköttetésenként vagy a két hoszt között élő minden összeköttetésre együtt lehet lefoglalni. Alternatív megoldásként a vevő a jövendő forgalmat nem, de saját puffereinek adatait ismerve közölheti az adóval: „Lefoglaltam részedre X db puffert.” Ha a nyitott összeköttetések száma növekszik, akkor az eddigi foglalásokat csökkenteni kell, tehát a protokollnak erre is lehetőséget kell adnia.
A pufferek dinamikus kezelésének egy ésszerű, általános módja a pufferelésnek a nyugtázástól való különválasztása. Ez a 3. fejezetben leírt csúszóablakos protokollal ellentétes működést jelent. A dinamikus pufferkezelés valójában változó méretű csúszóablakot jelent. Először az adó a forgalom becsült igénye alapján adott mennyiségű puffert kér a vevőtől. A vevő annyi puffert ad, amennyit adhat. Az adó minden szegmens küldésekor ezt a számot csökkenti, és leáll az átvitellel, ha eléri a nullát. A vevő közben a visszafelé menő forgalomra ülteti rá a nyugtákat és a pufferek foglaltsági jellemzőit. A TCP is ezt a sémát alkalmazza, és a pufferek foglaltsági jellemzőit egy Window size (Ablakméret) nevű fejlécmezőben közli.
A 6.16. ábrán egy példát mutatunk arra, hogy hogyan működne a dinamikus ablakkezelés datagramalapú hálózat fölött négybites sorszámokkal. Példánkban az adatok szegmensekben áramlanak A-tól B felé, a nyugták és a pufferfoglaltsági jellemzők pedig ugyancsak szegmensekben áramlanak, de ellentétes irányba. Először az A hoszt 8 puffert szeretne, de ezekből csak négyet kaphat. A ezután elküld három szegmenst, amelyek közül az utolsó elvész. A 6. szegmens nyugtáz az 1-es sorszámúval bezárólag (azt is beleértve) minden elküldött szegmenst, lehetővé téve A-nak, hogy azok puffereit fölszabadítsa. Ezenfelül arról is értesíti A-t, hogy újabb három szegmenst küldhet 1-es fölötti sorszámmal (tehát a 2-es, 3-as és 4-est). A tudja, hogy a kettes sorszámút már továbbította, úgy gondolja, hogy elküldheti a rákövetkező kettőt, és így is tesz. Ezen a ponton blokkolódik, és további pufferek lefoglalására kell várakoznia. Viszont az időtúllépés miatti újraküldés blokkolt állapotban is lehetséges (9-es sor), mivel ilyenkor az elküldendő szegmens már pufferben van. A 10-es sorban B nyugtázza minden szegmens megérkezését a 4-es sorszámúval bezárólag (azt is beleértve), de nem hagyja A-t továbbadni. Ez a helyzet nem fordulhat elő a 3. fejezetben tárgyalt rögzített ablakméretű protokolloknál. A B által küldött következő szegmens újabb puffert foglal, így A továbbhaladhat. Ez akkor történhet meg, ha B-nek rendelkezésére áll puffer, például azért, mert a szállítási felhasználó több szegmens adatrészét átvette.
6.16. ábra - Dinamikus pufferfoglalás. A nyilak az átvitel irányát mutatják, a három pont (…) elveszett szegmenst jelöl
Ilyen jellegű pufferfoglalási módszernél problémát jelenthet, ha a szállítási réteg datagramalapú hálózatra épül, és egy vezérlőszegmens vész el – ami persze könnyen megtörténhet. Nézzük a 16. sort! B újabb puffereket foglalt le A részére, de a szegmens, amivel erről A-t tájékoztatná, elveszett. Hoppá! Mivel a vezérlőszegmensek nem kapnak sorszámot, és időzítő se figyeli hiányukat, az A holtpontba kerül. Az ilyen helyzetek elkerülésére mindkét hoszt rendszeres időközönként vezérlőszegmenseket küld társának, amelyekben nyugta és az összeköttetésekhez tartozó pufferek állapotáról szóló információ van. Ily módon előbb-utóbb föloldódik a holtpont.
Eddig hallgatólagosan feltételeztük, hogy a küldő adási sebességének egyedül a vevő szabad puffereinek száma szab határt. Leggyakrabban azonban nem ez a helyzet, ugyanis a memóriaárak rohamos csökkenése miatt lehetséges annyi memóriát zsúfolni a hosztokba, hogy a szabad pufferek hiánya ritkán vagy egyáltalán nem okoz problémát, még nagy kiterjedésű hálózatokban sem. Természetesen ez akkor igaz, ha a pufferek méretét elég nagyra választják, ami nem volt mindig így a TCP esetén [Zhang és társai, 2002].
Amikor a pufferterület többé nem korlátozza a maximális forgalmat, egy újabb szűk keresztmetszet bukkan föl: a hálózat szállítókapacitása. Ha szomszédos útválasztók legfeljebb x keret/s sebességre képesek, és k független út van két hoszt között, semmilyen módon nem valósíthat meg a szóban forgó két hoszt kx szegmens/s-nál nagyobb sebességű átvitelt, akármennyi szabad puffere van is a két félnek. Ha a küldő túl sűrűn ad (tehát kx-nél több szegmenst küld másodpercenként), a hálózatban torlódás keletkezik, mert nem képes olyan gyorsan továbbítani a szegmenseket, mint amilyen gyorsan kapja azokat.
Valójában olyan, a küldő sebességét szabályozó mechanizmusra van szükség, amely a hálózat szállítókapacitására, és nem a vevő pufferfoglalási lehetőségeire épít. Belsnes [1975] egy olyan csúszóablakos forgalomszabályozási módszert javasolt, melyben a küldő dinamikusan a hálózat szállítási kapacitásához igazítja az ablak méretét. Ez azt jelenti, hogy a dinamikus ablakállítás egyszerre valósítja meg a forgalomszabályozást és a torlódáskezelést. Ha a hálózat másodpercenként c szegmens átvitelére képes, és a körülfordulási idő r (ami magában foglalja a küldési, átviteli és vevő várakozási soraiban eltöltött időt, a vevő által végzett feldolgozás idejét és a nyugta visszaérkezésének idejét), a küldő ablakmérete célszerűen cr kell legyen. Ekkora ablakmérettel normális állapotban a küldő folyamatosan tele csővel dolgozik, így a hálózati teljesítőképesség legkisebb csökkenése is a küldő blokkolását okozza. Mivel a hálózati kapacitás bármely folyamra időben változó, az ablakméretet sűrűn kell állítani, hogy a szállítási kapacitást nyomon tudja követni. Amint látni fogjuk, a TCP is hasonló megoldást alkalmaz.
Több beszélgetés összeköttetésekre, virtuális áramkörökre és fizikai összeköttetésekre való nyalábolása, multiplexelése a hálózati architektúra számos rétegében játszik szerepet. A szállítási rétegben a multiplexelés igénye többféle okból is felmerülhet. Ha például egy hoszton csak egyetlen hálózati cím áll rendelkezésre, akkor azon a gépen minden szállítási összeköttetésnek azt kell használnia. Szükség van valamilyen módszerre, amellyel eldönthetjük, hogy a beérkező szegmenseket melyik folyamatnak kell továbbítani. Ezt a feladatot nyalábolásnak vagy multiplexelésnek (multiplexing) hívják és a 6.17.(a) ábra mutatja be. Az ábrán négy különböző szállítási összeköttetés használja ugyanazt a hálózati összeköttetést (vagyis IP-címet) a távoli hoszt eléréséhez.
A nyalábolás a szállítási rétegben egy másik ok miatt is hasznos lehet. Tegyük fel például, hogy egy hálózaton belül egy hoszt több útvonalat is használhat. Ha egy felhasználónak nagyobb sávszélességre van szüksége, mint amennyit az egyik útvonal nyújtani tud, akkor egy olyan hálózati összeköttetést kell megnyitnia, mely a forgalmat körforgásos alapon elosztja több útvonal között, ahogy az a 6.17.(b) ábrán is látható. Ezt a működési módot fordított nyalábolásnak vagy fordított multiplexelésnek (inverse multiplexing) nevezik. Ha k hálózati összeköttetést nyitunk meg, akkor a rendelkezésre álló sávszélesség k-szorosára növekedhet. A fordított nyalábolás egy példája az SCTP (Stream Control Transmission Protocol – folyamvezérlő átviteli protokoll), amely képes több hálózati interfészt használni egyetlen összeköttetésen belül. Ezzel ellentétben a TCP csak egy hálózati végpontot használ. A fordított nyalábolás megtalálható az adatkapcsolati rétegben is, ahol több kis sebességű összeköttetést párhuzamosan használva egy nagy sebességű összeköttetést alakítanak ki.
Ha az útválasztók és hosztok összeomlás veszélyének vannak kitéve, vagy ha az összeköttetések hosszú életűek (például méretes szoftver- vagy médialetöltés esetén), fontos kérdéssé válik a feléledés megvalósítása. Ha a teljes szállítási entitás a hoszton belül van megvalósítva, a hálózat és az útválasztók összeomlás utáni helyreállítása nyilvánvaló. A szállítási entitások mindig számolnak elveszett szegmensekkel, és tudják, hogyan kezeljék azokat.
Ennél sokkal komolyabb probléma a hosztok összeomlás utáni újraindítása. Általános cél lehet, hogy a kliensek a szerver összeomlása és gyors föléledése után tovább tudják folytatni munkájukat. A nehézség illusztrálására tegyük föl, hogy az egyik hoszt, a kliens, egy nagy állományt küld a másik hoszt, az állományszolgáltató számára egyszerű megáll-és-vár (stop-and-wait) protokollal. A szerver szállítási rétege egyszerűen egyenként átadja a beérkező szegmenseket a szállítási felhasználónak. Az átvitel közepén a szerver összeomlik. Amikor újraéled, az összes táblázatát újrainicializálja, így nem tudja, hogy hol tartott.
Előző állapotának kiderítése érdekében a szerver minden kliens részére elküldhet egy szegmenst, melyben bejelenti, hogy tönkrement, és mindenkit kér, hogy küldjék el használt összeköttetéseik állapotát. Minden kliens az alábbi két állapot egyikében lehet: egy elküldött szegmens van függőben (S1), vagy nincs ilyen szegmens (S0). Csupán ezen információ birtokában el kell döntenie a kliensnek, hogy újraküldje-e a legutolsó szegmenst, vagy sem.
Első pillantásra nyilvánvalónak tűnhet, hogy a kliensnek, amikor megtudja, hogy a szerver összeomlott, csak akkor kell újraküldenie a kérdéses szegmenst, ha az nyugtázatlan (azaz ha S1 állapotban van). Közelebbi vizsgálatok során azonban kiderül ennek a naiv megközelítésnek a veszélye. Vegyük például azt a helyzetet, amikor a szerver szállítási entitása elküldi a nyugtát, és miután a nyugta elindult, csak ezután adja át a szegmenst az alkalmazói folyamatnak. A szegmens kimenő folyamba írása és a nyugta elküldése két, külön-külön oszthatatlan esemény, melyeket nem lehet egyszerre végrehajtani. Ha az összeomlás a nyugta elküldése után, de még a szegmens átadása előtt történik meg, a kliens megkapja a nyugtát, és így S0 állapotban lesz, amikor az újraindulás bejelentése megérkezik. A kliens ekkor nem fogja újraküldeni a szegmenst, mivel (helytelenül) abban a tudatban él, hogy az megérkezett. Döntése egy hiányzó szegmenst eredményez.
Ezen a ponton azt gondolhatjuk: „Ez a probléma könnyen megoldható. Csak annyit kell tenni, hogy átírjuk a szállítási entitást, és ezentúl először a folyamba ír, és csak azután küldi a nyugtát.” Játsszuk el a fönti kísérletet ismét! Képzeljük el, hogy a folyamba írás már megtörtént, de az összeomlás éppen a nyugta elküldése előtt következik be. A kliens így S1 állapotban lesz, és újraküldi a szegmenst, ami így észrevétlenül egy második szegmensként a szerveroldali alkalmazási folyamat irányába menő kimeneti adatáramba kerül.
Mindegy, hogyan programozzuk a szervert és a klienst, mindig vannak olyan helyzetek, melyekben ez a protokoll kudarcot vall. A szerver kétféleképpen valósítható meg: először nyugtáz vagy először ír. A kliens négyféleképpen programozható: mindig újraküldi az utolsó szegmenst, sohasem küldi újra, csak az S0 állapotban küldi újra, vagy csak az S1-ben. Ez nyolc kombinációt jelent, de mint látni fogjuk, ezek közül mindegyikhez van egy eseményhalmaz, aminek hatására a protokoll hibázik.
A szerver oldalán három esemény lehetséges: nyugta küldése (Ny), kimeneti folyamba írás (K) és összeomlás (Ö). A három esemény hat különböző sorrendben történhet: NyÖ(K), NyKÖ, Ö(NyK), Ö(KNy), KNyÖ és KÖ(Ny), ahol a zárójelek azt jelölik, hogy az összeomlást már sem nyugtázás, sem írás nem követi (azaz ha a rendszer összeomlott, akkor tényleg összeomlott). A 6.18. ábrán a kliens- és szerverstratégiák nyolc kombinációját mutatjuk be az érvényes eseménysorrendekkel együtt. Vegyük észre, hogy minden stratégiára található valamilyen eseménysorrend, amire a protokoll kudarcot vall. Például, ha a kliens újraküld, az NyKÖ eseménysorozat észrevétlen kettőzést eredményez, miközben a másik két sorozatra helyesen működik a protokoll.
A protokoll további finomítása sem segít. Még ha a szerver és a kliens – mielőtt a szerver írni próbálna – több szegmenst váltana is egymással, hogy a kliens pontosan tudja, mi fog történni, arról semmiképpen sem szerezhet tudomást, hogy az összeomlás közvetlenül az írás előtt vagy közvetlenül utána következett-e be. A következtetés elkerülhetetlen: alapszabályunk – mely szerint az események nem történhetnek párhuzamosan – kizárja annak lehetőségét, hogy a rendszerösszeomlás és újraindulás a felső rétegek számára transzparens módon mehessen végbe.
Általánosabban szólva, ez az eredmény azt jelenti, hogy az N. réteg összeomlásából történő újraindulás csak az N + 1. réteg segítségével lehetséges, feltéve, hogy a magasabb réteg elegendő információt tárol az állapotáról, hogy helyre tudja állítani azt az összeomlás előtti állapotra. Ez egybevág azzal, amit fönt említettünk, vagyis a szállítási réteg akkor képes kezelni a hálózati rétegben történt összeomlásokat, ha az összeköttetés mindkét vége állandóan nyomon követi, hogy hol tartanak.
Ez a probléma végül elvezet ahhoz a kérdéshez, hogy mit is jelent valójában az ún. végpontok közötti nyugtázás. Alapjában véve a szállítási protokoll két végpont között működik, és nem láncolt, mint az alatta levő rétegek. Most vegyük azt az esetet, amikor a felhasználó a távoli adatbázisban tranzakciókat kezdeményez. Tegyük föl, hogy a távoli szállítási entitás programja szerint először átadja a szegmenst a fölötte levő rétegnek, és ezután nyugtáz. Még ebben az esetben sem jelenti a nyugta vétele a felhasználó gépén, hogy a távoli hoszt addig működőképes maradt, amíg a tranzakció lezárása meg nem történt. Egy igazi végpontok közötti nyugtát – melynek vétele azt jelenti, hogy a tevékenység ténylegesen végbement, és hiánya annak elmaradását jelzi – valószínűleg lehetetlen elérni. A kérdésről bővebben Saltzer és munkatársai [1984] művében olvashatunk.
[27] Ez persze nem mindig igaz, hiszen a Keep-Alive HTTP opció esetén a webszerver nem zárja az összeköttetést, és felkészül további kérések fogadására. (A fordító megjegyzése)