Kapittel 2 · Del 3

Hvordan internett snakker: e-post, navn og sokler

En grundig gjennomgang av tre av applikasjonslagets viktigste byggesteiner — protokollene som flytter e-posten din, oversetter navn til adresser, og lar prosesser snakke sammen på tvers av jordkloden.

Det du sitter igjen med
  • Hvorfor SMTP fortsatt ser ut som en samtale fra 1982 — og hvorfor det er en god ting
  • Hvordan DNS klarer å være både desentralisert og lynraskt på samme tid
  • Forskjellen mellom å programmere mot UDP og TCP, og når du bør velge hva
  • De gjennomgående temaene: push vs pull, tilstand vs tilstandsløs, kompleksitet i kanten
01 · E-post og SMTP

Den eldste meldingstjenesten på nettet

E-post fantes før weben, før Facebook, før alt egentlig. Når du forstår hvordan en e-post faktisk reiser fra deg til mottakeren, forstår du også hvorfor hele applikasjonslaget er bygd rundt et lite knippe gjentakende mønstre.

La oss begynne med et spørsmål som høres trivielt ut, men som ikke er det: når du trykker «Send» i Gmail, hva skjer egentlig? Du vet at meldingen havner hos mottakeren — men mellom «trykk» og «levert» ligger det et helt lite økosystem av programvare, og det er det økosystemet vi nå skal pakke ut.

E-postsystemet består av tre hovedkomponenter. Det er klientprogrammet du bruker for å lese og skrive — kalt user agent eller «mail reader». Det er e-postserverne som faktisk lagrer meldinger, både i en innboks for det som kommer inn og i en utgående kø for det som skal sendes. Og så er det SMTP, protokollen som binder serverne sammen og flytter meldinger mellom dem.

Det viktige å innse er at user agent-en aldri snakker direkte med mottakerens user agent. Posten din havner først hos din mailserver, som så åpner en SMTP-forbindelse mot mottakerens mailserver og leverer den. Mottakeren plukker den så opp fra sin egen server med en helt annen protokoll. Dette er ikke en tilfeldighet — det er et bevisst design som vi straks skal se hvorfor er smart.

Reisen til en e-post

Tenk deg at Alice vil sende en melding til bob@someschool.edu. Sekvensen er alltid den samme:

Alice user agent Alice sin mail-server utgående kø Bob sin mail-server postkasse Bob user agent SMTP/HTTPS SMTP IMAP/HTTPS Tre etapper, tre roller. Avsenderens server snakker med mottakerens server.

Legg merke til en fundamental asymmetri her: SMTP brukes kun mellom serverne (og fra Alice sin user agent inn til hennes egen server). Når Bob skal lese posten, bruker han ikke SMTP. Han bruker en «mail access protocol» — typisk IMAP, eller HTTPS dersom han bruker Gmail i nettleseren. Hvorfor? Fordi SMTP er en push-protokoll: avsenderen tar initiativet og dytter meldingen frem. Men når Bob skal hente post, vil han trekke meldinger ned fra sin egen server. Det er et helt annet bruksmønster, og det krever en helt annen protokoll.

SMTP er fra 1982 (RFC 821, oppdatert til RFC 5321). At den fortsatt er i bruk, mer eller mindre uendret, sier noe om hvor godt den løste problemet sitt — og hvor vanskelig det er å bytte ut etablerte protokoller.

SMTP — en samtale i klartekst

SMTP er en TCP-basert protokoll på port 25. Den er bemerkelsesverdig enkel: kommandoer sendes som ASCII-tekst, og serveren svarer med en tresifret statuskode etterfulgt av en kort tekst. Hele samtalen mellom to mailservere ser slik ut:

