6.5. Az internet szállítási protokolljai: a TCP

Az UDP egy egyszerű protokoll és van néhány nagyon fontos alkalmazása, mint például a kliens–szerver-interakciók és a multimédia. A legtöbb internetes alkalmazás azonban megbízható, sorrendhelyes kézbesítést igényel. Az UDP ezt nem tudja nyújtani, ezért egy másik protokollra is szükség van. Ezt a protokollt TCP-nek hívják, és ez az internet legfontosabb igáslova. Lássunk neki a részletes tanulmányozásának!

6.5.1. A TCP bemutatása

A TCP-t (Transmission Control Protocol átvitelvezérlő protokoll) kifejezetten arra tervezték, hogy megbízható bájtfolyamot biztosítson a végpontok között egy egyébként megbízhatatlan összekapcsolt hálózaton. Egy összekapcsolt hálózat abban különbözik egyetlen hálózattól, hogy az egyes részeinek topológiája, sávszélessége, késleltetése, csomagmérete és más paraméterei nagymértékben különbözhetnek. A TCP-t arra tervezték, hogy dinamikusan alkalmazkodjon az összekapcsolt hálózatok tulajdonságaihoz, valamint hogy nagymértékben ellenálló legyen sokféle meghibásodással szemben.

A TCP-t formálisan 1981 szeptemberében, a 793-as RFC-ben definiálták, az idő előrehaladtával azonban sok hibára és belső ellentmondásra derült fény, ezért ezeket kijavították, és a protokollt sokat tökéletesítették. Hogy képet adjunk a TCP terjedelméről, a legfontosabb RFC-k jelenleg az RFC 793, továbbá: hibajavítások és tisztázások az RFC 1122-ben; kiterjesztések a nagy teljesítőképesség érdekében az RFC 1323-ban; szelektív nyugtázás az RFC 2018-ban; torlódáskezelés az RFC 2581-ben; némely fejrészmező újraértelmezése a szolgáltatásminőség érdekében az RFC 2873-ban; továbbfejlesztett újraküldési időzítők az RFC 2988-ban; explicit torlódásjelzés az RFC 3168-ban. A teljes lista sokkal hosszabb, ami egy, az RFC-k közötti útmutató létrehozásához vezetett, természetesen egy újabb RFC formájában, ez az RFC 4614.

Minden TCP-t támogató gép rendelkezik egy TCP szállítási entitással, amely lehet egy könyvtári eljárás, egy felhasználói folyamat vagy leggyakrabban a kernel része. A TCP-folyamokat és az IP-réteg felé használható interfészeket minden esetben a TCP-entitás kezeli. A helyi folyamatoktól kapott felhasználói adatfolyamokat a TCP-entitás 64 KB-ot meg nem haladó méretű darabokra szedi szét (a gyakorlatban sokszor 1460 adatbájtos darabokra, mert így az IP- és TCP-fejrészekkel együtt is beleférnek egy Ethernet-keretbe). Az egyes darabokat önálló IP-datagramokban küldi el. Amikor egy géphez TCP-adatokat tartalmazó datagram érkezik, az a TCP-entitáshoz kerül, amely visszaállítja az eredeti bájtfolyamokat. Az egyszerűség kedvéért néha olyankor is a „TCP” szót fogjuk használni, amikor a TCP-entitásról (ami egy szoftverelem) vagy a TCP-protokollról (ami egy szabálykészlet) beszélünk, a szövegkörnyezetből azonban mindig világos lesz, hogy melyikről van szó. Például, ha azt mondjuk, hogy „a felhasználó átadja a TCP-nek az adatokat”, akkor nyilván a TCP szállítási entitásról van szó.

Az IP-réteg nem ad garanciát a datagramok helyes kézbesítésére, sem útmutatást arra, hogy a datagramokat milyen sebességgel lehet küldeni. A TCP-n múlik, hogy a datagramokat olyan sebességgel küldje, hogy a teljes kapacitást kihasználja, de ne okozzon torlódást. A TCP-re marad az időzítők kezelése és a szükség szerinti újraküldés. A helyesen megérkező datagramok is érkezhetnek rossz sorrendben, ezért a TCP felelőssége az is, hogy a datagramokat megfelelő sorrendben rakja össze üzenetekké. A fentieket röviden összefoglalva azt mondhatjuk, hogy a TCP-nek kell megfelelő teljesítőképesség mellett megteremtenie azt a megbízhatóságot, amelyet a legtöbb felhasználó megkíván, és amelyet az IP nem ad meg.

6.5.2. A TCP szolgáltatási modellje

A TCP-szolgáltatás úgy valósul meg, hogy mind a küldő, mind a fogadó létrehoz egy csatlakozónak (socket) nevezett végpontot, ahogy azt a 6.1.3. pontban tárgyaltuk. Minden csatlakozónak van egy száma, azaz csatlakozócíme, ami a hoszt IP-címéből és egy hoszton belül érvényes 16 bites számból, a port (kapu) azonosítójából tevődik össze. A port a TCP-környezetben használt elnevezése a TSAP-nek. A TCP-szolgáltatás megvalósításához egy közvetlen összeköttetést kell létesíteni a küldő- és fogadógép csatlakozói (socketjei) között. A csatlakozókat kezelő hívásokat a 6.5. ábrán foglaltuk össze.

Egy csatlakozó egyidejűleg több összeköttetés kezelésére is használható, azaz két vagy több összeköttetés közös csatlakozóban is végződhet. Az összeköttetéseket a két végükön található csatlakozók azonosítói azonosítják: (socket1, socket2). Nem használják a virtuális áramkör sorszámát vagy más azonosítót.

Az 1024 alatti portokat jól ismert vagy közismert portoknak (well-known port) hívják, és a megszokott szolgáltatások részére vannak fenntartva, amelyeket csak privilegizált felhasználók indíthatnak el (például root felhasználó UNIX-rendszereken). Például, bármely olyan folyamat, amely egy távoli levelezőszerverről szeretne leveleket letölteni, a levelezőszerver 143-as portján kapcsolatba léphet a szerveren futó IMAP-démonnal. A jól ismert portok listája megtalálható a www.iana.org -on. Eddig több mint 700 portot osztottak ki. A 6.34. ábra néhány ismertebbet sorol fel ezek közül.

6.34. ábra - Néhány közismert port

kepek/06-34.png


Az 1024 és 49 151 közötti portok is regisztrálhatók az IANA-nál, de ezeket egyszerű felhasználók is használhatják, és az alkalmazások megválaszthatják, milyen portot használjanak. Például a BitTorrent P2P-állománymegosztó alkalmazás (nem hivatalosan) a 6881–6887 portokat használja, de más portokon is futhat.

Teljes mértékben lehetséges volna, hogy a gép indításakor az FTP-démon automatikusan rákapcsolódjon a 21-es portra, az SSH démon rákapcsolódjon a 22-es portra, és így tovább. Ezzel a megoldással azonban telezsúfolnánk a memóriát olyan démonokkal, amelyek az idő jelentős részében tétlenek. A UNIX-ban ehelyett általában egyetlen, inetd-nek (Internet Daemon internetdémon) nevezett démont alkalmaznak. Az inetd egyszerre több portra kapcsolódik, és mindegyiken várja a bejövő összeköttetési kéréseket. Amikor kérés érkezik, az inetd egy új folyamatot indít el, amelyben lefuttatja a megfelelő démont, így az lekezelheti a kérést. Ezzel a megoldással elérhető, hogy (az inetd-t kivéve) minden démon csak akkor fusson, amikor munkája is van. Az inetd a beállításait tartalmazó állományból tudja, hogy mely portokat kell figyelnie. Ez lehetővé teszi a rendszergazdának, hogy a legforgalmasabb portokra (például a 80-as portra) állandó démonokat tegyen és az összes többit az inetd-re bízza.

Minden TCP-összeköttetés duplex és kétpontos. A duplex azt jelenti, hogy a forgalom egyszerre haladhat minkét irányba. A kétpontos azt jelenti, hogy minden összeköttetésnek pontosan két végpontja van. A TCP nem támogatja az adatszórást és a többesküldést.

A TCP-összeköttetésen nem üzenetfolyamok, hanem bájtfolyamok áramlanak. Az üzenethatárokat a rendszer nem őrzi meg. Például, ha a küldőfolyamat négy 512 bájtos írást hajt végre a TCP-folyamon, az a vevőhöz megérkezhet négy 512 bájtos darabban, két 1024 bájtos darabban, egy 2048 bájtos darabban (lásd 6.35. ábra) vagy akár más felosztásban is. A vevőnek nem áll módjában kideríteni, hogy az adatokat mekkora darab(ok)ban küldték, akárhogy is próbálkozik.

6.35. ábra - (a) Négy 512 bájtos szegmens, amelyeket az adó külön IP-datagramokban küld el. (b) A 2048 adatbájtot a vevő alkalmazási folyamat egyetlen read hívásban kapja meg.

kepek/06-35.png


A UNIX-os állományok is rendelkeznek ezzel a tulajdonsággal. Az állomány olvasója nem tudja megállapítani, hogy az állományt blokkonként, bájtonként vagy egyetlen darabban írták-e. A UNIX állománykezeléséhez hasonlóan a TCP-szoftver sem tud semmit a bájtok jelentéséről, és azt kitalálni sem próbálja. A TCP számára egy bájt csak egy bájt.

Amikor egy alkalmazás adatot ad át a TCP-nek, a TCP saját belátása alapján dönti el, hogy azonnal elküldi azt, vagy puffereli egy ideig, hogy így nagyobb adatmennyiséget küldhessen el egyszerre. Az alkalmazásnak azonban néha fontos, hogy az adatokat a TCP azonnal elküldje. Tegyük fel például, hogy a felhasználó egy interaktív játékban frissítések sorozatát szeretné elküldeni. Rendkívül fontos, hogy a frissítéseket a TCP azonnal továbbítsa, és ne pufferelje addig, amíg egy gyűjtemény lesz belőle. Az azonnali adatküldésre a TCP a csomagban található PUSH jelzőbit fogalmát használja. Az eredeti szándék szerint az alkalmazások a PUSH bit beállításával közölték volna a TCP-megvalósításokkal, hogy az adatot azonnal el kell küldeni. Az alkalmazások azonban nem tudják közvetlenül állítani a PUSH bitet, amikor adatot küldenek. Ehelyett különböző operációs rendszerek különböző lehetőségeket fejlesztettek ki a küldés felgyorsítására (például Windows- és Linux-rendszereken a TCP_NODELAY lehetőség).

Az internet régészeinek megemlítünk egy érdekes TCP-szolgáltatást, amely bár a protokollban megmaradt, de ritkán használják: a sürgős adatot (urgent data). Amikor egy alkalmazásnak sürgős adata van, amit azonnal fel kell dolgozni, például amikor egy interaktív felhasználó CTRL-C-t üt egy már megkezdett távoli számítás megszakítására, a küldőalkalmazás további vezérlőinformációt ad az adatfolyamhoz, és sürgős jelzéssel átadja a TCP-nek. Ennek hatására a TCP abbahagyja az adatok egybegyűjtését az adott összeköttetésre, és azonnal továbbítja az eddig összegyűlt adatot.

Amikor a sürgős adat célba ér, a vevőalkalmazás végrehajtása megszakad (UNIX-terminológiában signal-t kap), abbahagyja amit éppen csinált, és beolvassa az adatfolyamot, hogy megtalálja a sürgős adatot. A sürgős adat vége jelezve van, így az alkalmazás tudja, hogy meddig tart. A sürgős adat kezdetén ezzel ellentétben nincs jelzés, azt az alkalmazásnak kell megtalálnia.

Ez a megoldás csak egy kezdetleges jelzési rendszert biztosít, és minden más feladatot az alkalmazásra hagy. A sürgős adat potenciálisan hasznos, de nem talált alkalmazásra, így idővel elavulttá vált. Használata nem ajánlott, mivel a megvalósítások különböznek, és az alkalmazásra hagyják a jelzési módszereik kezelését. Talán a jövőbeli szállítási protokollok jobb jelzési rendszerrel lesznek felfegyverkezve.

6.5.3. A TCP-protokoll

Ebben a szakaszban a TCP-protokollt tekintjük át nagy vonalakban. A következő szakaszban először a protokoll fejrészét mezőnként vesszük vizsgálat alá.

