Kapittel 3 · Transportlaget

Pålitelig dataoverføring

Steg for steg fra en perfekt kanal til en realistisk verden med bitfeil, tap og forsinkelse — og protokollene som mestrer dem.

00 · Problemet

Pålitelig over upålitelig

Applikasjoner vil ha en pålitelig kanal. Nettverket gir dem alt annet. Transportlagets jobb er å bygge bro mellom drøm og virkelighet.

Tenk deg to prosesser som kommuniserer. Applikasjonen ser det slik: data sendes inn i en magisk pipe på den ene siden og kommer ut, uforandret og i riktig rekkefølge, på den andre. Ingen tap, ingen feil, ingen forsinkelse. Det er abstraksjon — en tjeneste som transportlaget tilbyr oppover.

Men under denne abstraksjonens overflate ligger virkeligheten: et nettverk som kan flippe bits, miste pakker og levere ting i feil rekkefølge. Kompleksiteten til protokollen vi trenger avhenger direkte av hvor ille den underliggende kanalen oppfører seg.

Kjerneprinsipp

Sender og mottaker kjenner ikke hverandres tilstand — de vet ikke om den andre har mottatt noe som helst, med mindre de eksplisitt kommuniserer det via meldinger. Det er derfor vi trenger en protokoll.

Grensesnittene — fire funksjoner

Uansett hvor kompleks protokollen blir, bruker vi alltid de samme fire grensesnittene:

FunksjonRolle
rdt_send()Kalles fra applikasjonen ovenfor. Overleverer data som skal sendes pålitelig.
udt_send()Kalles av protokollen for å sende en pakke over den upålitelige kanalen.
rdt_rcv()Kalles når en pakke ankommer mottakersiden fra den upålitelige kanalen.
deliver_data()Kalles av protokollen for å levere data opp til mottaker-applikasjonen.
Sende-app Motta-app rdt sender rdt mottaker upålitelig kanal rdt_send() deliver_data() udt_send() rdt_rcv()
De fire grensesnittene i pålitelig dataoverføring. Applikasjonen ser en pålitelig kanal; protokollen håndterer den upålitelige virkeligheten under.

Nå bygger vi opp protokollen steg for steg — fra det trivielle til det realistiske. Hvert steg legger til én ny komplikasjon, og vi ser nøyaktig hva som kreves for å håndtere den.

01 · rdt 1.0

Perfekt kanal — trivielt

Utgangspunktet: en kanal som aldri gjør feil. Protokollen trenger ikke gjøre noe spesielt.

Vi starter med den enkleste modellen: den underliggende kanalen er perfekt. Ingen bitfeil, ingen pakketap. I denne utopien er protokollen triviell.

Sender (rdt 1.0)

Mottar data fra applikasjonen (rdt_send). Lager en pakke (make_pkt). Sender den ned (udt_send). Ferdig.

Mottaker (rdt 1.0)

Mottar pakken (rdt_rcv). Trekker ut dataene (extract). Leverer dem opp (deliver_data). Ferdig.

Sender FSM Vent på kall ovenfra rdt_send(data) pkt=make_pkt(data); udt_send(pkt) Mottaker FSM Vent på kall nedenfra rdt_rcv(pkt) extract(pkt,data); deliver_data(data)
FSM for rdt 1.0 — begge sider har én tilstand. Ingen feilhåndtering, ingen bekreftelser.

Poenget med rdt 1.0 er ikke at den er nyttig — det er at den etablerer grunnlinjen. Alt som følger handler om hva som skjer når vi fjerner antakelsen om en perfekt kanal.

02 · rdt 2.0

Bitfeil — ACK og NAK

Kanalen kan nå flippe bits i pakker. Vi trenger en mekanisme for at mottakeren kan si «fikk den» eller «fikk den ikke».

Forestill deg at du dikterer en tekst over telefon. Etter hvert ord sier lytteren enten «OK» eller «gjenta det». Det er nøyaktig det rdt 2.0 gjør: mottakeren bruker en sjekksum for å oppdage bitfeil, og svarer med ACK (alt vel) eller NAK (feil oppdaget — send på nytt).