SMTP-samtale — steg for steg
S: 220 hamburger.eduC: HELO crepes.frS: 250 Hello crepes.fr, pleased to meet youC: MAIL FROM: <alice@crepes.fr>S: 250 alice@crepes.fr... Sender okC: RCPT TO: <bob@hamburger.edu>S: 250 bob@hamburger.edu ... Recipient okC: DATAS: 354 Enter mail, end with "." on a line by itselfC: Do you like ketchup?C: How about pickles?C: .S: 250 Message accepted for deliveryC: QUITS: 221 hamburger.edu closing connection
Trykk «Neste» for å se hver linje i SMTP-samtalen mellom to mailservere.
Steg 0 / 15

Dette er hele protokollen i et nøtteskall. Tre faser: håndhilsing (HELO), overføring (MAIL FROM, RCPT TO, DATA), og avslutning (QUIT). Klienten — det er den sendende mailserveren her — skriver kommandoer, serveren svarer med statuskoder. 250 betyr OK, 354 betyr «fortsett», 220 og 221 er åpning og lukking. Akkurat som HTTP, men eldre.

En liten, men viktig detalj: legg merke til at meldingen avsluttes med en linje som bare inneholder ett enkelt punktum. Dette er SMTP sin måte å si «her slutter meldingen». Eller mer presist: serveren ser etter sekvensen CRLF.CRLF for å finne slutten på meldingsinnholdet. Og fordi alt skal være 7-bits ASCII, må alt binært (bilder, vedlegg, æøå) først kodes om til ASCII med MIME-koding.

Sjekk forståelsen
Hvorfor bruker e-post to forskjellige protokoller (SMTP og IMAP) i stedet for bare én?
SMTP er designet for at avsenderen skal kunne dytte en melding frem til mottakerens server. Når mottakeren senere skal hente posten sin — kanskje fra en mobiltelefon, kanskje senere i dag, kanskje fra flere enheter — er det et helt annet behov. IMAP løser det problemet: tilgang til en postkasse som ligger lagret på en server.

SMTP vs HTTP — så like, og likevel så ulike

Det er fristende å se på SMTP og HTTP og tenke at de er nesten det samme. Begge bruker TCP. Begge har ASCII-kommandoer og statuskoder. Begge ble bygd i samme tidsperiode. Men forskjellene avslører to helt forskjellige designfilosofier:

EgenskapHTTPSMTP
InitiativPull (klienten ber)Push (avsender dytter)
InnholdHver ressurs i sin egen responsFlere objekter i én multipart-melding
TegnsettBinært tillattKrever 7-bits ASCII
ForbindelsePersistent (HTTP/1.1+)Persistent
BruksområdeHente sider/data på forespørselLevere meldinger asynkront

Disse forskjellene er ikke vilkårlige. De følger direkte av problemene protokollene løser. Når du henter en nettside, er du der akkurat nå, og du venter på svaret. Når du sender e-post, skal mottakeren kanskje lese den om to dager — så meldingen må kunne legges i en kø, mellomlagres, og leveres når det passer.

Meldingsformatet — RFC 822

SMTP er protokollen som flytter meldingen, men selve meldingen — det som ligger inne i DATA-blokken — har sitt eget format definert i RFC 822. Sammenligningen er omtrent som mellom HTTP og HTML: HTTP frakter, HTML er det som fraktes.

En e-postmelding består av headerlinjer (To:, From:, Subject: og så videre), en blank linje, og en body. Det er viktig å skille disse headerne fra SMTP-kommandoene MAIL FROM og RCPT TO — sistnevnte er en del av forhandlingen mellom serverne (omtrent som en konvolutt), mens RFC 822-headerne er en del av selve brevet som ligger inne i konvolutten. De inneholder ofte de samme adressene, men trenger ikke å gjøre det. (Det er nettopp dette gapet noen typer phishing utnytter.)

Hvordan moderne webmail egentlig ser ut

I praksis ser ikke trafikken din ut akkurat som læreboken. Når du bruker Gmail eller Outlook på web, er det ikke SMTP du snakker fra nettleseren din ut til serveren — det er HTTPS. Først når Gmails servere skal levere meldingen videre til mottakerens server, kommer SMTP inn i bildet. Og når mottakeren — også på web — leser den, er det igjen HTTPS hele veien til nettleseren.