A TCP rendelkezik egy olyan, kulcsfontosságú tulajdonsággal, amely az egész protokoll kialakítását befolyásolja: a TCP-összeköttetéseken minden bájt rendelkezik egy 32 bites sorszámmal. Amikor az internet története elkezdődött, az útválasztókat összekötő vonalak általában 56 kb/s-os bérelt vonalak voltak, ezért egy teljes sebességgel adó hoszt esetében is több mint egy hét eltelt a sorszámozás két újrakezdése között. Később látni fogjuk, hogy a mai hálózati sebességek mellett a hosztok a sorszámokat ijesztő sebességgel fogyaszthatják. A nyugták és a csúszóablakok ettől független 32 bites sorszámokat használnak, amint az a következőkből is kiderül.

A küldő és a vevő TCP-entitások az adatokat szegmensekben viszik át egymás között. A TCP-szegmensek (TCP segment) egy rögzítetten 20 bájtos fejrészből (és egy nem kötelező, opcionális részből), valamint 0 vagy több adatbájtból állnak. A TCP-szoftver dönti el, hogy a szegmensek mekkorák legyenek. Összegyűjtheti több írási utasítás adatait egyetlen szegmensbe, de egy írás adatait is eloszthatja több szegmensbe. A szegmensek méretének két korlátja van. Egyrészt, minden szegmensnek a hozzá tartozó TCP-fejrésszel együtt bele kell férnie a 65 515 bájtos IP-adatmezőbe. Másrészt, minden hálózaton meghatározzák az úgynevezett legnagyobb átvihető adategységet (Maximum Transfer Unit, MTU). Minden szegmensnek bele kell férnie mind a küldő-, mind a vevőoldalon[28] az MTU által megszabott keretekbe, hogy egyben, további darabolás nélkül lehessen továbbítani. A gyakorlatban az MTU, vagyis a szegmensméret felső korlátja általában 1500 bájt (az Ethernet-adatmező mérete).

Továbbra is lehetséges azonban a TCP-szegmenseket hordozó IP-csomagok darabolása olyan hálózati útvonalakon, ahol valamely összeköttetés MTU-értéke kisebb. Ebben a helyzetben a teljesítőképesség romlik, és egyéb más problémák is felmerülnek [Kent és Mogul, 1987]. Ehelyett a modern TCP-megvalósítások az útvonal MTU-meghatározását (path MTU discovery) alkalmazzák, amelynek a leírását az RFC 1191 tartalmazza, és amelyet az 5.5.5. szakaszban tárgyaltunk. Ez a módszer ICMP-hibaüzeneteket használ, hogy megtalálja az útvonalon található legkisebb MTU-val rendelkező összeköttetést. A TCP végül ehhez az értékhez igazítja a küldendő szegmensek méretét, hogy elkerülje a feldarabolást.

A TCP-entitások egy csúszóablakos protokoll-változatot használnak dinamikus ablakmérettel. A küldő minden szegmens feladásakor egy időzítőt is elindít. Amikor a szegmens megérkezik a célhoz, a vevő TCP-entitás visszaküld egy olyan szegmenst (adatokkal, ha van elküldendő adat, egyébként üresen), amely tartalmazza a maradék ablakméretét, és amelyben a következőnek várt szegmens sorszámával visszaigazolja az adást. Ha a küldőoldalon az időzítő a nyugta vétele előtt lejár, akkor a küldőentitás újra elküldi a szegmenst.

Annak ellenére, hogy ez a protokoll egyszerűnek hangzik, van néhány, esetenként apró buktatója, amelyeket alább fogunk megtárgyalni. A szegmensek érkezhetnek helytelen sorrendben, így előfordulhat, hogy a 3072–4095 sorszámú bájtok megérkeznek, de a vevő nem nyugtázhatja azokat, mivel a 2048–3071 sorszámú bájtok még nem érkeztek meg. Előfordulhat, hogy a szegmensek olyan hosszú ideig utaznak, hogy a küldő időzítője közben lejár és újra elküldi azokat. Előfordulhat az is, hogy az újraküldés során az adó nem ugyanazokat a bájttartományokat küldi el az egyes szegmensekben, ezért gondos nyilvántartásra van szükség ahhoz, hogy a TCP-entitás nyomon követhesse az adott pillanatig helyesen vett bájtokat. Ez azért kivitelezhető megoldás, mert a folyam minden bájtja egyedi eltolással (offset) rendelkezik.

A TCP-nek felkészülten kell szembenéznie ezekkel a problémákkal, és hatékony módon kell megoldania azokat. A tervezők jelentős mennyiségű erőfeszítést fordítottak arra, hogy a TCP-folyamok teljesítménye még hálózati gondok esetén is optimális legyen. A későbbiekben ismertetni fogunk néhány olyan algoritmust, amelyet sok TCP-megvalósítás használ.

6.5.4. A TCP-szegmens fejrésze

A 6.36. ábrán láthatjuk a TCP-szegmens felépítését. Minden szegmens egy fix kiosztású 20 bájtos fejrésszel kezdődik, amit fejrészopciók követhetnek. Ezek után – ha van – legfeljebb  bájt adat állhat. A kivonandók közül az első 20 bájt az IP-, a második 20 bájt a TCP-fejrészt jelenti. Az adatot nem tartalmazó szegmensek érvényesek, általában nyugtázásra és vezérlőszegmensként használják azokat.

6.36. ábra - A TCP-fejrész

kepek/06-36.png


Elemezzük most mezőről mezőre a TCP-fejrészt! A Forrásport és Célport mezők azonosítják az összeköttetés helyi végpontjait. A TCP-portszám és a hoszt IP-címe együtt egy egyedi 48 bites azonosítót jelent a végpontok megkülönböztetésére. A forrás és a cél végpontok együtt azonosítják az összeköttetéseket. Ezt az összeköttetés-azonosítót 5-ösnek (5 tuple) hívják, mert 5-féle információt tartalmaz: a protokollt (TCP), a forrás IP-címét, a forrás portszámát, valamint a címzett IP-címét és a címzett portszámát.

A Sorszám (sequence number) és Nyugtaszám (acknowledgement number) mezők szerepe szokásos. Jegyezzük meg, hogy az utóbbi a következő várt bájt sorszámát tartalmazza, nem az utolsó rendben beérkezett bájtét. Mivel a nyugta egyetlen számmal minden vett adatot nyugtáz, ezért halmozott nyugtáról (cumulative acknowledgement) van szó. Elveszett adat esetén nem léptetik. Mindkét mező 32 bit széles, mivel a TCP-folyamban minden adatbájt sorszámot visel.

A TCP-fejrész hossza (TCP header length) mondja meg, hány 32 bites szóból áll a TCP-fejrész. Ez az információ azért szükséges, mert a fejrész mérete az Opciók (options) mező változó hossza miatt szintén változó. Tulajdonképpen ez a mező jelzi az adat kezdetét (32 bites szavakban mérve) a szegmensen belül, de mivel ez egyben a fejrész szavakban mért hossza is, a végeredmény ugyanaz.

Ezután egy használaton kívüli 4 bites mező következik. A TCP jól átgondolt tervezésére szolgál tanúbizonyságul, hogy ezek a bitek 30 éve változatlanul használaton kívül vannak (és az eredeti 6-ból csupán 2-t használtak fel). Kevésbé jól sikerült protokollokban már fölhasználták volna az eredeti változat hibáinak kiküszöbölésére.

Ezt nyolc egybites mező követi. A CWR és ECE mezőket torlódás jelzésére használják, amikor az RFC 3168-ban specifikált ECN-t (Explicit Congestion Notification – explicit torlódásjelzés) alkalmazzák. Ha a vevő a hálózattól torlódási jelzést kap, az ECE bittel ECN-Echo jelzést küld a TCP-küldő számára, ami azt jelenti, hogy a küldőnek le kell lassítania. A CWR bittel a TCP-küldő a TCP-vevő felé jelzi a Torlódási ablak csökkentve (Congestion Window Reduced) eseményt, így a vevő tudni fogja, hogy az adó lelassított, és megszüntetheti az ECH-Echo küldését. A TCP torlódáskezelésén belül az ECN szerepét a 6.5.10. szakaszban mutatjuk be.

Az URG bit értéke 1, ha Sürgősségi mutatót használt. A Sürgősségi mutató a sürgős adatnak bájtban mért eltolt helyét jelzi a jelenlegi bájtsorszámhoz viszonyítva. Ez a mechanizmus a megszakítás üzeneteket helyettesíti. Mint korábban említettük, ez a végsőkig leegyszerűsített módszer lehetőséget ad a küldőnek, hogy jelzést küldjön a vevő felé anélkül, hogy a TCP-nek külön megszakításokkal kelljen foglalkoznia, de ritkán használják.

Az ACK bit 1 értéke jelzi a Nyugtaszám mező érvényességét. A Nyugtaszám mező majdnem minden csomag esetén érvényes. Ha , a szegmens nem tartalmaz nyugtát, tehát a Nyugtaszám mező figyelmen kívül hagyható.

A PSH bit jelzi a késedelem nélküli adat továbbítását (PUSH). Ez egyben a vevő felé is udvarias kérést jelent: ne pufferelje a vett adatot (amit amúgy hatékonysági okokból megtehetne), hanem azonnal továbbítsa az alkalmazás felé.

Az RST bit egy hoszt összeomlása, vagy más okból összezavart összeköttetés helyreállítására szolgál. Ezenkívül érvénytelen szegmens elutasítására és összeköttetés létesítésének megtagadására is használják. Rendszerint ha valaki értéket viselő szegmenst kap, akkor felkészülhet valamilyen probléma megoldására.

A SYN bit összeköttetés létesítésére szolgál. Az összeköttetés-kérésben és jelzi, hogy ráültetett Nyugtaszám mezőt nem használnak. Az összeköttetés-kérésre adott válaszban már van nyugta, így abban és . Lényegében a SYN bit jelzi a CONNECTION REQUEST és CONNECTION ACCEPTED üzeneteket, melyeket az ACK bit különbözteti meg egymástól.

A FIN bit szolgál egy összeköttetés bontására. Jelzi, hogy a küldőnek nincs több továbbítandó adata. Az összeköttetés bontása után azonban még korlátlan ideig folytathatja az adatok vételét. Mind a SYN, mind a FIN szegmensek rendelkeznek sorszámokkal, így garantált a helyes sorrendben történő feldolgozás.

A TCP forgalomszabályozása változó méretű csúszóablakkal történik. Az Ablakméret mező határozza meg, hogy a Nyugtaszám mező által mutatott bájttal kezdődően hány bájtot lehet elküldeni. Az Ablakméret 0 értéke is érvényes, és azt jelzi, hogy a Nyugtaszám bájtnál eggyel kisebb sorszámú bájtok mind rendben megérkeztek, viszont a vevőnek nagy szüksége van egy kis pihenésre és köszöni szépen, több adatot jelenleg nem kér. Később azonos Nyugtaszám értékkel és nem nulla Ablakméret mezővel ellátott szegmenssel engedélyezni lehet az adatok küldését.

A 3. fejezet protokolljaiban a vett keretek nyugtázása és új keretek küldésének engedélyezése szorosan összefonódott egymással. Ez annak a következménye, hogy az egyes protokollokban használt ablakok mérete rögzített volt. A TCP-ben a nyugtázás teljesen szétválik a további adatküldés engedélyezésétől. A vevő tulajdonképpen azt is mondhatja, hogy „k-ig megkaptam a bájtokat, de most egyelőre nem kérek többet”. Ez a szétválasztás (amely gyakorlatilag egy változó méretű ablakot jelent) megnöveli a rendszer rugalmasságát, ezért később részletes vizsgálat alá vesszük.

A nagyobb megbízhatóság érdekében van még egy Ellenőrző összeg is a fejrészben. Ez ellenőrzi a fejrész, az adat és az álfejrész épségét, pontosan úgy, ahogy az UDP-nél láttuk. Az egyetlen kivétel, hogy a protokollazonosító a TCP esetén 6, és az ellenőrző összeg kötelező.