Nye mekanismer i rdt 2.0

Feildeteksjon: Sjekksum i hver pakke lar mottakeren oppdage korrupte data.
Tilbakemelding: ACK og NAK fra mottaker til sender.
Retransmisjon: Ved NAK sender senderen pakken på nytt.

Protokollen er stop-and-wait: senderen sender én pakke, og venter deretter på respons fra mottakeren før den sender neste.

Sender FSM (rdt 2.0) Vent på kall ovenfra Vent på ACK/NAK rdt_send(data) / make_pkt, udt_send rcv ACK → Λ rcv NAK → udt_send(pkt)
Senderen venter på ACK/NAK. Ved NAK retransmitteres pakken. Ved ACK fortsetter senderen med neste.
Fatalt problem med rdt 2.0

Hva skjer hvis selve ACK/NAK-en blir korrupt? Senderen vet ikke om mottakeren fikk pakken eller ei. Å bare sende på nytt kan gi duplikater som mottakeren ikke kan skille fra nye data. rdt 2.0 er ødelagt.

Sjekk forståelsen

Hva er det fundamentale problemet med rdt 2.0?

03 · rdt 2.1

Sekvensnumre — håndtere duplikater

Løsningen på det fatale problemet: legg til et sekvensnummer slik at mottakeren kan gjenkjenne og kaste duplikater.

Ideen er enkel men kraftfull: senderen merker hver pakke med et sekvensnummer. Hvis ACK/NAK er korrupt, retransmitterer senderen uansett. Mottakeren sjekker sekvensnummeret — er det det hun forventer, leverer hun data opp. Er det et duplikat, kaster hun det stille og sender ACK på nytt.

Nøkkelobservasjon

For stop-and-wait trenger vi bare to sekvensnumre: 0 og 1. Hvorfor? Fordi senderen aldri har mer enn én ubekreft pakke ute om gangen. Mottakeren trenger bare skille mellom «den pakken jeg venter på» og «den forrige pakken (duplikat)».

Hva endret seg?

KomponentEndring fra rdt 2.0
SenderLegger til seq# (0 eller 1) i pakken. Ved korrupt eller NAK: retransmitterer med samme seq#. Dobbelt så mange tilstander — må huske om forventet seq# er 0 eller 1.
MottakerSjekker seq# mot forventet. Hvis riktig: lever data opp, send ACK. Hvis duplikat: kast data, send ACK likevel (senderen trenger bekreftelsen).
Sender FSM (rdt 2.1) — forenklet Vent kall 0 ovenfra Vent ACK/NAK 0 Vent kall 1 ovenfra send(0,data) rcv ACK corrupt/NAK → resend ... og speilbilde for seq# 1 (vist forenklet) etter ACK for seq 1 → tilbake til seq 0
rdt 2.1 har dobbelt så mange tilstander fordi senderen må huske om neste pakke skal ha seq# 0 eller 1. Mottakeren veksler tilsvarende.
04 · rdt 2.2

NAK-fri — bare ACK

Vi dropper NAK helt. I stedet sender mottakeren alltid en ACK — men med sekvensnummeret til den siste pakken hun mottok korrekt.

Ideen er elegant: i stedet for å si «den var feil» (NAK), sier mottakeren «den forrige var OK» (duplikat-ACK). Effekten er den samme, men vi eliminerer en hel meldingstype.

Slik fungerer det

Mottaker: Send alltid ACK med sekvensnummeret til den sist korrekt mottatte pakken.
Sender: Hvis ACK-ens sekvensnummer ikke matcher det hun nettopp sendte, behandler hun det som en NAK — retransmitter.

Eksempel: Senderen sender pakke med seq=0. Mottakeren finner bitfeil. I stedet for NAK sender hun ACK(1) — altså «den siste pakken jeg fikk korrekt hadde seq# 1». Senderen ser at hun ventet på ACK(0), men fikk ACK(1) — tolker det som feil og retransmitterer.

TCP bruker dette TCP er NAK-fri — den bruker kun ACK-er med sekvensnummer, akkurat som rdt 2.2. Duplikat-ACK-er er det grunnleggende signalet for tap i TCP.
Sjekk forståelsen