Oppsummert
Webmail = HTTPS fra deg til din server, SMTP mellom serverne, HTTPS fra mottakerens server til mottakeren. Tradisjonell e-postklient (Outlook, Apple Mail) = SMTP fra deg ut, SMTP mellom serverne, IMAP fra mottakerens server til mottakeren. Blanding av disse er normalen.
❋ ❋ ❋
02 · DNS — Domain Name System

Hvordan internett finner ut hvor ting er

Mennesker husker navn. Datamaskiner ruter pakker etter tall. DNS er den enorme, distribuerte oversetter-maskinen som bygger broen mellom de to verdenene — og den må gjøre det milliarder av ganger hver dag, uten å bryte sammen.

Hver gang du skriver www.ntnu.no i nettleseren din, skjer det noe nesten umiddelbart som er så raskt at du aldri legger merke til det: et navn må oversettes til en IP-adresse før noe annet kan skje. Denne oversettelsen er tilsynelatende triviell — bare slå opp i en tabell — men den må fungere for hvert eneste navn på hele internett, og hvert eneste navn må kunne endres når noen flytter en tjeneste til en ny server. Dette er ikke trivielt.

Hvorfor ikke bare én stor tabell?

La oss starte med den naive løsningen, som er pedagogisk verdifull nettopp fordi den ikke fungerer. Hva om vi hadde én sentral server et sted i verden, med en gigantisk tabell over alle navn-til-IP-mappinger? Klienten spør, serveren svarer, ferdig.

Problemet med sentralisering
Comcast sine DNS-servere alene håndterer rundt 600 milliarder forespørsler hver dag. En sentral server ville (1) være et enkeltpunkt for feil — hele internett dør om den ryker, (2) ha umulig trafikkmengde, (3) være langt unna for de fleste brukere, og (4) være et mareritt å vedlikeholde. Det skalerer rett og slett ikke.

Løsningen — og dette er kanskje den vakreste designideen i hele protokollverdenen — er å gjøre DNS distribuert og hierarkisk. Ingen enkelt server vet alt. I stedet vet hver server litt, og vet hvem den skal spørre om resten.

Hierarkiet

Tenk på navnet www.amazon.com. Hierarkiet bygges fra høyre mot venstre. Helt på toppen sitter root-serverne. De vet ikke noe om amazon, men de vet hvem som styrer .com. Under root sitter TLD-serverne (Top-Level Domain) — én gruppe for .com, én for .org, én for .no, og så videre. .com-serveren vet ikke IP-adressen til www.amazon.com, men den vet hvilken server som er autoritativ for amazon.com. Den autoritative serveren — som driftes av Amazon selv — vet til slutt det faktiske svaret.

Root .com .org .edu amazon.com yahoo.com pbs.org nyu.edu umass.edu Nivå 1 TLD Autoritativ Hierarkiet — ingen enkelt server vet alt, men hver server vet hvor du skal gå videre.

Det er omtrent 13 logiske rot-«servere» i verden, men hver av dem er replikert hundrevis av ganger ved hjelp av en teknikk som heter anycast — over 1000 fysiske maskiner totalt. ICANN (Internet Corporation for Assigned Names and Numbers) administrerer rotsonen. Disse er kontaktpunktet for siste utvei: en lokal navneserver kontakter root-serverne kun når den ikke har funnet svaret andre steder.

Lokale navneservere — den glemte helten

Mellom deg og hele dette hierarkiet sitter det noen som ikke teknisk sett er en del av selve DNS-hierarkiet, men som likevel er den absolutt viktigste serveren for ytelsen din: din lokale navneserver. Hver internettleverandør driver en. NTNU har en. Bedriften din har en. Når maskinen din skal slå opp et navn, går spørsmålet alltid først hit.