Az Opciók mezőt a szabályos fejrészben nem szereplő lehetőségek megvalósítására tervezték. Sok opciót definiáltak, és néhányat gyakran használnak. Az Opciók mező változó hosszúságú, de mindig 32 bit többszöröse, szükség esetén nullákkal kitöltve. Maximális mérete 40 bájt lehet, elfoglalva a legnagyobb TCP-fejrészt, ami lehetséges. Némely opciót összeköttetés felépítésekor küldenek, hogy egyezkedjenek, vagy információt közöljenek a képességekről a távoli féllel. Más opciókat az összeköttetés élettartama alatt minden továbbított csomag tartalmaz. Minden opció TLV (Type-Length-Value – típus-hossz-érték) kódolású.

Egy széles körben használt lehetőség lehetővé teszi a hosztoknak, hogy meghatározzák az MSS-t (Maximum Segment Size – legnagyobb szegmensméret), amit hajlandók elfogadni. Nagy szegmensek használata hatékonyabb a kicsik alkalmazásánál, hiszen a 20 bájtos fejrész több adathoz tartozik, viszont kis hosztok esetleg képtelenek nagyon nagy szegmensek kezelésére. Összeköttetés létesítésekor minden hoszt megadhatja a saját maximumát, és tudomást szerezhet a partnere maximum értékéről. Ha egy hoszt nem használja ezt a lehetőséget, az alapértelmezés 536 bájtos adat mező. Minden internetre csatlakozó hosztnak el kell fogadnia  bájt hosszúságú szegmenseket, viszont a két irányban nem kell azonosnak lenni a maximális hosszaknak.

Nagy sávszélességgel vagy nagy késleltetéssel, esetleg mindkettővel rendelkező vonalak számára gyakran problémát jelent a 16 bitnek megfelelő 64 KB ablakméret. Például egy OC-12 vonalon (durván 600 Mbit/s) kevesebb mint 1 ms ideig tart egy 64 KB-os tele ablak továbbítása. Ha a teljes átviteli késleltetés 50 ms (tipikus kontinensek közötti fényvezető szálakra), a küldő a várakozási idő 98 százalékában tétlenül várakozik a nyugtára. Nagyobb ablakméret használata esetén a küldő tovább pumpálhatná az adatot. A skálatényező (window scale) opció használata lehetővé teszi a küldő és a fogadó számára, hogy összeköttetés létesítésekor megegyezzenek egy ablak-skálatényezőben. Ez a szám mindkét oldalon lehetővé teszi az Ablakméret mező legfeljebb 14 bites balra történő eltolását, így legfeljebb  bájtos ablakok használatát. A legtöbb TCP-implementáció támogatja ezt a lehetőséget.

Az időbélyeg (timestamp) opció egy időbélyeget hordoz, melyet a küldő helyez a csomagba és a vevő visszaküldi. Minden csomagban megtalálható, hiszen használatáról az összeköttetés létesítése során megállapodnak, és a körülfordulási idő mintáinak számítására használják, hogy megbecsüljék, mikor veszett el egy csomag. A 32 bites sorszám logikai kiegészítésére is használják, ugyanis a gyors összeköttetéseken a sorszámok hamar átfordulnak, félreértést okozva az új és régi sorszámok között. A PAWS (Protection Against Wrapped Sequence numbersátfordult sorszámok elleni védelem) módszer a probléma megoldása érdekében eldob minden olyan szegmenst, amely régi időbélyeggel érkezett.

Végül a SACK (Selective ACKnowledgement – szelektív nyugtázás) opció lehetővé teszi a vevőnek, hogy a küldőt tájékoztassa a vett sorszámokról. Ez az opció kiegészíti a Nyugtaszám mezőt, és csomagvesztés után használják abban az esetben, ha további adatok (vagy másolatok) viszont érkeztek. A további adatokat a fejrész Nyugtaszám mezeje nem mutatja, hiszen az csak sorrendben a következő, várt bájtokat mutatja. A SACK segítségével a küldő közvetlenül értesül arról, hogy a vevő milyen adatokat kapott, és így meghatározhatja, melyeket kell újraküldeni. A SACK-ot az RFC 2108 és az RFC 2883 definiálja, és egyre gyakrabban alkalmazzák. A SACK használatát a torlódáskezeléssel kapcsolatban a 6.5.10. szakaszban tárgyaljuk.

6.5.5. TCP-összeköttetés létesítése

Az összeköttetések létesítésére a 6.2.2. szakaszban tárgyalt „háromutas kézfogás” technikáját alkalmazzák a TCP-ben. Az összeköttetés létesítéséhez az egyik fél, nevezzük szervernek, passzívan várakozik a bejövő kérésekre a listen és accept primitívek végrehajtásával. Ehhez megjelölhet egy adott forrást, vagy nem jelöl ki senkit.

A másik oldal, melyet kliensnek hívunk, végrehajtja a connect primitívet, melynek hívásakor rögzíti az IP-címet, a használni kívánt port számát, az általa megengedett maximális TCP-szegmens méretét és esetleg felhasználói adatot (például jelszót) is átad. A connect primitív elküld egy TCP-szegmenst és értékkel, majd választ vár.

Amikor a szegmens megérkezik a rendeltetési helyre, az ottani TCP-entitás ellenőrzi, hogy létezik-e egy folyamat, amely a célport mezőben meghatározott porton végrehajtotta a listen primitívet. Ha nem, válasszal elutasítja az összeköttetés-kérést.

Ha valamilyen folyamat figyeli a megadott portot, akkor az megkapja a beérkező TCP-szegmenst, és rajta múlik, hogy elfogadja-e vagy visszautasítja az összeköttetést. Ha elfogadja, egy nyugtázó szegmenst küld vissza. A 6.37.(a) ábrán láthatjuk az elküldött TCP-szegmensek sorozatát normális esetben. Figyeljük meg, hogy a SYN szegmens egy bájtnak megfelelő sorszámot fogyaszt, így egyértelműen nyugtázható.

6.37. ábra - (a) TCP-összeköttetés létesítése normális esetben. (b) Egyidejű összeköttetés létesítése mindkét oldalon.

kepek/06-37.png


Abban az esetben, ha a két hoszt egyszerre próbál összeköttetést létesíteni ugyanazon két csatlakozó (socket) között, a 6.37.(b) ábrán látható eseménysorozat alakul ki. Ennek eredményeképpen csak egyetlen összeköttetés jön létre, nem kettő, mivel az összeköttetéseket végpontjaik azonosítják. Ha a művelet során létrejövő első összeköttetés azonosítói , és a második is ilyen azonosítóval születik meg, csak egyetlen azonosítójú bejegyzés keletkezik a táblázatban.

Emlékezzünk vissza, hogy az összeköttetés kezdő sorszámát minden hosztnak úgy kell megválasztania, hogy az lassan körbejáró legyen, nem pedig állandó, például 0. Ezt a szabályt betartva lehet védekezni a késleltetett kettőzött csomagok ellen, ahogy azt a 6.2.2. szakaszban leírtuk. Eredetileg ezt egy óraalapú megoldással valósították meg, ahol az óra 4 mikromásodpercenként lépett előre.

A „háromutas kézfogás”-nak azonban van egy sérülékenysége, ugyanis a figyelő folyamatnak emlékeznie kell a saját a sorszámára mielőtt visszaküldené a saját SYN-szegmensét. Ez azt jelenti, hogy egy rosszindulatú küldő lekötheti egy hoszt erőforrásait azáltal, hogy folyamatosan SYN-szegmenseket küld, és nem fejezi be az összeköttetés felépítését. Ezt a támadást SYN-elárasztásnak (SYN flood) nevezzük, és az 1990-es években sok webszervert megbénított.

Egy védekezés az ilyen támadások ellen a SYN-sütik (SYN cookies) használata. Ahelyett, hogy a hoszt megjegyezné a sorszámot, kriptográfiai módszerrel választ sorszámot, majd a soron következő szegmensben elküldi, és a sorszámot elfelejti. Ha a „háromutas kézfogás”-nál a válasz megjött, akkor a hoszt visszakapja a sorszám eggyel növelt értékét. Ezután a hoszt a helyes sorszámot újra legenerálja ugyanazzal a kriptográfiai módszerrel, feltéve, hogy a bemeneti paramétereket ismeri, amelyek például a másik hoszt IP-címe és portszáma, valamint egy helyi titkos kulcs. Ez az eljárás lehetővé teszi, hogy a hoszt leellenőrizze a nyugtázott sorszám helyességét anélkül, hogy a sorszámra külön emlékeznie kellene. Azért pár hiányosság itt is van, például a TCP-opciókat nem tudja kezelni, ezért a SYN-sütiket csak olyan hosztok esetén célszerű használni, amelyek SYN-elárasztásnak vannak kitéve. Mindazonáltal ez egy érdekes csavarás az összeköttetés létesítése területén. További információkhoz lásd az RFC 4987-et, valamint Lemon [2002] művét.

6.5.6. TCP-összeköttetés lebontása

Bár a TCP-összeköttetések duplexek, hogy megérthessük az összeköttetések bontásának módját, legjobb két szimplex összeköttetésnek tekinteni azokat. Mindkét szimplex összeköttetés a másiktól függetlenül lebontható. Egy összeköttetés bontásához bármelyik fél küldhet egy értékű TCP-szegmenst, amivel jelzi, hogy nem szándékozik több adatot küldeni. Amint a FIN nyugtája megérkezik, ez az irány lezárul. A másik irányban azonban ettől függetlenül korlátlanul folyhat adatátvitel. Amikor mindkét irányt lezárták, az összeköttetés befejeződött. Normális esetben egy összeköttetés bontásához négy TCP-szegmens szükséges, egy FIN és egy ACK mindkét irányban. Az első ACK és a második FIN viszont elhelyezhető ugyanabban a szegmensben is, így összesen csak háromra van szükség.

Hasonlóan a telefonhívásokhoz, ahol mindkét ember egyszerre köszön el és teszi le a kagylót, itt is küldhet mindkét fél egy időben FIN szegmenst. Ezeket a szokásos módon nyugtázzák, majd az összeköttetés befejeződik. Valójában nincs lényeges eltérés az egyszerre vagy egymás után történő összeköttetés-bontások között.

A „két hadsereg” probléma elkerülésére időzítőket használnak. Ha egy FIN-re nem érezik válasz két csomag-élettartamnyi idő alatt, a FIN küldője bontja az összeköttetést. A másik fél végül észreveszi, hogy már senki sem figyel rá, az általa indított időzítő is lejár. Bár ez a megoldás nem tökéletes, az tény, hogy tökéletes megoldás elméletben sem létezhet, ezért ennek elégnek kell lenni. A gyakorlatban ritkán merülnek föl problémák.

6.5.7. A TCP összeköttetés-kezelésének modellje

Összeköttetések létesítését és bontását véges automatával is modellezhetjük. A gép 11 állapotát a 6.38. ábrán láthatjuk. Minden állapotban az események bizonyos halmaza érvényes. Ha érvényes esemény történik, lejátszódik valamilyen tevékenység, más esemény hatására hibajelzés keletkezik.

6.38. ábra - A TCP összeköttetés-kezelő véges állapotú gép állapotai

kepek/06-38.png


Minden összeköttetés CLOSED állapotból indul. Akkor hagyja el ezt az állapotot, amikor passzív módon (listen) vagy aktív módon (connect) összeköttetést próbál létesíteni. Ha a másik fél az ellenkező primitívet hajtja végre, az összeköttetés felépül és ESTABLISHED állapotot vesz fel. Bármelyik fél kezdeményezheti az összeköttetés bontását. Amikor ez lezajlott, az állapot ismét CLOSED lesz.

Magát a véges állapotú gépet a 6.39. ábrán láthatjuk. Vastag vonalak mutatják egy aktív kliens összeköttetés-létesítési folyamatát egy passzív szerverrel, a folytonos vonalak a kliensre, a szaggatottak a szerverre vonatkoznak. A vékony vonalak váratlan eseményeket jeleznek. A 6.39. ábrán az összes nyílhoz tartozik egy esemény/tevékenység pár. Az esemény lehet egy felhasználó által végrehajtott rendszerhívás (connect, listen, send vagy close), egy szegmens érkezése (SYN, FIN, ACK vagy RST), vagy egy esetben a kétszeres maximális csomagélettartamra beállított időzítő lejárása. A tevékenység lehet vagy egy vezérlőszegmens (SYN, FIN vagy RST) elküldése, vagy semmi (ezt „–” jelöli). A megjegyzések zárójelben láthatók.