I rdt 2.2, hva gjør mottakeren når hun mottar en korrupt pakke?

05 · rdt 3.0

Tap og timeout — den komplette protokollen

Den underliggende kanalen kan nå også miste pakker helt — både datapakker og ACK-er. Sjekksum og sekvensnumre alene er ikke nok.

Med bitfeil og duplikathåndtering på plass, gjenstår én stor utfordring: pakker kan forsvinne sporløst. Senderen sender en pakke, og det kommer rett og slett aldri noe svar. Uten en ny mekanisme ville senderen ventet evig.

Løsning: countdown timer

Senderen starter en timer etter å ha sendt en pakke. Hvis ACK ikke kommer innen en «rimelig» tid, antar senderen at pakken (eller ACK-en) er tapt, og retransmitterer.

Men hva om pakken bare var forsinket, ikke tapt? Da vil retransmisjonen skape et duplikat — men det er greit! Sekvensnumrene fra rdt 2.1/2.2 lar mottakeren gjenkjenne og kaste duplikater. Mottakeren sender bare ACK med riktig sekvensnummer.

Fire scenarier

rdt 3.0 håndterer alle disse situasjonene korrekt:

(a) Ingen tap Sender Mottaker pkt0 ack0 pkt1 ack1 (b) Pakketap Sender Mottaker pkt0 ack0 × pkt1 timeout pkt1 (c) ACK-tap Sender Mottaker pkt0 × ack0 timeout pkt0 dup → kast ack0 (d) For tidlig timeout Sender Mott. pkt0 t/o pkt0 ack0 dup→kast ack0
rdt 3.0 i aksjon: (a) alt fungerer, (b) datapakke tapt, (c) ACK tapt, (d) for tidlig timeout. Protokollen håndterer alle korrekt takket være sekvensnumre og timer.
rdt 3.0 er korrekt, men...

rdt 3.0 — også kalt alternating-bit protocol — er en funksjonelt korrekt protokoll for pålitelig overføring over en kanal med bitfeil og tap. Men den er katastrofalt treg. Det skal vi se i neste seksjon.

Sjekk forståelsen

Hva skjer i rdt 3.0 hvis senderen timer ut men pakken bare var forsinket (ikke tapt)?

06 · Utnyttelsesanalyse

Stop-and-wait er forferdelig

rdt 3.0 er korrekt. Den er også så treg at den sløser bort nesten all tilgjengelig båndbredde.

La oss regne på et realistisk eksempel: en 1 Gbps lenke med 15 ms forsinkelse i hver retning (30 ms RTT), og pakker på 8000 bit.

Beregning

Dtrans = L/R = 8000 bit / 109 bit/s = 8 μs

Usender = L/R ÷ (RTT + L/R)
            = 0,008 ms / 30,008 ms
            = 0,00027 = 0,027 %

Senderen bruker bare 0,027 % av lenkens kapasitet. Resten av tiden — 99,97 % — sitter hun og venter på ACK. En gigabit-lenke oppfører seg som en 267 kbps-lenke. Protokollen er den dominerende flaskehalsen, ikke nettverket.

Sender Mottaker L/R pkt ACK RTT + L/R Senderen er aktiv i kun L/R av hele syklusen
Stop-and-wait: senderen sender én pakke og venter hele RTT + L/R før neste. Resultatet er katastrofalt lav utnyttelse.
07 · Pipelining

Fyll røret — send flere pakker

Løsningen på stop-and-wait-problemet: la senderen sende flere pakker uten å vente på ACK for den forrige.

Pipelining er konseptuelt enkelt: i stedet for å vente på ACK etter hver pakke, sender vi flere pakker før noen ACK har kommet tilbake. Akkurat som et samlebånd — vi skyver kontinuerlig nye enheter inn, i stedet for å vente til den første er ferdig før vi starter den neste.

Konsekvenser av pipelining

1. Vi trenger et større rom av sekvensnumre — to (0 og 1) er ikke lenger nok.
2. Senderen og/eller mottakeren trenger buffere for å holde styr på pakker som er sendt men ikke bekreftet, eller mottatt men ikke levert.
3. Vi trenger nye regler for hvordan vi håndterer tap og retransmisjon når mange pakker er «i flukt» samtidig.