Den lokale serveren har en lokal cache med navn den nylig har slått opp. Hvis svaret ligger i cachen, slipper du hele turen oppover hierarkiet — du får svaret tilbake på millisekunder. Dette er hvorfor DNS oppleves som umiddelbar selv om det egentlig kan involvere flere mellomledd. De fleste forespørsler stopper i cachen.

To måter å spørre på: iterativ vs rekursiv

Når en navneserver må gå videre i hierarkiet, finnes det to fundamentale strategier. La oss følge et eksempel: en maskin på engineering.nyu.edu vil vite IP-adressen til gaia.cs.umass.edu.

Steg-for-steg — iterativ DNS-oppslag
requesting host engineering.nyu.edu local DNS dns.nyu.edu root DNS .edu TLD authoritative dns.cs.umass.edu
Trykk «Neste» for å se hvert steg i en iterativ DNS-oppslag.
Steg 0 / 8

Det du så i animasjonen ovenfor er en iterativ oppslag: hver server svarer ikke med endelig svar, men med «jeg vet ikke, men spør denne i stedet». Den lokale serveren gjør alt arbeidet selv — den hopper fra root til TLD til autoritativ.

Den andre strategien er rekursiv: den lokale serveren spør root-serveren og sier «finn ut av dette for meg», og root-serveren tar selv ansvaret for å spørre TLD, som spør den autoritative, og svaret bobler tilbake gjennom hele kjeden. Dette er enklere å implementere på klientsiden, men legger en mye tyngre belastning på de øverste lagene av hierarkiet — og derfor brukes det typisk bare på den siste delen, mellom deg og din lokale server.

DNS-poster (Resource Records)

Hva er det egentlig som ligger lagret i alle disse navneserverne? Svaret er en distribuert database av resource records, eller RR-er. Hver post har formatet (name, value, type, ttl) — et navn, en verdi, en type, og en «time to live» som sier hvor lenge svaret er gyldig.

TypeHva «name» erHva «value» er
AEt hostnameEn IPv4-adresse
NSEt domene (f.eks. amazon.com)Hostname til en autoritativ navneserver
CNAMEEt alias-navnDet «kanoniske» (egentlige) navnet
MXEt domeneHostname til en mailserver for det domenet

A-posten er den klassiske: navn til IP. NS peker på hvilken server som er autoritativ. CNAME er et alias — derfor kan www.ibm.com egentlig være en peker til noe sånt som servereast.backup2.ibm.com, og du som bruker trenger aldri å vite det. MX er hvordan SMTP-serveren din finner ut hvor den skal levere e-post for et domene; uten MX-poster ville e-post ikke fungert.

DNS muliggjør også lastbalansering: et travelt nettsted kan ha mange servere med forskjellige IP-adresser, alle registrert som A-poster for samme hostname. Den autoritative DNS-serveren roterer rekkefølgen på adressene for hvert oppslag, slik at trafikken sprer seg utover mange servere. CNAME gir derimot aliasing — muligheten til å la ett hostname peke på et annet — noe som er nyttig, men en annen mekanisme enn lastbalansering.

Hvordan får man et nytt domene inn i DNS?

Tenk deg at du starter et nytt firma, «Network Utopia», og vil ha domenet networkutopia.com. Hvordan får du det inn i den globale DNS-databasen? Svaret er en registrar — en kommersielt autorisert organisasjon (akkreditert av ICANN) som lar deg registrere domenenavn.

Prosessen er omtrent slik: du går til en registrar (for eksempel GoDaddy eller Domeneshop), sjekker at networkutopia.com er ledig, betaler en avgift, og oppgir navn og IP-adresser til dine autoritative navneservere — la oss si dns1.networkutopia.com (212.44.9.129) og dns2.networkutopia.com (212.44.9.130). Registraren setter da inn to poster i .com-TLD-serverne:

(networkutopia.com, dns1.networkutopia.com, NS)
(dns1.networkutopia.com, 212.44.9.129, A)