6.39. ábra - A TCP-összeköttetést kezelő véges állapotú gép. A vastag folytonos vonalak a kliens szokásos állapotátmenetei. A vastag szaggatott vonalak a szerver szokásos állapotátmenetei. A vékony vonalak a rendkívüli események. Minden átmenet címkéje az átmenetet kiváltó esemény és az átmenet által okozott tevékenység, „/” jellel elválasztva

kepek/06-39.png


Az ábrát úgy a legkönnyebb megérteni, ha először végigkövetjük a kliens állapotátmeneteit (a vastag folytonos vonalakat), majd a szerver állapotátmeneteit (a vastag szaggatott vonalakat) is sorra végignézzük. Amikor az egyik alkalmazási program a kliensgépen kiad egy connect kérést, a helyi TCP-entitás létrehoz egy új összeköttetés-rekordot, amelyet SYN SENT állapotúnak jelöl meg, és elküld egy SYN szegmenst. Fontos megjegyezni, hogy egyszerre több összeköttetés felépítése, illetve fenntartása lehet folyamatban, több különböző alkalmazás részére. Az állapotok emiatt külön-külön tartoznak az egyes összeköttetésekhez és a TCP-entitás az összeköttetés-rekordokban tartja nyilván azokat. Amikor a SYN+ACK megérkezik, akkor a TCP elküldi a „háromutas kézfogás” utolsó ACK szegmensét és az ESTABLISHED állapotba viszi át az összeköttetést. Ezután szabadon lehet adatokat küldeni és fogadni.

Amikor egy alkalmazás befejezte tevékenységét, végrehajtja a close primitívet, melynek hatására a helyi TCP-entitás egy FIN szegmenst küld, majd az ehhez tartozó nyugtára (ACK) vár (szaggatott téglalap aktív lebontás megjegyzéssel). Amikor megérkezik az ACK, az új állapot FIN WAIT 2 lesz, és az összeköttetés egyik irányban befejeződik. Amikor a másik fél is bont, egy FIN szegmens érkezik, amire a helyi entitás nyugtát küld. Ekkorra mindkét fél lebontotta az összeköttetést, de a TCP még a maximális csomagélettartam kétszereséig várakozik, így még akkor is garantálható, hogy az összeköttetés összes csomagja kihal, ha esetleg egy nyugta elveszett volna. Amikor az időzítő lejár, a TCP törli az összeköttetés bejegyzését.

Most vizsgáljuk meg az összeköttetés kezelését a szerver szemszögéből. A szerver listen hívást ad ki és csöndben figyeli, hogy ki bukkan föl. Amikor beérkezik egy SYN szegmens, nyugtázza, és SYN RCVD állapotba lép. Amint a szerver SYN szegmensére is nyugta érkezik, a „háromutas kézfogás” protokoll véget ér és a szerver ESTABLISHED állapotba kerül. Megkezdődhet az adatátvitel.

Ha a kliens végzett tennivalóival, close hívást ad ki, melynek hatására FIN szegmens érkezik a szerverhez (szaggatott vonalas téglalap passzív lebontás felirattal). A szerver ezután megszakítást (signal) kap. Amikor a szerver is végrehajtja a close primitívet, a TCP-entitás FIN szegmenst küld a kliensnek. Amint a kliens nyugtája megjelenik, a szerver bontja az összeköttetést és törli a hozzá tartozó bejegyzést.

6.5.8. A TCP-csúszóablak

Amint azt már korábban is említettük, a TCP-ben az ablakkezelés különválasztja a helyesen vett szegmensek nyugtázását és a vevő pufferkezelését. Tegyük fel például, hogy a vevő 4096 bájtos pufferrel rendelkezik (6.40. ábra). Ha a küldő egy 2048 bájtos szegmenst küld el, ami rendben meg is érkezik, a vevő nyugtázza a szegmenst. Mivel azonban most csak 2048 bájt szabad pufferterülete van (amíg az alkalmazás ki nem olvassa a lefoglalt pufferterület egy részét vagy egészét) bejelenti, hogy a következő bájttól kezdve 2048 bájtos ablakot használ.

6.40. ábra - A TCP ablakkezelése

kepek/06-40.png


Most a küldő újabb 2048 bájtot továbbít, amit a vevő nyugtáz, továbbá közli, hogy az ablakméret 0 bájt. A küldőnek le kell állnia, amíg a fogadóhoszton futó alkalmazói folyamat el nem távolít valamennyi adatot a pufferből, amikor is a TCP egy nagyobb ablak használatát jelentheti be, és a küldő több adatot küldhet.

Amikor az ablakméret 0 bájt, a küldő normális esetben nem küldhet szegmenst, azonban van két kivétel. Először is a sürgős adatot továbbíthatja, hogy lehetővé tegye például a távoli gépen futó folyamat megszakítását. Másodszor, a küldő elküldhet egy egybájtos szegmenst, kényszerítve a vevőt, hogy közölje vele a következő várt bájt sorszámát és az ablakméretet. Ezt a csomagot ablaktesztelésnek (window probe) nevezzük. A TCP-szabvány explicit módon biztosítja ezt a lehetőséget, hogy elkerülje a holtpontot, amennyiben egy ablakméretet közlő szegmens elveszne.

A küldő nem köteles azonnal továbbítani az alkalmazástól kapott adatot. A vevő sem köteles azonnal nyugtázni a beérkezett szegmenseket. Például a 6.40. ábrát tekintve, amikor beérkezett az első 2 KB adat, a TCP, tudva, hogy 4 KB-os ablak áll rendelkezésére, teljesen korrekt módon járna el, ha az adatot újabb 2 KB beérkezéséig a pufferben tárolná, hogy 4 KB rakománnyal tudja továbbítani a szegmenst. Ezt a szabadságot a teljesítőképesség fokozásában lehet kamatoztatni.

Vegyünk egy összeköttetést egy távoli terminállal, például SSH vagy telnet használatával, ami minden billentyűleütésre reagál. Legrosszabb esetben, amikor megérkezik egy karakter a küldő TCP-entitáshoz, az létrehoz egy 21 bájtos szegmenst, amelyet átad az IP-nek, hogy 41 bájtos IP-datagramként továbbítsa. A vevőoldali TCP azonnal visszaküld egy 40 bájtos nyugtát (20 bájtnyi TCP-fejrész és újabb 20 bájtnyi IP-fejrész). Később, mikor a távoli terminál beolvasta a kapott bájtot, a TCP egy bájttal jobbra mozdítja az ablakot, és ezt közli a küldővel is. Ez a csomag szintén 40 bájtos. Végül, mikor a távoli terminál feldolgozta a karaktert, egy 41 bájtos csomag segítségével megjeleníti a karaktert a helyi képernyőn. Összesen négy szegmens továbbítása, azaz a sávszélesség 162 bájtja szükséges minden egyes begépelt karakterhez. Amikor a sávszélesség az igényeltnél kisebb, nem célszerű így gazdálkodni.

A fenti helyzet optimalizálására több TCP-implementációban alkalmazzák a késleltetett nyugtázást (delayed acknowledgements). Az ötlet az, hogy késleltessük a nyugták és ablakméret-információk elküldését 500 ms-ig azt remélve, hogy egy visszaküldendő adatcsomagba ágyazva ingyen elküldhetjük azokat. Ha feltételezzük, hogy a terminál fél másodpercen belül küld visszajelzést, csak egyetlen 41 bájtos csomagot kell elküldeni a távoli felhasználónak. Az elküldött csomagok száma és a fölhasznált sávszélesség így a felére csökken.

Bár a késleltetett nyugtázás csökkenti a hálózat vevő által okozott terhelését, a küldő a sok rövid csomag (például az egyetlen adatbájtot tartalmazó 41 bájtos csomagok) küldözgetésével még mindig pazarlóan gazdálkodik. Az ennek csökkentésére kidolgozott módszer a Nagle-féle algoritmus [Nagle, 1984] néven ismert. Nagle ötlete egyszerű: amikor a küldőhöz kis darabokban érkezik az adat, csak az elsőt továbbítja, a többit addig puffereli, amíg az elküldött darab nyugtája meg nem érkezik. Ezután a pufferben tárolt összes adatot egyetlen TCP-szegmensben elküldi, és újra kezdi a pufferelést, amíg a szegmens nyugtája meg nem érkezett. Így tehát csak egyetlen kis csomag lehet kinn egy időben. Ha a körülfordulási idő alatt az alkalmazás sok kisméretű adatot küld, akkor Nagle algoritmusa a sok kis darabot egy szegmensbe rakja, jelentősen csökkentve a használt sávszélességet. Az algoritmus továbbá kimondja, hogy egy új szegmenst kell küldeni, ha elég adat csordogált be, hogy megtöltsön egy maximális méretű szegmenst.

A Nagle-féle algoritmus széles körben elterjedt a TCP-implementációkban, de némely esetben szerencsésebb kikapcsolni. Például az interneten futtatott interaktív játékok esetén a játékos tipikusan rövid, frissítő csomagok tömkelegét szeretné küldeni mihamarabb. A frissítéseket bevárva, majd löketszerűen továbbítva a játékélményt erősen lerontaná, és sok elégedetlen felhasználót eredményezne. Egy még alapvetőbb probléma is felbukkanhat, ha Nagle algoritmusát a késleltetett nyugtákkal használjuk, ugyanis ideiglenes holtpontok alakulhatnak ki: a vevő az adatokra vár, hogy a nyugtát ráültethesse, a küldő pedig a nyugtára vár, hogy több adatot küldjön. Ez az összeférhetetlenség késleltetheti a weboldalak letöltését. A fenti problémák miatt Nagle algoritmusa kikapcsolható (ezt TCP_NODELAY opciónak nevezik). Mogul és Minshall [2001] tárgyalja a problémát, és más megoldásokat is ad.

Egy másik probléma, ami le tudja rontani a TCP teljesítőképességét, a buta ablak jelenség (silly window syndrome) [Clark, 1982]. Ez a probléma akkor merül föl, amikor a küldő TCP-entitás nagy blokkokban kapja az adatokat, de a fogadóoldalon futó interaktív alkalmazás bájtonként olvassa be. A probléma könnyebb megértésére vegyük szemügyre a 6.41. ábrát. Kezdetben a vevő TCP-puffere tele van, és a küldő ezzel tisztában van (tehát az ablakmérete 0). Ezután az interaktív alkalmazás beolvas egy karaktert a TCP-adatfolyamról. Megörül ennek a fogadó TCP-entitás, elküldi az új ablakméretet a küldőnek, mondván, hogy minden rendben, egy bájtot küldhet. A küldő teljesíti ezt a kívánságot is, egy bájtot elküld. A puffer ismét tele lesz, így a vevő nyugtázza a bájt érkezését, de egyidejűleg közli, hogy az ablak mérete 0. Ez a viselkedés így mehet örökké.

6.41. ábra - A buta ablak jelenség

kepek/06-41.png


Clark megoldása szerint nem szabad megengedni a vevőnek, hogy 1 bájt változásra ablakméret-frissítést küldjön. Ehelyett mindaddig várakoztatni kell, amíg elegendő hely szabaddá nem válik, és inkább azt kell a küldővel közölni. Pontosabban a fogadó nem küldhet addig ablakméret-információt, amíg az összeköttetés létesítésekor bejelentett maximális szegmensméretet nem tudja kezelni, vagy félig ki nem ürült a puffer. A két korlát közül a szabad hely méretének a kisebbet kell elérnie. Ezenkívül a küldő is segíthet azzal, hogy nem küld apró szegmenseket. Ehelyett addig el kell halasztani a továbbítást, amíg elég hely össze nem gyűlt az ablakban, hogy egy teljes szegmenst elküldhessen, vagy legalább olyan hosszút, mint a vevő ablakméretének fele.

A Nagle-féle algoritmus és Clark megoldása a buta ablak problémára kiegészítik egymást. Nagle olyan problémát próbált megoldani, melyben a küldőalkalmazás bájtonként adta át az adatokat a TCP-nek. A Clark által megoldott problémában a vevőalkalmazás kérte bájtonként az adatokat a TCP-től. Mindkét megoldás helyes és képesek együtt működni. Az a cél, hogy a küldő ne adjon apró szegmenseket, és a vevő se kérjen kicsiket.