Effekten

Hvis vi sender 3 pakker i pipeline i stedet for 1:

Forbedring

Usender = 3 × L/R ÷ (RTT + L/R)
            = 0,024 ms / 30,008 ms
            = 0,00081 = 0,081 %

Tredoblet utnyttelse med bare 3 pakker i pipeline! Med et større vindu nærmer vi oss 100 % utnyttelse.

To fundamentalt forskjellige tilnærminger til pipelining har vokst fram: Go-Back-N og Selective Repeat. De gjør ulike avveininger mellom enkelhet og effektivitet.

Sjekk forståelsen

Hvorfor trenger pipelining mer enn to sekvensnumre (0 og 1)?

08 · Go-Back-N

Kumulativ ACK og vinduet

Go-Back-N (GBN) er den enkleste pipelining-protokollen: mottakeren buffrer ingenting, og senderen går tilbake og sender alt på nytt fra der det gikk galt.

I GBN har senderen et vindu av størrelse N: hun får lov til å ha opptil N pakker «i flukt» (sendt men ikke bekreftet) samtidig. Vinduet glir framover etterhvert som ACK-er kommer. Mottakeren er enkel — hun godtar bare pakker i riktig rekkefølge.

Senderens vindu

0
1
2
3
4
5
6
7
8
9
10
11

ACK-et    Sendt, venter på ACK    Kan sendes (vindu ledig)    Kan ikke sendes ennå

Nøkkelregler

EgenskapGBN-regelen
ACK-typeKumulativ: ACK(n) bekrefter alle pakker opp til og med n.
TimerÉn timer for den eldste ubekreftede pakken.
TimeoutVed timeout: retransmitter alle pakker i vinduet — fra den eldste ubekreftede og utover.
MottakerGodtar bare neste forventede sekvensnummer. Alt annet kastes. Sender ACK for siste korrekte.
Sender (N=4) Mottaker pkt0 pkt1 × pkt2 (TAPT) pkt3 ack0 ack1 pkt3 out-of-order → kast, send ack1 ack1 (dup) pkt2 timeout! Send alt fra pkt2 på nytt pkt2 (resendt) pkt3 (resendt) ack2 ack3
Go-Back-N i aksjon: pkt2 går tapt. Mottakeren kaster pkt3 (out-of-order). Ved timeout sender senderen pkt2, pkt3 (og evt. flere) på nytt.
Ulempen med GBN

Én tapt pakke kan føre til at mange korrekt mottatte pakker kastes og må sendes på nytt. Med et stort vindu (f.eks. N=1000) kan et enkelt tap utløse retransmisjon av hundrevis av pakker — en enorm sløsing med båndbredde.

Sjekk forståelsen

I Go-Back-N med vinduestørrelse N=4, hva gjør mottakeren når hun mottar pkt5 men venter på pkt3?

09 · Selective Repeat

Individuell ACK og buffring

Selective Repeat (SR) unngår unødvendig retransmisjon: mottakeren buffrer out-of-order pakker og senderen retransmitterer bare den ene pakken som faktisk gikk tapt.

GBN kaster alt som kommer ute av rekkefølge. Det er enkelt, men i et nettverk med lave tapsnivåer er det vanvittig sløsete. Selective Repeat tar en annen tilnærming: mottakeren buffrer pakker som kommer ut av rekkefølge, og sender individuelle ACK-er for hver pakke hun mottar korrekt — uavhengig av rekkefølge.

Nøkkelforskjeller fra GBN

EgenskapGo-Back-NSelective Repeat
ACK-typeKumulativIndividuell per pakke
TimerÉn for eldsteÉn per uACK-et pakke
Mottaker-bufferNei — kast out-of-orderJa — buffr out-of-order
RetransmisjonAlt fra tap-punktetBare den tapte
KompleksitetLavHøyere

Sender- og mottakerregler

SR-senderen