Nå vet .com-TLD-serverne at spørsmål om networkutopia.com skal sendes videre til dine autoritative servere. På disse serverne legger du selv inn A-poster for webserveren din (www.networkutopia.com → IP-adresse) og MX-poster for e-postserveren. Fra det øyeblikket er hele kjeden komplett: root → TLD → autoritativ → endelig svar.

Caching — den hemmelige sausen

Det vi egentlig ikke har sagt eksplisitt ennå er hvordan DNS klarer å være så raskt. Svaret er caching, overalt. Når en navneserver — hvilken som helst — lærer en mapping, lagrer den den i minnet sitt for fremtidige forespørsler. Den lagrer den til TTL utløper. TLD-server-adresser er typisk cachet i lokale servere stort sett alltid, så root-serverne blir nesten aldri kontaktet for vanlige oppslag.

Bakdelen er at cachede svar kan bli utdatert. Hvis du flytter en tjeneste til en ny IP-adresse, vil ikke endringen være synlig overalt på internett før alle relevante TTL-er har utløpt. Dette er hvorfor DNS-endringer ofte tar timer eller dager å «forplante seg» — det er ikke en feil, det er TTL-en som gjør jobben sin.

Hva tror du skjer hvis du sender e-post til noen@example.com, men example.com ikke har noen MX-post i DNS?

Mailserveren din vil først spørre etter MX-posten for example.com. Hvis den ikke finner noen, vil den i de fleste implementasjoner falle tilbake på å spørre etter en A-post for example.com direkte og prøve å levere til den IP-adressen. Dette er en historisk fallback fra tidlig SMTP. I praksis bør alle domener som mottar e-post ha MX-poster — uten dem er du avhengig av at avsendere implementerer fallbacken, og noen gjør ikke det.

DNS-meldinger

DNS bruker stort sett UDP på port 53 — det er en spørsmål-svar-protokoll der hvert spørsmål er kort nok til å passe i én UDP-pakke, og lavere overhead enn TCP gir mye bedre ytelse for noe som skjer milliarder av ganger om dagen. Både spørsmål og svar har samme meldingsformat: en header med en 16-bits identifikator og noen flagg, etterfulgt av seksjoner for spørsmål, svar, autoritative poster, og «hjelpsom tilleggsinformasjon».

Identifikatoren er viktig: når en server får mange svar tilbake, må den vite hvilket spørsmål hvert svar tilhører, og det er det dette tallet brukes til. Flagg-feltet sier blant annet om meldingen er et spørsmål eller svar, om rekursjon er ønsket, og om svaret kommer fra en autoritativ kilde.

DNS-sikkerhet

DNS er kritisk infrastruktur, og det betyr at noen vil angripe det. De vanligste angrepstypene er:

  • DDoS — bombardere root- eller TLD-serverne med trafikk. Har vært forsøkt mange ganger; har hittil aldri klart å lamme internett, blant annet på grunn av caching og trafikkfiltrering.
  • DNS-poisoning — sende falske svar til en navneserver slik at den cacher feil informasjon, og sender brukere til feil sted.
  • DNS-amplifisering — sende DNS-spørsmål med forfalsket avsenderadresse, slik at svarene (som er mye større enn spørsmålene) sendes til offeret. Brukes som DDoS-våpen.

Forsvaret heter DNSSEC (RFC 4033), som legger digitale signaturer på DNS-svar slik at klienten kan verifisere at svaret faktisk kommer fra en autoritativ kilde. Det er ikke universelt utrullet ennå, men det er den langsiktige løsningen.

Sjekk forståelsen
Hvorfor blir root-serverne sjelden faktisk kontaktet, selv om de teknisk sett er øverst i hierarkiet?
Lokale navneservere har stort sett alltid TLD-servernes adresser i cachen sin, så den iterative oppslagsprosessen kan hoppe rett til TLD og videre derfra. Root blir dermed «kontakt-av-siste-utvei» — viktig at den finnes, men sjelden faktisk forespurt.
❋ ❋ ❋
03 · Socket-programmering

Når du selv skal skrive noe som snakker