A fogadó TCP-entitás tovább növelheti a teljesítőképességet azon túl, hogy csak nagyobb egységekben frissíti az ablakot. A küldő TCP-entitáshoz hasonlóan a fogadónak is van lehetősége az adatok pufferelésére, így az alkalmazás egy read hívását addig blokkolhatja, amíg egy nagyobb adatblokkot nem tud átadni. Ezzel csökken a TCP-hívások száma, vele együtt a többletráfordítás (overhead) is. Ez természetesen a válaszidőt is megnöveli, de az állomány átviteléhez hasonló nem interaktív alkalmazások esetében a hatékonyság ellensúlyozhatja az egyes kérések megnövekedett válaszidejét.

Egy másik feladat a vevő számára a rossz sorrendben érkező szegmensek kezelése. A vevő egészen addig fogja pufferelni az adatokat, amíg azokat egyben, helyes sorrendben át nem tudja adni az alkalmazásnak. Ha az alkalmazás pufferelés helyett eldobná a nem soron következő szegmenseket, a pazarláson kívül semmi különös nem történne, hiszen a küldő úgyis újraküldené őket.

Nyugta természetesen csak akkor küldhető, ha a nyugtázott bájtig terjedő összes adat megérkezett. Ezt a megoldást halmozott nyugtázásnak (cumulative acknowledgement) hívjuk. Ha a vevő a 0, 1, 2, 4, 5, 6 és 7 szegmenseket kapja meg, mindent nyugtázhat a 2. szegmens utolsó bájtjáig (azt is beleértve). Amikor a küldő időzítése lejár, újraküldi a 3. szegmenst. Ha a vevő megtartotta a 4–7. szegmenseket, a 3. szegmens vétele után a 7. szegmens utolsó bájtjáig mindet nyugtázhatja.

6.5.9. A TCP időzítéskezelése

A TCP (elvileg) több időzítőt használ feladata elvégzéséhez. Ezek közül legfontosabb az RTO (Retransmission TimeOut – ismétlési időzítő) . Egy szegmens elküldésekor az ismétlési időzítőt is elindítja. Ha a szegmensre az időzítő lejárta előtt nyugta érkezik, az időzítő leáll. Ha viszont az időzítő még a nyugta beérkezése előtt lejár, a TCP a szegmenst újraküldi (és az időzítőt is újraindítja). Fölmerül a kérdés: milyen hosszú ideig fusson az időzítő?

Ez sokkal nehezebb kérdés a szállítási rétegben, mint amilyen az adatkapcsolati rétegben (például a 802.11 esetén) volt. Az adatkapcsolati rétegben a várható késleltetés mikroszekundum nagyságrendű és nagymértékben kiszámítható volt (vagyis kicsi volt a szórása), így az időzítőt úgy lehetett beállítani, hogy kevéssel a nyugta várt megérkezése után járjon le, ahogy az a 6.42.(a) ábrán látható. Az adatkapcsolati rétegben a nyugták (a torlódások hiánya miatt) ritkán késnek, ezért ha egy nyugta nem érkezik meg a várt időn belül, az általában azt jelenti, hogy vagy az adatkeret, vagy a nyugta elveszett.

6.42. ábra - (a) A nyugtabeérkezési idők sűrűségfüggvénye az adatkapcsolati rétegben. (b) A nyugtabeérkezési idők sűrűségfüggvénye a TCP-ben

kepek/06-42.png


A TCP ettől gyökeresen eltérő környezettel szembesül. A TCP-nyugták körülfordulási idejének sűrűségfüggvénye sokkal inkább a 6.42.(b) ábrára hasonlít, mint a 6.42.(a) ábrára. A körülfordulási idő nagyobb és a szórása is tágabb. A rendeltetési helyig terjedő körülfordulási időt nehéz megállapítani. Még ha ismert is, az időzítés időtartamáról is nehéz dönteni. Ha az időtartamot túl rövidre állítjuk be, mondjuk a 6.42.(b) ábra értékére, felesleges újraküldések történnek, az internetet haszontalan csomagok terhelik. Ha túl hosszúra állítjuk , a teljesítőképesség egy csomag elvesztekor a hosszú újraküldési késleltetés miatt csökken. Ezenkívül a nyugta érkezési idejének átlaga és szórásnégyzete is jelentősen változhat pár másodperc alatt, ha torlódás lép fel vagy szűnik meg.

A megoldás az, ha erősen dinamikus algoritmust használunk, ami a hálózat teljesítőképességének folyamatos mérése alapján állandóan újra beállítja az időintervallumot. A TCP-ben általánosan használt algoritmus Jacobson [1988] nevéhez fűződik, és a következőképpen működik. A TCP minden összeköttetés részére fenntart egy SRTT-nek (Smooth Round-Trip Time – csillapított körülfordulási idő) nevezett változót, ami a szóban forgó rendeltetési helyig terjedő körülfordulási idő legjobb jelenlegi becsült értéke. Egy szegmens elküldésekor egy időzítőt is elindít a TCP, hogy egyrészt megmérje, mennyi idő alatt ér vissza a nyugta, másrészt, ha túl sokáig késik, újraküldhesse a csomagot. Ha a nyugta az időzítő lejárása előtt visszaér, a TCP megméri, hogy mennyi ideig tartott R. Ezután újraszámolja az SRTT értékét a következők szerint:

A képletben egy csillapítási tényező, amely azt határozza meg, hogy a régi értékeket milyen gyorsan felejtsük el. Tipikusan . Ez a formula EWMA (Exponentially Weighted Moving Average – exponenciálisan súlyozott mozgóátlag) néven ismert, és tulajdonképpen egy aluláteresztő szűrő, ami a mintákban található zajt távolítja el.

Még az SRTT jó értékének tudatában sem triviális egy megfelelő ismétlési késleltetés kiválasztása. Korai TCP-implementációkban a értéket használták, de a tapasztalat azt mutatta, hogy a konstans érték rugalmatlanul viselkedik, nem tudja követni a változásokat, ha a szórásnégyzet megnőtt. Különösen a véletlen (például Poisson) forgalomra építő sorbanállási modellek mutatták ki, hogy ha a terhelés a kapacitáshoz közeledik, akkor a késleltetés megnő, és erősen változó lesz. Ez ahhoz vezet, hogy az újraküldési időzítő lejár, és a csomag másolatát újraküldi, holott az eredeti csomag még a hálózatban tartózkodik. Ráadásul ez éppen nagy terhelés esetében történik, amikor egy további csomag hálózatba juttatása a legrosszabb dolog, ami történhet.

A problémára Jacobson azt javasolta, hogy az ismétlési késleltetés értékét a körülfordulási idő szórásától és a csillapított körülfordulási időtől kell függővé tenni. Ehhez a változtatáshoz egy újabb csillapított változó, az RTTVAR (Round-Trip Time VARiation – körülfordulási idő szórása) kiszámítását kell elvégezni:

Ez ugyanaz az EWMA, amit az előbb láttunk, és tipikusan . Az újraküldési késleltetés, az RTO értékét pedig a következőre kell megválasztani:

A 4-es szorzótényező választása valamennyire önkényes, a néggyel történő szorzás megvalósítható egyetlen eltolással, és így a csomagok kevesebb mint 1%-a fog beérkezni később, mint a standard szórás négyszerese. Vegyük észre, hogy az RTTVAR nem egyezik meg pontosan a standard szórással (valójában inkább egy átlagos szórás), de a gyakorlatban elég közel jár hozzá. Jacobson dolgozata tele van okosabbnál okosabb trükkökkel, hogy az újraküldési késleltetést csupán egész számokat érintő összeadással, kivonással és eltolással kiszámítsuk. A mai modern számítógépek esetén ez a gazdaságosság természetesen nem szükséges, de a TCP azon törekvései közé került, hogy mindenféle eszközön futtatható legyen, napjaink szuperszámítógépeitől egészen apró eszközökig. Jelenleg még senki nem alkalmazta a TCP-t RFID-eszközökben[29], de talán egyszer fogják. Ki tudja?

Az újraküldési késleltetés számításáról, beleértve a változók kezdeti beállítását, az RFC 2988 tartalmaz részletesebb információkat. Ezenkívül az újraküldési időzítés értékét a becslésektől függetlenül 1 másodpercben minimalizálták. Ezt az óvatossági megoldást annak megelőzésére vezették be, hogy elkerüljék a mérések alapján végrehajtott hamis újraküldéseket [Allman és Paxson, 1999].

A körülfordulási idő, R mintáinak gyűjtése során felmerül egy probléma: mi a teendő, ha egy szegmens időzítése lejár, és újraküldik? Amikor beérkezik a nyugta, nem tudható, hogy az első átvitelre vonatkozik, vagy az újabbra. Egy rossz tipp jelentősen megzavarhatja az ismétlési időzítés becslését. Phil Karn nehéz körülmények között fedezte fel ezt a problémát. Ő lelkes rádióamatőr, aki a TCP/IP-csomagok amatőr rádióval történő átvitelével foglalkozik, ami egy hírhedten megbízhatatlan médium. Javaslata egyszerű: ne frissítsük a becslések értékét újraküldött szegmensek esetén, hanem minden kudarc esetén duplázzuk meg az időzítés hosszát, amíg a szegmens végül át nem jut. Ezt a javítást Karn-féle algoritmusnak nevezik [Karn és Partridge, 1987]. A legtöbb TCP-implementációban alkalmazzák.

A TCP nem csak az ismétlési időzítőt használja, hanem a folytatódó időzítőt is (persistence timer). Ezt az alábbi holtpont elkerülésére tervezték. A vevő küld egy nyugtát 0 ablakmérettel, amivel a küldőt várakozásra kéri. Később a vevő frissíti az ablakot, de a frissítést hordozó csomag elvész. Most a küldő és a fogadó is arra vár, hogy a másik tegyen valamit. Amikor a folytatódó időzítő lejár, a küldő egy kérést küld a vevőnek, amire válaszul megkapja az ablakméretet. Ha ez még mindig 0, a folytatódó időzítőt újraindítja, és az egész folyamat megismétlődik, különben, ha nagyobb 0-nál, megkezdheti az adatátvitelt.

A harmadik időzítő, amelyet néhány implementáció használ, az életben tartó időzítő (keepalive timer). Amikor egy összeköttetés már régóta tétlen, az életben tartó időzítő lejár, és ennek hatására a TCP ellenőrzi, hogy partnere még mindig működik-e. Ha a távoli entitás nem válaszol, az összeköttetés befejeződik. Ez a szolgáltatás ellentmondásos, mert többletterhelést okoz, és egy átmeneti hálózatszakadás hatására befejezhet egy amúgy még működő összeköttetést.

Az utolsó időzítő, amit minden TCP-összeköttetésben alkalmaznak, a TIME WAIT állapotban az összeköttetés bontásakor használt időzítő. Ez a maximális csomagélettartam kétszereséig jár, hogy biztosítsa az összeköttetés lebontása után az összeköttetés összes korábban generált csomagjának kihalását.

6.5.10. A TCP torlódáskezelése

Utoljára hagytuk a TCP egyik legfontosabb feladatát: a torlódáskezelést. Amikor bármely hálózatban a felajánlott terhelés nagyobb, mint amennyit a hálózat kezelni képes, torlódás keletkezik. Ez alól az internet sem kivétel. A hálózati réteg az útválasztókban lévő várakozási sorok növekedését figyelve észreveszi a torlódás tényét, és – ha csak a csomagok eldobásával is – megpróbálja kezelni. A szállítási réteg feladata, hogy torlódási jelzést kapjon a hálózati rétegtől, és lecsökkentse a hálózatba küldött forgalom sebességét. Az interneten a TCP játssza a legfőbb szerepet a torlódások kezelésében, ahogy a megbízható adatátvitelben is. Éppen ezért ilyen rendkívüli protokoll.

A torlódáskezelés általános eseteit már tárgyaltuk a 6.3. szakaszban. A legfontosabb következtetésünk az volt, hogy kétállapotú torlódási jelzések esetén egy AIMD (Additive Increase Multiplicative Decrease – additív növelés, multiplikatív csökkentés) vezérlési törvényt alkalmazó szállítási protokoll egy igazságos és hatékony sávszélesség-kiosztáshoz közelít. A TCP torlódáskezelése is ezt a módszert valósítja meg egy ablak segítségével, és a csomagvesztést használja fel, mint kétállapotú torlódási jelzést. Ennek elérése érdekében a TCP egy torlódási ablakot (congestion window) használ, amelynek a mérete megmutatja, hogy a küldő összesen hány bájt adatot tarthat a hálózaton egy időben. Az ennek megfelelő sebesség az ablak mérete osztva összeköttetéshez tartozó körülfordulási idővel. A TCP az ablak méretét az AIMD-szabálynak megfelelően állítja be.