Data ovenfra: Hvis neste seq# er innenfor vinduet, send pakken og start en timer for den.
Timeout(n): Retransmitter bare pakke n, restart dens timer.
ACK(n): Merk pakke n som mottatt. Hvis n er den eldste ubekreftede, skyv vinduet fram til neste ubekreftede.

SR-mottakeren

Pakke n i [rcvbase, rcvbase+N-1]: Send ACK(n). Hvis ut av rekkefølge: buffr. Hvis in-order: lever alle sammenhengende buffrede pakker opp, skyv vinduet.
Pakke n i [rcvbase-N, rcvbase-1]: Send ACK(n) likevel — senderen trenger kanskje bekreftelsen.
Ellers: Ignorer.

Sender (N=4) Mottaker pkt0 pkt1 × pkt2 (TAPT) pkt3 ack0 ack1 pkt3 buffret ack3 pkt2 timeout! Send bare pkt2 på nytt pkt2 (resendt) pkt2 mottatt → lever 2,3 opp ack2
Selective Repeat: pkt2 tapt, men pkt3 buffres hos mottakeren. Bare pkt2 retransmitteres. Når den ankommer, leveres både pkt2 og pkt3 opp.

SR-dilemmaet: sekvensnummerrom og vinduestørrelse

Selective Repeat har en subtil felle: hvis sekvensnummerrommet er for lite i forhold til vinduestørrelsen, kan mottakeren ikke skille mellom en ny pakke og en retransmisjon av en gammel.

Eksempel på dilemmaet

Sekvensnumre: 0, 1, 2, 3 (4 mulige). Vinduestørrelse: 3.
Scenario A: Sender sender pkt0, pkt1, pkt2. Alle ACK-er kommer fram. Sender sender pkt3, pkt0 (ny data), pkt1. Alt er bra — mottakeren vet at den nye pkt0 er ny data.
Scenario B: Sender sender pkt0, pkt1, pkt2. Alle tre ACK-er går tapt. Sender timeouter og resender pkt0. Mottakeren har allerede mottatt pkt0, pkt1, pkt2 og venter nå på pkt3 — men hun ser pkt0 komme og tror det er ny data med seq# 0.

Mottakeren kan ikke se forskjell! Oppførselen hennes er identisk i begge scenarier.

Regelen for vinduestørrelse

For å unngå dette dilemmaet må vinduestørrelsen N oppfylle:
Selective Repeat: N ≤ 2k-1 (der k = antall bits i seq#-feltet)
Go-Back-N: N ≤ 2k - 1

Sjekk forståelsen

Hva er den viktigste forskjellen mellom Go-Back-N og Selective Repeat når en pakke går tapt?

Oppsummering: evolusjon av pålitelig overføring

rdt 1.0
Perfekt kanal

Triviell: send og motta. Ingen mekanismer nødvendig.

rdt 2.0
Bitfeil i data

+ Sjekksum, ACK/NAK, retransmisjon. Fatalt problem: korrupte ACK/NAK.

rdt 2.1
Bitfeil i data og ACK/NAK

+ Sekvensnumre (0/1). Mottaker kaster duplikater.

rdt 2.2
Samme som 2.1

− NAK fjernet. Duplikat-ACK erstatter NAK. TCP gjør dette.

rdt 3.0 (alternating-bit)
Bitfeil + tap

+ Countdown timer. Korrekt, men stop-and-wait = forferdelig ytelse.

Pipelining
Realistisk nettverk

+ Flere pakker i flukt. Krever større seq#-rom og buffring. → GBN eller SR.

✶ ✶ ✶

Denne seksjonen har bygget opp hele det konseptuelle grunnlaget for pålitelig overføring. Hvert prinsipp vi har sett — sekvensnumre, ACK-er, timer, kumulative bekreftelser, vindu, pipelining — dukker opp igjen i TCP. Forskjellen er at TCP kombinerer alle disse mekanismene i én protokoll, tilpasset den virkelige verdens behov.

Neste steg: TCP i praksis — segmentstruktur, pålitelig levering, flytkontroll og forbindelsesstyring.

Se også: Transportlagets tjenester, multipleksing og UDP — grunnlaget vi bygger videre på.