Alle protokollene vi har sett på — SMTP, HTTP, DNS — er bygget oppå noe lavere: socket-API-et. Når du skal skrive ditt eget nettverksprogram, er det her du faktisk møter transportlaget ansikt til ansikt.

En sokkel er et merkelig ord for noe så fundamentalt. Tenk på den som en dør: på den ene siden står prosessen din, på den andre siden står transportlaget i operativsystemet. Du som programmerer kontrollerer det som skjer på innsiden av døren. Operativsystemet styrer alt som skjer på utsiden — pakkene, rutingen, retransmisjon. Sokkelen er kontrakten mellom dere to.

application proc transport network link / physical Internett application proc transport network link / physical styres av deg styres av OS socket socket
Sokkelen er døren mellom prosessen din og transportlaget — på begge sider av forbindelsen.

Det finnes to typer sokler, fordi det finnes to transporttjenester du kan bruke: UDP for upålitelige datagrammer, og TCP for pålitelig, ordnet bytestrøm. Forskjellen mellom dem er stor nok til at API-ene faktisk ser annerledes ut — du bruker forskjellige funksjoner for å sende og motta — og valget mellom dem er det første og kanskje viktigste designvalget du tar når du skriver et nettverksprogram.

Et lite eksempelprogram

Gjennom resten av denne delen skal vi bygge en helt enkel applikasjon, både i UDP-versjon og TCP-versjon, slik at forskjellen kommer tydelig frem. Applikasjonen er bevisst trivielt:

Spesifikasjon
Klienten leser en linje fra tastaturet og sender den til serveren. Serveren konverterer linjen til store bokstaver og sender den tilbake. Klienten skriver ut svaret. Det er det. Hele protokollen.

UDP — datagrammer i posten

Det første du må forstå om UDP er at det ikke finnes noen forbindelse. Det er ingen håndhilsing før data sendes. Hver pakke står på egen hånd. Avsenderen putter eksplisitt på destinasjons-IP og portnummer for hver eneste pakke, og mottakeren får ut avsenderens IP og port fra hver pakke som kommer inn. Det er som å sende postkort — hvert postkort er sin egen lille hendelse, og det finnes ingen samtale.

Konsekvensene er viktige: pakkene kan bli borte, og de kan komme i feil rekkefølge. Operativsystemet vil ikke gjøre noe med det. Hvis applikasjonen din krever pålitelighet, må du bygge det selv. Til gjengjeld er UDP ekstremt lett — ingen oppstartstid, ingen tilstand, ingen ekstra runtripp før første byte kan sendes.

La oss se på koden. Først klienten:

# Python UDP-klient
from socket import *
serverName = 'hostname'
serverPort = 12000

# Lag en UDP-sokkel
clientSocket = socket(AF_INET, SOCK_DGRAM)

message = input('Skriv en setning: ')

# Vi må selv legge ved adresse og port for hver send
clientSocket.sendto(message.encode(),
                    (serverName, serverPort))

# Les svar — vi får også vite hvem som svarte
modifiedMessage, serverAddress = clientSocket.recvfrom(2048)
print(modifiedMessage.decode())

clientSocket.close()

Legg merke til SOCK_DGRAM — det er det som sier «UDP, takk». Og legg merke til sendto: det andre argumentet er en tuple med servernavn og port. Vi må oppgi det hver eneste gang vi sender, fordi sokkelen ikke har noen forestilling om hvem den «er forbundet med». Tilsvarende returnerer recvfrom både dataene og avsenderens adresse — for hvert datagram kan teknisk sett komme fra en hvilken som helst avsender.

Serveren ser slik ut:

# Python UDP-server
from socket import *
serverPort = 12000

serverSocket = socket(AF_INET, SOCK_DGRAM)
serverSocket.bind(('', serverPort))   # Lytt på port 12000

print("Serveren er klar.")

while True:
    message, clientAddress = serverSocket.recvfrom(2048)
    modifiedMessage = message.decode().upper()
    serverSocket.sendto(modifiedMessage.encode(),
                        clientAddress)