Emlékezzünk vissza, hogy a torlódási ablakot a forgalomszabályozási ablak mellett tartjuk karban, amely a vevő által a pufferben tárolható bájtok számát mutatja. Mindkét ablakot párhuzamosan követjük, és az elküldhető bájtok száma megegyezik a kisebbik ablak méretével. Így az effektív ablak kisebb, mint amit a küldő és a vevő helyesnek vél. Tehát kettőn áll a vásár. A TCP leáll az adatok küldésével, ha bármelyik ablak, akár a torlódási, akár a forgalomszabályozási ablak ideiglenesen tele van. Ha a vevő azt mondja, „küldj 64 KB-ot”, de a küldő tudja, hogy egy 32 KB-nál nagyobb löket eltömíti a hálózatot, akkor csupán 32 KB-ot fog küldeni. Másrészt, ha a vevő azt mondja, hogy „küldj 64 KB-ot”, de a küldő tudja, hogy egy 128 KB-os löket is könnyedén átfér, akkor is csak a kért 64 KB-ot fogja elküldeni. Mivel a forgalomszabályozási ablakról már beszéltünk korábban, most csak a torlódási ablakot tárgyaljuk.

A modern torlódáskezelés nagyrészt Van Jacobson [1988] erőfeszítéseinek köszönhetően került a TCP-be. A történet magával ragadó. 1986-ban történt, amikor a korai internet növekvő népszerűsége elvezetett az első, torlódási összeomlás (congestion collapse) néven elterjedt, hosszan elnyúló időszakhoz, amely alatt a hasznos adatátvitel meredeken (akár több századára) lecsökkent a hálózatban található torlódás következtében. Jacobson (és sokan mások) elhatározták, hogy végére járnak a történteknek, és gyógyírt találnak rá.

Az a magas szinten elkövetett javítás, amit Jacobson megvalósított, egy AIMD torlódási ablak volt. A már létező TCP-megvalósításba Jacobson úgy olvasztotta bele a javítását, hogy az üzenetformátumokat nem kellett megváltoztatni, így a végeredmény gyorsan telepíthető lett. Habár ez a megoldás legérdekesebb része, a TCP torlódáskezelési részének az összetettségéért is nagymértékben felel. Kezdetben észrevette, hogy a csomagvesztés használható a torlódás jelzésére. Bár egy kicsit későn érkezik (mivel a hálózat már torlódott), de igen megbízható. Végül is nehéz olyan útválasztót építeni, amelyik nem dobja el a csomagokat túlterhelés esetén. Ez valószínűleg a jövőben sem változik. Még ha terabájt méretű pufferekben is fogjuk tárolni a rengeteg csomagot, valószínűleg akkor is lesz ezeket feltöltő terabájt/másodperc sebességű hálózatunk.

A csomagvesztés torlódási jelzésként történő alkalmazása azonban ritkán előforduló átviteli hibákat feltételez. Vezeték nélküli hálózatokon, például a 802.11 esetén, sajnos nem ez a helyzet, éppen ezért itt az adatkapcsolati réteg saját újraküldési megoldást alkalmaz. A vezeték nélküli újraküldések ezért a hálózati réteg felé elfedik az átviteli hibákat. Más összeköttetéseken, mint vezetékes vagy optikai kapcsolatokon pedig a hibaarány tipikusan alacsony.

Minden interneten használt TCP-algoritmus azt feltételezi, hogy az elveszett csomagokat hálózati torlódások okozzák, és figyelik az időzítők lejárását, valamit a baj előjelei után kutatnak éppen úgy, ahogy a bányászok figyelik a kanárimadaraikat. A csomagvesztések pontos és időben történő felfedezéséhez egy jó újraküldési időzítőre van szükség. Arról már esett szó, hogyan veszi figyelembe a TCP az újraküldési időzítőknél a körülfordulási idő átlagának és szórásának becslését. A szórási tényező bevezetésével Jacobson egy nagyon fontos lépést tett. Egy megfelelő újraküldési időzítéssel a TCP-küldő nyomon tudja követni azokat a kinn lévő bájtokat, melyek még a hálózatban találhatók. Ehhez egyszerűen megnézi az elküldött és nyugtázott sorszámok különbségét.

Most már úgy tűnik, könnyű feladatunk van. Mindössze annyi a dolgunk, hogy nyilvántartsuk a torlódási ablakot az elküldött és nyugtázott sorszámok segítségével, és a méretét az AIMD-szabállyal állítsuk. Ahogy azonban várható, azért ez nem ilyen egyszerű. Először is, azt az utat, amelyen a csomagok a hálózatba kerülnek, még ha csak rövid időre is, meg kell feleltetni a hálózati útvonalnak, ellenkező esetben a forgalom torlódást fog okozni. Vegyünk például egy állomást 64 KB torlódási ablakkal, amely 1 Gbit/s sebességű kapcsolt Ethernetre csatlakozik. Ha az állomás a teljes ablakot egyszerre küldi el, ez a löket akár egy lassú 1 Mbit/s sebességű ADSL-vonalon is átutazhat az útvonal mentén. A löket, amelynek továbbítása csupán fél milliszekundumnyi időt vett igénybe az 1 Gbit/s sebességű vonalon, az 1 Mbit/s sebességű ADSL-vonalat fél másodpercre is eldugítja, teljesen tönkretéve olyan protokollokat, mint amilyen például a VoIP. Ez a viselkedés esetleg megfelel egy torlódást okozó protokoll számára, de nem alkalmas egy torlódáskezelő protokollhoz.

Kiderült azonban, hogy a csomagok egy kis csoportját az előnyünkre is fordíthatjuk. A 6.43. ábra bemutatja, mi történik akkor, amikor egy küldő egy gyors hálózaton (1 Gbit/s-os adatkapcsolaton) 4 csomagot küld egy vevőnek egy lassú hálózatra (1 Mbit/s), amely a hálózati útvonal szűk keresztmetszete vagy leglassúbb része. Kezdetben a négy csomag olyan gyorsan áthalad az adatkapcsolaton, amilyen gyorsan csak a küldő el tudja küldeni. Az útválasztónál a továbbküldésig a csomagok várakozási sorba kerülnek, hiszen egy csomag elküldése tovább tart a lassú adatkapcsolaton, mint egy új megérkezése a gyorsabb adatkapcsolaton. A várakozási sor mérete azonban nem nagy, hiszen csak pár csomagot küldtünk egyszerre. Vegyük észre a csomag megnövekedett hosszát a lassabb adatkapcsolaton. Ugyanaz a csomag (például 1 KB méretű) most hosszabb, hiszen több időbe telik elküldeni egy lassabb adatkapcsolaton, mint egy gyorsabb adatkapcsolaton.

6.43. ábra - A küldőtől jövő csomagok egy lökete és a visszatérő nyugta ütemezése

kepek/06-43.png


A csomagok végül megérkeznek a vevőhöz, ahol az nyugtázza őket. A nyugták időpontjai a csomagok beérkezési idejét tükrözik a vevőnél, miután azok átkeltek a lassú adatkapcsolaton. A csomagok szétterülnek a gyors adatkapcsolaton haladó eredeti csomagokhoz képest. A nyugták, miközben átutaznak a hálózaton a vevőtől vissza a küldőig, megőrzik eredeti időzítéseiket.

A legfontosabb megállapításunk tehát a következő: a nyugták nagyjából olyan sebességgel érkeznek vissza a küldőhöz, mint amilyen sebességgel a csomagokat az útvonal leglassabb adatkapcsolatán továbbítani lehet. Pontosan ez az a sebesség, amelyet a küldőnek használnia kell. Ha az új csomagokat ilyen sebességgel bocsátja a hálózatba, akkor a csomagokat olyan gyorsan fogja küldeni, amilyen gyorsan a leglassabb adatkapcsolat megengedi, de nem fognak a várakozási sorok feltöltődni egyik útválasztóban sem az útvonal mentén, és így nem jön létre torlódás sem. Ez az időzítés nyugtaórajel (ack clock) néven ismert, és alapvető része a TCP-nek. Nyugtaórajel használatával a TCP elsimítja a kimenő forgalmat, és elkerüli a felesleges sorbaállást az útválasztókban.

Másik fontos megfontolásunk az, hogy az AIMD-szabály szerint nagyon sok időt fog igénybe venni gyors hálózatokon a megfelelő munkapont elérése, ha a torlódási ablak kis méretről indul. Vegyünk egy szerény hálózati útvonalat, amely 10 Mbit/s sebességgel és 100 ms körülfordulási idővel (RTT) rendelkezik. A megfelelő torlódási ablak mérete a sávszélesség-késleltetés szorzat, ami 1 Mbit vagy 100 darab 1250 bájtos csomag. Ha a torlódási ablak mérete 1 csomagról indul, és minden körülfordulás alatt 1 csomaggal nő, akkor 100 körülfordulási időbe, tehát 10 másodpercbe fog telni, mire az összeköttetés sebessége a kívánatos közelébe ér. Ez elég hosszú idő annak kivárására, hogy elérjük a megfelelő átviteli sebességet. Csökkenthetnénk ezt az időt, ha nagyobb kezdeti ablakkal indulnánk, mondjuk 50 csomaggal. De ez az ablakméret lassú vagy rövid adatkapcsolatokra hatalmas lenne. Ha a teljes ablakot egyszerre kihasználnánk, akkor a leírtak alapján torlódást okozna.

Jacobson e helyett a fenti problémák megoldására a lineáris és az exponenciális növekedés egyfajta keveréke mellett döntött. Amikor az összeköttetés felépült, a küldő a torlódási ablak kezdeti méretét egy kicsi, maximálisan 4 szegmens méretűre választja; a részleteket az RFC 3390 tartalmazza. A 4 szegmens használata a korábbi 1 szegmens használatát váltotta fel a tapasztalatokból kiindulva. A küldő ekkor elküldi a kezdeti ablakméretet. A csomagok nyugtázása egy körülfordulási időbe kerülnek. Az újraküldési időzítő lejárta előtt nyugtázott minden szegmens után a küldő egy szegmensnek megfelelő számú bájtot hozzáad a torlódási ablakhoz, továbbá, mivel azt a szegmenst nyugtázták, eggyel kevesebb csomag lesz a hálózatban. A végeredmény tehát az, hogy minden nyugtázott szegmens után két új szegmenst lehet elküldeni. A torlódási ablak minden körülfordulási idő után megduplázódik.

Ezt az algoritmust lassú kezdésnek vagy lassú kezdést biztosító algoritmusnak (slow start) hívják, de egyáltalán nem lassú – valójában exponenciális a növekedés – csupán az előző algoritmussal szemben az, ahol a teljes forgalomszabályozási ablakot egyszerre küldték el. A lassú kezdést a 6.44. ábra mutatja. Az első körülfordulási idő alatt a küldő mindössze egy csomagot ereszt a hálózatba (és a küldő is egy csomagot kap). A következő körülfordulási idő alatt két csomagot, a harmadik körülfordulási idő alatt pedig már négy csomagot küld.

6.44. ábra - Lassú kezdést biztosító algoritmus, egy szegmens méretű, kezdeti torlódási ablakkal

kepek/06-44.png


A lassú kezdés kielégítően működik olyan adatkapcsolatok esetén, ahol különböző a sebesség és a körülfordulási idő, és a küldő adási sebességének a hálózati útvonal sebességéhez való illesztéséhez nyugta órajelet használ. Vessünk egy pillantást a 6.44. ábrára, és figyeljük meg a nyugták érkezését a vevőhöz! Amikor a küldő egy nyugtát kap, akkor megnöveli a torlódási ablak méretét eggyel, és azonnal két csomagot küld a hálózatba. (Az egyik csomag az eggyel növelés miatt; a másik a nyugtázott, és így a hálózatot elhagyó csomag helyett kerül elküldésre. Bármely időpillanatban a torlódási ablak mérete adja meg az összes nyugtázatlan csomag számát.) Ez a két csomag azonban nem feltétlenül érkezik meg pontosan olyan időközzel a vevőhöz, mint ahogy elküldték azokat. Vegyünk például egy küldőt egy 100 Mbit/s Ethernet-vonalon. Minden 1250 bájtos csomag elküldése 100 s-ot igényel. A csomagok közötti időköz tehát akár 100 s méretű is lehet. A helyzet akkor változik meg, ha ezek a csomagok valahol az útvonal mentén áthaladnak egy 1 Mbit/s sebességű ADSL-adatkapcsolaton. Most 10 ms időbe telik ugyanannak a csomagnak a továbbítása. Ez azt jelenti, hogy a minimális időköz a csomagok között százszorosára nőtt. Ez az időköz ilyen nagy marad, hacsak nem várják be egymást egy várakozási sorban valamely gyorsabb adatkapcsolaton.

A 6.44. ábra ezt a jelenséget az adatcsomagok vevőhöz érkezésénél a nyilak végei között egy kisebb távolság feltüntetésével ábrázolja. Ugyanez a távolság szerepel a nyugták elküldésénél, és így a nyugták küldőhöz való visszaérkezésénél is. Ha a hálózati útvonal lassú, akkor a nyugták lassan érkeznek (egy körülfordulásnyi idővel később). Ha a hálózati útvonal gyors, akkor a nyugták is hamar megérkeznek (ugyancsak egy körülfordulásnyi idővel később). A küldőnek csupán a nyugtaórajelet kell követnie az új csomagok kiküldésénél. A lassú kezdés pont ezt teszi.

Mivel a lassú kezdés exponenciális növekedéssel jár, ezért előbb vagy utóbb túl sok csomag kerül túl gyorsan a hálózatba. Amint ez megtörténik, a hálózatban található várakozási sorok betelnek, és a teli sorok következtében csomagok fognak elveszni. Ezek után a TCP-küldő időzítője lejár, amikor egy nyugta nem érkezik meg időben. A lassú kezdés túl gyors növekedésének a 6.44. ábrán is szemtanúi lehetünk. Három körülfordulási idő után négy csomag található a hálózatban. A négy csomag megérkezéséhez a vevőhöz egy egész körülfordulásnyi időre van szükség. Tehát a négycsomagnyi torlódási ablakméret éppen megfelelő ehhez az összeköttetéshez. Ahogy azonban ezeket a csomagokat is nyugtázzák, a lassú kezdés tovább növeli a torlódási ablakot, és az a következő körülfordulási idő végére eléri a nyolccsomagnyi méretet. Összesen csupán négy csomag jut el a vevőhöz egyetlen körülfordulási idő alatt, függetlenül attól, hány csomagot küldtünk. A hálózati csővezeték tehát tele van. Ha a küldő további csomagokat bocsát a hálózatba, azok beragadnak az útválasztók várakozási soraiba, mivel nem lehet a küldő felé elég gyorsan továbbítani. A torlódás és a csomagvesztés pedig hamarosan bekövetkezik.

A küldő a lassú kezdés kordában tartására egy, a kapcsolathoz tartozó határt szab, amelyet a lassú kezdés küszöbértékének (slow start threshold) hívunk. Ennek értékét kezdetben önkényesen magasan tartjuk, a forgalomszabályozási ablak méretén, hogy az összeköttetés sebességét ne korlátozza. A TCP a lassú kezdés során a torlódási ablak méretét mindaddig növeli, amíg nem jár le az újraküldési időzítő, vagy a torlódási ablak át nem lépi ezt a küszöböt (vagy meg nem telik a vevő ablaka).

Amint a küldő – például az újraküldési időzítő lejárta miatt – felfedez egy csomagvesztést, a küszöböt a torlódási ablak méretének felére csökkenti, és a teljes folyamatot elölről kezdi. Az ötlet az, hogy az aktuális ablakméret túl nagy, mivel az előbbiekben torlódást okozott, és csak most, az időzítő lejártával vettük észre. Az ablak méretének a fele, amit már korábban is sikeresen alkalmaztunk, valószínűleg a legjobb becslése egy olyan torlódási ablaknak, amely közel van az útvonal kapacitásához, de még nem okoz csomagvesztést. A 6.44. ábrán látható példánkban a 8 csomagra növekedett torlódási ablakunk már okozhat csomagvesztést, míg a 4 csomag méretű ablak az előző körülfordulási idő alatt éppen a megfelelő nagyságú volt. A torlódási ablak méretét aztán az alacsony kezdeti értékére állítjuk, és visszatérünk a lassú kezdéshez.

Amint a lassú kezdés küszöbértékét átlépi, a TCP lassú kezdésről additív növekedésre vált. Ebben az üzemmódban a torlódási ablak méretét minden körülfordulási idő után eggyel növeli. A megvalósítás során a léptetés, ahogy a lassú kezdésnél is, általában minden nyugtázott szegmens után történik, nem pedig a körülfordulási időnként történő növeléssel. Legyen a torlódási ablak mérete cwnd és a legnagyobb lehetséges szegmensméret MSS. Egy gyakori megközelítés szerint a számú csomag minden nyugtájának megérkezése után a cwnd értékét értékkel növelik. A léptetésnek nem kell feltétlenül gyorsnak lenni. A megoldás lényege az, hogy a TCP-összeköttetés torlódási ablaka az idő nagy részét az optimális pont közelében töltse – ne legyen olyan kicsi, hogy lelassítsa az összeköttetést, de ne legyen olyan nagy sem, hogy torlódást okozzon.

Az additív növelést a 6.45. ábrán mutatjuk be a lassú kezdéssel megegyező esetben. A küldő torlódási ablaka minden körülfordulási idő végére annyit nő, hogy további csomagokat tudjon a hálózatra bocsátani. A lassú kezdéshez hasonlítva azonban a lineáris növekedés sokkal lassabb. Kisméretű torlódási ablakok esetén a különbség nem nagy (ahogy jelen esetben sem), de például az ablak 100 csomagnyi méretűre növelése esetén az időkülönbség már számottevő.

6.45. ábra - A kezdeti, egy szegmens méretű torlódási ablak additív növelése

kepek/06-45.png


A teljesítmény növelésére egy másik eszközünk is van. Az eddigi megoldás gyenge pontja, hogy az újraküldési időzítő lejárását meg kell várnunk, ez pedig hosszú idő lehet, hiszen az időzítések meglehetősen nagyok. Ha egy csomag elvész, a vevő nem tudja nyugtázni, így a nyugtázott sorszámok nem változnak, és a küldő a torlódási ablak telítettsége miatt nem tud további csomagokat küldeni a hálózatba. Ez a helyzet sokáig eltarthat, egészen az újraküldési idő lejártáig. A csomagot ebben az esetben a TCP újraküldi, és újra lassú kezdésbe fog.

A küldő számára van egy gyorsabb módszer is a csomagvesztés megállapítására. Ahogy az elveszett csomag utáni csomagok beérkeznek a vevőhöz, a vevő nyugtákkal válaszol a küldőnek. Ezek a nyugták ugyanazt a nyugtasorszámot tartalmazzák, ezért ezeket nyugtamásolatoknak hívjuk. Amint a küldő egy ilyen nyugtamásolatot kap, feltételezheti, hogy a vevőhöz egy újabb csomag érkezett, de az elveszett csomag még mindig nem került elő.

Mivel a csomagok különböző útvonalakat járhatnak be a hálózatban, nem feltétlenül kell sorrendben beérkezniük. Így előfordulhat, hogy csomagvesztés nélkül kapunk nyugtamásolatokat. Az interneten azonban ez nem túl gyakori. Tulajdonképpen a csomagok sorrendje akkor sem változik meg túlságosan, ha más útvonalon haladnak a hálózatban. Így a TCP, valamelyest önkényesen, azt feltételezi, hogy három nyugtamásolat vétele csomagvesztést jelent. Az elveszett csomagra a nyugták sorszámából következtethetünk, ugyanis a sorrendben következő csomag veszett el. Ez a csomag egyből újraküldhető anélkül, hogy az időzítő lejártát meg kellene várni.

Az ilyen heurisztikát gyors újraküldésnek (fast retransmission) nevezzük. Amint ez megtörténik, a lassú kezdés küszöbértékét ugyancsak a torlódási ablak pillanatnyi értékének felére állítjuk, ahogyan azt az időzítés túllépése esetén tennénk. A lassú kezdés a torlódási ablak egy csomag méretűre állításával kezdhető újra. Ezzel az ablakmérettel a TCP egy új csomagot küld minden körülfordulási idő után, amely az újraküldött csomag, és minden, a csomagvesztés előtt elküldött adat nyugtájának a visszaérkezéséhez szükséges.

Az eddigiekben felépített torlódáskezelő algoritmust a 6.46. ábrán mutatjuk be. A TCP ilyen megvalósítását TCP Tahoe-nak hívjuk, ugyanis a 4.2BSD Tahoe 1988-as kiadásának része volt. A maximális szegmensméret 1 KB. Kezdetben a torlódási ablak mérete 64 KB, de egy időtúllépés következtében a küszöb értékét 32 KB-ra, a torlódási ablakot pedig 1 KB-ra állította a 0-dik menethez. A torlódási ablak mérete exponenciálisan nő, amíg el nem éri a küszöböt (32 KB). Az ablakot minden nyugta beérkezésekor állítja, nem folytonosan, így egy diszkrét lépcsőmintázat alakul ki. Amint a küszöböt átlépte, a növekedés lineárisan folytatódik, minden körülfordulási idő után egy szegmenssel nő.

6.46. ábra - Lassú kezdést követő additív növekedés a TCP Tahoe-ban

kepek/06-46.png


A 13. körben az átvitel egy szerencsétlenség áldozata lett (sejthettük volna…) és egy csomag el is veszett a hálózatban. Három nyugtamásolat érkezése után a csomagvesztés kiderül, és ekkor a csomag újraküldésre kerül, a küszöb pedig az aktuális ablakméret felére csökken (azaz 40 KB-ról 20 KB-ra), és a lassú kezdés újraindul. Újra kezdve az egy csomagnyi méretű torlódási ablakkal, összesen egy körülfordulási időre van szükség, hogy az előzőleg elküldött, valamint az újraküldött csomagok elhagyják a hálózatot, és nyugtázásra kerüljenek. A torlódási ablak az előzőekhez hasonlóan növekszik mindaddig, amíg el nem éri a 20 KB méretű küszöböt. Attól kezdve a növekedés megint lineárissá alakul, és a folytatásban is e szerint növekszik, amíg nyugtamásolatok vagy időtúllépések csomagvesztést nem jeleznek (vagy a vevő ablaka meg nem telik).

A TCP Tahoe (amely megfelelő újraküldési időzítőket alkalmaz) egy működő torlódáskezelő algoritmust biztosított, és megoldotta a torlódási összeomlás problémáját. Jacobson azonban rájött, hogy készíthető ennél is jobb algoritmus. Amikor gyors újraküldés történik, az összeköttetés egy túl nagy torlódási ablakot használ, de a nyugtaórajel még mindig megfelelő. Minden esetben, amikor egy nyugtamásolat érkezik, valószínűleg egy újabb csomag hagyta el a hálózatot. Ha a nyugtamásolatotokat a hálózatban található csomagok megszámlálására használjuk, akkor lehetővé válik, hogy pár csomagnak megengedjük a hálózat elhagyását, és folytassuk az új csomagok küldését minden további nyugtamásolat esetén.

A gyors helyreállítás (fast recovery) egy olyan heurisztikus algoritmus, amely ezt a viselkedést valósítja meg. Ez egy olyan ideiglenes állapot, ami megpróbálja a nyugtaórajelet tovább biztosítani egy olyan torlódási ablakkal, amelynek a mérete az új küszöbbel vagy a gyors újraküldés idején érvényes torlódási ablakméret felével egyenlő. Ennek elérése érdekében a nyugtamásolatokat folyamatosan számolja (beleértve azt a hármat is, amelyek a gyors újraküldést okozták) mindaddig, amíg a hálózatban lévő csomagok száma az új küszöb értékére nem esik. Ez körülbelül fél körülfordulásnyi időbe telik. Ettől kezdve minden egyes nyugtamásolat beérkezésekor egy új csomagot küldhetünk. A gyors újraküldés után egy körülfordulásnyi idővel az elveszett csomagra megérkezik a nyugta. Ekkor megszűnik a nyugtamásolatok özöne, és a gyors helyreállás üzemmód véget ér. A torlódási ablak mérete az új lassú kezdés küszöbértékére áll, és innentől lineárisan növekszik.