Serveren er nesten enklere enn klienten. Den lager en sokkel, binder den til en spesifikk port (12000), og går inn i en evig løkke. For hvert datagram som kommer inn, leser den både innholdet og klientens adresse — og bruker akkurat den adressen til å sende svaret tilbake. Det finnes ingen «forbindelse» som blir åpnet eller lukket. Hver iterasjon i løkken er en hel transaksjon i seg selv.

TCP — en samtale fra start til slutt

TCP er en helt annen verden. Før noen data kan flyte, må klienten opprette en forbindelse til serveren — det berømte trefoldige håndtrykket. Når forbindelsen er etablert, har du noe som oppfører seg som et rør: bytes du sender inn på den ene enden kommer ut i samme rekkefølge på den andre enden, garantert, selv om underliggende pakker ble mistet og måtte sendes på nytt.

Det fine designtrekket — og noe som kan være forvirrende første gang du møter det — er at serveren faktisk har to typer sokler. Den ene er en «velkomstsokkel» som sitter og venter på at klienter skal koble seg til. Når en klient kobler seg til, lager operativsystemet en helt ny sokkel — en «forbindelsessokkel» — som er dedikert til akkurat denne klienten. Velkomstsokkelen går tilbake til å vente på neste klient, og forbindelsessokkelen brukes til å snakke med klienten som nettopp koblet seg til.

Dette er hvordan en server kan snakke med mange klienter samtidig: én velkomstsokkel, mange forbindelsessokler — én per aktiv klient. Operativsystemet bruker portnummer på avsendersiden til å holde dem fra hverandre.

Klientkoden:

# Python TCP-klient
from socket import *
serverName = 'servername'
serverPort = 12000

clientSocket = socket(AF_INET, SOCK_STREAM)
clientSocket.connect((serverName, serverPort))   # 3-veis håndtrykk skjer her

sentence = input('Skriv en setning: ')

# Ingen behov for å oppgi adresse — sokkelen vet det allerede
clientSocket.send(sentence.encode())

modifiedSentence = clientSocket.recv(1024)
print('Fra serveren:', modifiedSentence.decode())

clientSocket.close()

Forskjellene fra UDP-versjonen er små men avgjørende. SOCK_STREAM i stedet for SOCK_DGRAM. Den nye linjen connect() som ikke fantes i UDP — det er her selve TCP-forbindelsen blir opprettet. Og en gang det er gjort, bruker vi send og recv uten å oppgi noen adresse, fordi sokkelen vet hvem den er koblet til.

Serverkoden er der den interessante forskjellen virkelig blir tydelig:

# Python TCP-server
from socket import *
serverPort = 12000

serverSocket = socket(AF_INET, SOCK_STREAM)
serverSocket.bind(('', serverPort))
serverSocket.listen(1)            # Begynn å lytte etter forbindelser

print("Serveren er klar.")

while True:
    # accept() blokkerer til en klient kobler seg til,
    # og returnerer da en HELT NY sokkel for denne klienten
    connectionSocket, addr = serverSocket.accept()

    sentence = connectionSocket.recv(1024).decode()
    capitalizedSentence = sentence.upper()
    connectionSocket.send(capitalizedSentence.encode())

    connectionSocket.close()       # Lukk DENNE forbindelsen, ikke velkomstsokkelen

Se på serverSocket.accept(). Det er der det magiske skjer. Funksjonen blokkerer til en klient prøver å koble seg til, og når det skjer, returnerer den et par: en ny sokkel (connectionSocket) og adressen til klienten. Vi snakker med klienten gjennom denne nye sokkelen, og når vi er ferdige lukker vi kun den — velkomstsokkelen lever videre og venter på neste klient.

UDP og TCP — side om side

UDPTCP
SokkeltypeSOCK_DGRAMSOCK_STREAM
ForbindelseIngenEtableres med håndtrykk
PålitelighetIngen — pakker kan mistesGarantert levering, riktig rekkefølge
Sendesendto(data, adresse)send(data)
Mottarecvfrom() → data + adresserecv() → data
ServerÉn sokkel for altÉn velkomst + én pr. klient
Egnet forDNS, video, spill, telemetriHTTP, SMTP, filoverføring

Når velger man hva?

Det enkle svaret er: TCP for det meste. Det vanskelige svaret krever at du tenker på applikasjonens faktiske behov. Trenger du at hver eneste byte kommer frem? Bruk TCP. Tåler du tap, men trenger lav latenstid og kan ikke vente på retransmisjoner? Bruk UDP. Sender du noe som er så stort at TCP-forbindelsens overhead ikke betyr noe? TCP. Sender du små, hyppige, uavhengige beskjeder som DNS-oppslag eller video-pakker der en pakke som kommer 200 ms for sent like gjerne kan kastes? UDP.

Sjekk forståelsen
Hvorfor må TCP-serveren ha to sokler — en «velkomstsokkel» og en «forbindelsessokkel» — mens UDP-serveren klarer seg med én?
UDP behandler hvert datagram som en isolert hendelse — serveren bruker den samme sokkelen til å motta fra alle klienter. TCP, derimot, har en vedvarende forbindelse per klient, og hver av dem trenger sin egen sokkel slik at OS kan rute innkommende bytes til riktig samtale. Velkomstsokkelen sin eneste jobb er å «føde» nye forbindelsessokler.
Til slutt

De gjennomgående temaene

Tre forskjellige protokoller, tre forskjellige problemer — men hvis du leser dem ved siden av hverandre, ser du at de samme designvalgene dukker opp om og om igjen.

Det første temaet er sentralisert vs desentralisert. SMTP er desentralisert: hver organisasjon driver sin egen mailserver. DNS er hierarkisk, som er en form for desentralisering med koordinasjon — ingen vet alt, men strukturen sørger for at du alltid kan finne den som vet. Sentralisering er enkelt å forstå og umulig å skalere. Desentralisering er det motsatte: vanskelig å designe riktig, men eneste vei til de skalaene internett opererer på.

Det andre temaet er tilstandsløs vs tilstand. SMTP holder tilstand under en forbindelse (det er midt i en samtale). DNS er stort sett tilstandsløst — hvert spørsmål er uavhengig. UDP er fundamentalt tilstandsløst, TCP er fundamentalt tilstandsbevart. Tilstandsløse protokoller skalerer lettere fordi enhver server kan svare på ethvert spørsmål; tilstandsbevarte protokoller er nødvendige når noe må huskes på tvers av forespørsler.

Det tredje temaet er «kompleksitet i kanten». Dette er kanskje det mest karakteristiske ved internettarkitekturen i det hele tatt. Selve nettverket — rutere, kabler, IP — er bevisst dumt. All den interessante intelligensen sitter i endepunktene: i mailservere, i DNS-resolvere, i programmene du selv skriver med sokler. Dette designvalget er grunnen til at internett kunne vokse uten å måtte oppgraderes sentralt, og det er grunnen til at en student med en bærbar PC i Trondheim kan finne på en helt ny applikasjon i morgen og rulle den ut globalt uten å spørre noen om lov.

Det fjerde og siste temaet er pålitelig vs upålitelig meldingsoverføring — og innsikten at det ikke finnes ett riktig valg. TCP og UDP eksisterer side om side fordi de løser forskjellige problemer. Det samme gjelder oppover i stakken: SMTP er bygget på TCP fordi e-post må komme frem, mens DNS er bygget på UDP fordi raske oppslag som kan gjentas er bedre enn pålitelige oppslag som er trege.

Det viktigste å ta med seg
Protokoller er ikke magi. Hver eneste designvalg du har sett — push vs pull, hierarki vs sentralisering, datagram vs strøm — ble tatt fordi alternativet ikke ville fungert i den skalaen eller med de begrensningene som fantes. Når du forstår hvilket problem en protokoll løser, blir formen den tar nesten uunngåelig.