A heurisztika végkövetkeztetése az tehát, hogy a TCP elkerüli a lassú kezdést, kivéve, amikor az összeköttetést először felépítjük, vagy amikor időtúllépés következik be. Ez utóbbi még mindig megtörténhet abban az esetben, amikor több csomag is elvész, és a gyors újraküldés nem tudja kielégítően kijavítani a hibát. A lassú kezdések helyett egy működő összeköttetés torlódási ablakmérete fűrészfog-mintázatot alkot, additív növekedéssel (egy szegmenssel minden körülfordulási időnként), és multiplikatív csökkenéssel (egy körülfordulási idő alatt a felére csökken). Ez pont az AIMD-szabály, amit megkíséreltünk megvalósítani.

A fűrészfog-mintázatot a 6.47. ábra mutatja, amelyet a TCP Reno állított elő. A TCP Reno az 1990-ben kiadott 4.3BSD Reno része volt, és innen kapta a nevét. A TCP Reno gyakorlatilag a TCP Tahoe, gyors helyreállással megfűszerezve. A bevezető lassú kezdés után a torlódási ablak lineárisan növekszik mindaddig, amíg csomagvesztést nem észlel a nyugtamásolatoknak köszönhetően. Az elveszett csomagot újraküldi, és a gyors helyreállás segítségével az újraküldött csomag nyugtázásáig a nyugtaórajelet biztosítja. Innentől a torlódási ablak a növekedést az új lassú kezdés küszöbértékéről kezdi, nem pedig 1 szegmensről. Ez a viselkedés korlátlan ideig folytatódik, és az összeköttetés torlódási ablaka az idő nagy részét az optimális pont, tehát a sávszélesség-késleltetés szorzat közelében tölti.

6.47. ábra - A TCP Reno gyors helyreállása és fűrészfog-mintázata

kepek/06-47.png


A TCP Reno a torlódásiablak-szabályozó megoldásaival több mint két évtizede meghatározza a TCP torlódáskezelésének alapjait. Az azóta eltelt évek során, a módszeren csupán apró módosításokat végeztek, mint például a kezdeti ablak paramétereinek megváltoztatását vagy különböző, nem egyértelmű részek eltávolítását. Némely továbbfejlesztés arra irányult, hogy egy ablakban történt két vagy több csomag elvesztését helyreállítsák. A TCP NewReno például egy újraküldés után a nyugtasorszámokat részlegesen előrelépteti, hogy más csomagvesztéseket is megtaláljon és kijavítson [Hoe, 1996]. Az RFC 3782 részletesen ír a módszerről. A 90-es évek közepétől különböző olyan változatok jelentek meg, amelyek a leírt alapelveket követik, de valamelyest különböző vezérlési törvényeket alkalmaznak. Például a Linux a CUBIC TCP [Ha és mások, 2008], a Windows pedig a Compound TCP [Tan és mások, 2006] nevű változatot használja.

A TCP-megvalósításokra azonban két komoly változás is hatással volt. Az első szerint a TCP összetettsége főleg abból adódik, hogy a nyugtamásolatok tömkelegéből próbál következtetni arra, hogy mely csomagok érkeztek meg épségben, és mely csomagok vesztek el. A halmozott nyugtázás ilyen információt nem tartalmaz. Az egyszerű megoldás a SACK (Selective ACKnowledgementsszelektív nyugtázás) használata, amely akár három bájtsorszám-intervallumot is felsorolhat, amelyek vétele sikeres volt. Ennek az információnak a segítségével a küldő sokkal közvetlenebbül el tudja dönteni, mely csomagokat kell újraküldeni, és a torlódási ablak megvalósításában is nagy segítséget nyújt, hogy a még úton levő csomagokat is nyomon lehet követni.

Amikor a küldő és a vevő felépítenek egy összeköttetést, mindketten elküldik a SACK engedélyezve (SACK permitted) TCP-opciót, amellyel jelzik, hogy a szelektív nyugtákat megértik. Amint a kapcsolaton a SACK engedélyezve van, akkor az a 6.48. ábrán látható módon működik. A vevő a TCP Nyugtaszám mezőjét a szokásos módon használja, tehát a legmagasabb, sorrendben beérkezett bájt halmozott nyugtázására. Ha a 3. csomagot kapja, ami nem a sorrendben következő csomag (mert a 2 csomag elveszett), a fogadott adatra egy SACK opciót küld, az 1 csomagra vonatkozó halmozott nyugta (-másolat) mellett. A SACK opció azokat a bájtsorszám-intervallumokat tartalmazza, amely adatokat sikeresen vett a vevő a halmozott nyugtában közölt sorszámon felül. Az első intervallum azt a csomagot jelenti, amely a nyugtamásolatot okozta, a többi jelenlévő intervallum pedig régebbi blokkok nyugtája. Általában legfeljebb három intervallumot használnak. Mire a hatodik csomag megérkezik, már két SACK bájtsorszám-intervallum jelzi, hogy a 6. és a 3-tól 4-ig terjedő csomagok sikeresen megérkeztek. Ez kiegészül az 1. csomagig terjedő halmozott nyugtával. Minden egyes beérkezett SACK opció lehetővé teszi a küldőnek, hogy meghatározza az újraküldendő csomagokat. Ebben az esetben a 2. és 5. csomag újraküldése lenne a jó ötlet.

6.48. ábra - Szelektív nyugtázás

kepek/06-48.png


A SACK szigorúan csak ajánlást nyújt. A tényleges csomagvesztés ténye ugyanúgy megállapítható nyugtamásolatokkal és a torlódási ablak paramétereinek állításával, mint az eddigiekben. SACK használatával azonban a TCP hamarabb helyreállíthatja az adatátvitelt olyan esetekben, amikor nagyjából egy időben több csomag is elveszett, mivel a TCP-küldő pontosan tudja, mely csomagok nem jutottak el a vevőig. A SACK manapság széles körben elterjedt. Működését az RFC 2883 írja le, a SACK-ot használó TCP-torlódáskezelést pedig az RFC 3517.

A másik komolyabb változás az ECN (Explicit Congestion Notification – explicit torlódásjelzés) használata a torlódás jelzésére a csomagvesztésen felül. Az ECN az IP-rétegbeli megoldás az állomások értesítésére, hogy a hálózatban torlódás található, ahogy arról az 5.3.4. szakaszban írtunk. Segítségével a TCP-vevő torlódási jelzést kaphat az IP-rétegtől.

Egy TCP-kapcsolat akkor használ ECN-t, ha mind a küldő, mind a vevő ezt jelezte az összeköttetés felépítésekor az ECE és CWR bitek beállításával. ECN használata esetén minden TCP-szegmenst megjelölnek az IP-fejlécben annak jelzésére, hogy az képes ECN-jelzések szállítására. Az ECN-t támogató útválasztók minden, ECN-jelzést szállítani képes csomagban jelzik, ha torlódás közeleg ahelyett, hogy a csomagot egyszerűen eldobnák annak bekövetkezte után.

A TCP-vevő a csomagban az ECN torlódási jelzést észlelve az ECE (ECN Echo) bit segítségével jelzi a küldőnek, hogy a csomagjai torlódást tapasztaltak. A küldő a vevő felé a jelzés vételét a CWR (Congestion Window Reduced – torlódási ablak mérete csökkentve) bit segítségével közli.

A TCP-küldő ezekre a torlódási jelzésekre pontosan úgy reagál, mint ahogy a nyugtamásolatokkal megállapított csomagvesztésekre. A helyzet azonban sokkal kellemesebb, hiszen a torlódást felfedezték, és egyetlen csomagnak sem esett bántódása. Az ECN-t az RFC 3168 írja le. Az ECN mind a hoszt, mind az útválasztó támogatását igényli, ezért még nem túl elterjedt az interneten.

Részletesebb információkat a TCP-ben alkalmazott torlódáskezelő megoldásokról, és azok viselkedéséről az RFC 5681 tartalmaz.

6.5.11. A TCP jövője

A TCP-t az internet igáslovaként már rengeteg alkalmazásban használják, és az idők folyamán sokat fejlesztették, hogy megfelelő teljesítőképességgel rendelkezzen a hálózatok széles spektrumán. Sok változatát használják, amelyek némileg különböző megoldásokkal működnek, mint az általunk tárgyalt klasszikus algoritmusok, különösen a torlódáskezelés és a támadások elleni robusztusság területén. A TCP valószínűleg az internettel együtt fog fejlődni. A következőkben két különleges esetet említünk meg.

Az egyik, hogy a TCP nem minden alkalmazásnak biztosít kielégítő szállítási szolgáltatást. Néhány alkalmazás például elvárja, hogy az általuk küldött üzenetek vagy rekordok megőrizzék az üzenethatárukat. Más alkalmazások egymáshoz kapcsolódó párbeszédeket használnak, mint például egy webböngésző, amely számos objektumot kér le ugyanattól a szervertől. Megint más alkalmazások az általuk használt hálózati útvonalak felett szeretnének nagyobb irányítási lehetőséget. A klasszikus TCP csatlakozó interfész (socket) nem elégíti ki ezeket a kívánalmakat. Az alkalmazásoknak kell tehát megbirkózni minden olyan problémával, amit a TCP nem old meg. Ez vezetett az olyan újabb protokollok kidolgozása felé, amelyek némileg különböző interfészt nyújtanak. Két példát emelhetünk ki: az RFC 4960-ban definiált SCTP- (Stream Control Transmission Protocol – folyamvezérlő átviteli protokoll) és az SST- (Structured Stream Transport – strukturált folyamszállítás) protokollt [Ford, 2007]. Sajnos azonban, amikor valaki meg akar változtatni valami olyat, ami már hosszú idők óta jól működik, mindig nagy harcot vált ki a „felhasználók több lehetőséget akarnak” és a „ha működik, ne nyúlj hozzá” tábor között.

A másik a torlódáskezelés. A fejtegetéseink, és az idők folyamán kifejlesztett megoldások következtében azt gondolhatnánk, hogy a torlódáskezelés már megoldott probléma. Sajnos nem így van. Az általunk tárgyalt, jelenlegi formájában használt TCP-torlódáskezelés, amely ráadásul széles körben elterjedt, a csomagvesztést használja a torlódás jelzésére. Amikor Padhye és mások 1998-ban a fűrészfog mintán alapuló TCP átbocsátóképességét modellezték, megállapították, hogy a sebesség növekedésével a csomagvesztési aránynak meredeken csökkennie kell. 1 Gbit/s áteresztőképesség eléréséhez 100 ms körülfordulási idő szükséges, és 1500 bájtos csomagok esetén egy csomag veszhet el nagyjából minden 10 percben. Ez csomagvesztési arány, ami hihetetlenül alacsony. Ebben az esetben a csomagvesztés egyszerűen túl ritka ahhoz, hogy azt torlódási jelzésként kezeljük, valamint a csomagvesztés más forrása (például nagyságrendű csomagtovábbítási hibaarány) könnyedén elnyomhatja a nagyságrendű csomagvesztési arányt, csökkentve az áteresztőképességet.

Ez az összefüggés régebben nem jelentett problémát, de azóta a hálózatok már sokkal gyorsabbak, ami sok mérnököt a torlódáskezelés újragondolásához vezetett. Az egyik lehetőség az, hogy egy olyan, alternatív torlódáskezelést alkalmazunk, amely nem a csomagvesztésre építi a torlódás jelzését. A 6.2. szakaszban számos példát adtunk ilyen megoldásra. A FAST TCP a körülfordulási időre épít, mint jelzésre, amely torlódás esetén megnövekszik [Wei és mások, 2006]. Több más megoldás is lehetséges, az idő pedig majd kiválasztja a legjobbat.



[28] A szegmensek mérete esetén nemcsak a küldő- és a vevőoldalon, hanem a teljes útvonalon lévő legkisebb MTU-t kell figyelembe venni. (A fordító megjegyzése)

[29] Apró Wi-Fi-eszközökben már használják. (A fordító megjegyzése)