Netzwerk und HTTP: Von der Adresszeile zur Seite, DNS, TCP und HTTP/1.1, 2 und 3

Track Konzepte · Betriebssystem und Netzwerk · ca. 50 Min.

Worum es geht

Du tippst https://shop.beispiel.de/ und drückst Enter. 300 Millisekunden später steht die Seite da. Dazwischen passiert eine Kette von Schritten, und jeder davon kann langsam sein, ausfallen oder falsch konfiguriert sein: Namensauflösung (DNS), Verbindungsaufbau (TCP), Verschlüsselung (TLS), die eigentliche Anfrage (HTTP), mögliche Zwischenstationen (Cache, Proxy, Load Balancer).

Am Ende dieses Teils kannst du die Frage beantworten, was zwischen Enter und der gerenderten Seite passiert, und du kannst begründen, warum eine Seite über HTTP/3 in einem schlechten Mobilfunknetz schneller laden kann als über HTTP/2. Du hast selbst gerechnet (Rundlaufzeiten), selbst gebaut (DNS-Cache mit TTL) und selbst vorhergesagt (Head-of-Line Blocking). TLS im Detail, HTTP-Caching, Proxy und Load Balancer folgen in Teil 2.

Echte Sockets und DNS gibt es im Browser-Python (Pyodide) nicht. Darum ist hier alles eine Simulation mit Zahlen und Zeitstempeln: Zeit ist eine Zahl, Pakete sind Listeneinträge. So kommt bei jedem Lauf dasselbe heraus, und du kannst nachrechnen. Wie du echte HTTP-Antworten zerlegst, steht schon in Python 16a: Sockets, HTTP und JSON, den REST-Client mit Python baust du in Python 16b: XML und requests. Das wiederholen wir nicht.

Plane ehrlich 50 Minuten ein: etwa 22 Minuten Lesen, 28 Minuten Übungen (drei Stück). Eine gute Pause ist nach Übung 2.

Von JS/TS her gedacht

Du benutzt die Bausteine täglich, nur meist unsichtbar:

Baustein JS/TS (Frontend oder Node) Python
URL zerlegen new URL("https://a.de:8443/x?y=1") urllib.parse.urlsplit(...)
Namen auflösen dns.promises.lookup("example.org") (Node) socket.getaddrinfo(...)
Anfrage senden fetch(url) urllib.request, requests

fetch(url) ist eine Zeile. Darunter liegen DNS, TCP, TLS und HTTP, und der Browser hat meist schon einen Teil davon im Cache. Genau diese Schichten schauen wir uns an.

Konzept 1: Der Weg von Enter zur Seite

Das Schichtenmodell (layer model) hilft beim Sortieren. Jede Schicht benutzt die darunter und kümmert sich um eine Aufgabe:

Schicht Aufgabe Beispiel
Anwendung (application) Bedeutung der Daten HTTP, DNS, TLS
Transport (transport) Zustellung zwischen Programmen, über Ports TCP (zuverlässig, geordnet), UDP (schnell, ohne Garantie)
Internet (network) Adressierung und Weiterleitung zwischen Rechnern IP-Adresse, z. B. 203.0.113.7
Verbindung (link) Übertragung im lokalen Netz WLAN, Ethernet

Eine IP-Adresse findet den Rechner, der Port findet das Programm darauf. HTTPS läuft üblicherweise auf Port 443, HTTP auf 80. TCP baut eine Verbindung auf, nummeriert die Bytes, wiederholt verlorene Teile und liefert alles in der richtigen Reihenfolge. UDP schickt einzelne Pakete ohne Garantie, schneller im Start, aber du musst dich selbst um Verluste kümmern. Das wird bei HTTP/3 wichtig.

Und so sieht der Weg der Anfrage aus (ein Sequenzdiagramm, die Zeit läuft nach unten):

sequenceDiagram
  participant B as Browser
  participant R as DNS Resolver
  participant S as Server
  B->>R: Welche IP hat shop.beispiel.de?
  R-->>B: 203.0.113.7 (TTL 300 s)
  B->>S: TCP SYN
  S-->>B: SYN ACK
  B->>S: ACK + TLS ClientHello
  S-->>B: ServerHello, Zertifikat, fertig
  B->>S: GET / (verschlüsselt)
  S-->>B: 200 OK + HTML
  Note over B: Rendern, weitere Anfragen für CSS, JS, Bilder

In Worten: (1) Der Browser sucht erst in seinen eigenen Caches (HTTP-Cache, Verbindungen, DNS). (2) DNS liefert die IP. (3) TCP-Handshake baut die Verbindung auf. (4) TLS-Handshake vereinbart Schlüssel und prüft das Zertifikat des Servers. (5) HTTP: Anfrage und Antwort. (6) Der Browser rendert das HTML und holt CSS, JS und Bilder nach, oft über dieselbe Verbindung.

Rundlaufzeiten zählen

Die Pakete brauchen Zeit. Ein Round Trip (Rundlauf, RTT) ist die Zeit, die ein Paket zum Server und die Antwort zurück braucht. Bei großer Entfernung oder schlechtem Netz ist die RTT, nicht die Bandbreite, der Engpass für kleine Anfragen. Darum zählt man die Rundläufe, bis das erste Antwortbyte da ist. Annahmen für diese Lektion (vereinfacht, allgemeines Fachwissen):

  • DNS: 1 RTT zum Resolver (Antwort nicht im Cache des Resolvers, im eigenen Cache 0).
  • TCP-Handshake: 1 RTT.
  • TLS 1.2: 2 RTT. TLS 1.3: 1 RTT.
  • QUIC (die Grundlage von HTTP/3, läuft über UDP): Verbindungsaufbau und Verschlüsselung in einem gemeinsamen Handshake, zusammen 1 RTT.
  • HTTP-Anfrage bis zum ersten Antwortbyte: 1 RTT.
  • Eine bereits offene Verbindung (Keep-Alive) braucht nur noch die Anfrage: 1 RTT.
  • 0-RTT-Wiederaufnahme und weitere Tricks lassen wir weg.

Durchgerechnet für HTTP/1.1 über TLS 1.2, RTT = 50 ms, erster Besuch:

Das sind 5 Rundläufe, 250 ms, bevor das erste Byte der Seite da ist. Bei 200 ms RTT (Mobilfunk, andere Weltgegend) wären es schon 1000 ms. Jeder eingesparte Rundlauf zählt, und genau darum gibt es TLS 1.3, Keep-Alive und HTTP/3.

Übung 1: Rundläufe rechnen

Schreibe zeit_bis_erstes_byte(stack, rtt_ms, dns_im_cache=False, verbindung_offen=False). Sie gibt die Millisekunden bis zum ersten Antwortbyte zurück. Erlaubte Werte für stack: "http1_tls12" (HTTP/1.1 über TCP und TLS 1.2), "http2_tls13" (HTTP/2 über TCP und TLS 1.3), "http3" (HTTP/3 über QUIC). Ist verbindung_offen wahr, entfallen DNS und Handshakes. Beispiel: zeit_bis_erstes_byte("http1_tls12", 50) ergibt 250, mit dns_im_cache=True ergibt es 200.

Sammle pro Stack, welche Schritte vor der Anfrage nötig sind und was jeder kostet. Was fällt weg, wenn die Verbindung schon steht, und was, wenn nur DNS im Cache liegt?

def zeit_bis_erstes_byte(stack, rtt_ms, dns_im_cache=False, verbindung_offen=False):
    if verbindung_offen:
        return 1 * rtt_ms
    handshake = {"http1_tls12": 1 + 2, "http2_tls13": 1 + 1, "http3": 1}[stack]
    dns = 0 if dns_im_cache else 1
    return (dns + handshake + 1) * rtt_ms

zeit_bis_erstes_byte

Der Stack bestimmt nur die Handshake-Kosten (TCP 1 plus TLS 2 oder 1, QUIC 1 zusammen). DNS und die Anfrage selbst kosten bei allen gleich viel. Bei offener Verbindung bleibt nur die Anfrage. HTTP/2 spart gegenüber HTTP/1.1 hier keinen Handshake, der Gewinn von HTTP/2 liegt woanders (Multiplexing, siehe unten). Beachte: HTTP/2 über TLS 1.2 wäre genauso teuer wie HTTP/1.1 über TLS 1.2.

Konzept 2: DNS und TTL

DNS (Domain Name System) übersetzt Namen in IP-Adressen. Ein Eintrag (Record) hat einen Typ und eine TTL (time to live, Lebensdauer in Sekunden):

  • A-Record: Name zeigt auf eine IPv4-Adresse, z. B. shop.cdn.net zeigt auf 203.0.113.7.
  • CNAME-Record: Name ist ein Alias auf einen anderen Namen, z. B. www.shop.de zeigt auf shop.cdn.net. Die Auflösung folgt der CNAME-Kette, bis ein A-Record kommt.

Die TTL erlaubt es jedem Cache (Browser, Betriebssystem, Resolver deines Providers), die Antwort so lange zu behalten. Danach muss neu gefragt werden. Ein Beispiel für einen Namen mit TTL 60, jetzt ist eine Zahl in Sekunden:

Bei t=0 fragen wir (1 Anfrage), bei 10 und 59 trifft der Cache, bei 60 ist der Eintrag abgelaufen (60 < 60 ist falsch), also wird neu gefragt (2 Anfragen), bei 61 trifft der neue Eintrag wieder.

Die TTL ist ein Trade-off. Lange TTL bedeutet wenig DNS-Verkehr und schnelle Auflösung, aber wenn du die IP änderst (Umzug, Failover), sehen viele Clients noch lange die alte Adresse. Kurze TTL bedeutet schnelle Umstellung, aber mehr Anfragen und mehr Rundläufe.

Übung 2: DNS-Resolver mit Cache und CNAME-Kette

Baue die Klasse DnsResolver. Die zone ist ein Dict name -> (typ, wert, ttl) mit typ gleich "A" (wert ist die IP) oder "CNAME" (wert ist der Zielname).

  • aufloesen(name, jetzt) gibt die IP als String zurück. Bei einem CNAME folgst du der Kette bis zum A-Record.
  • Jeder Name der Kette hat seinen eigenen Cache-Eintrag mit eigener TTL. Ein Eintrag gilt, solange jetzt < ablauf ist (ablauf = Zeitpunkt der Abfrage + ttl).
  • self.anfragen zählt jede Frage an die Zone (Cache-Treffer zählen nicht).
  • Ein Name, der nicht in der Zone steht: gibt None zurück. Die Frage zählt trotzdem, und nichts wird gecacht.
  • Eine Kette mit mehr als 8 Namen (z. B. Schleife a zeigt auf b, b zeigt auf a) löst ValueError aus.

Beispiel: Mit www.shop.de (CNAME auf shop.cdn.net, TTL 300) und shop.cdn.net (A, TTL 60) kostet die erste Auflösung bei jetzt=0 zwei Anfragen, ein zweiter Aufruf bei jetzt=30 keine weitere.

Die Kette ist eine Schleife mit Obergrenze: Pro Name erst im Cache nachsehen (und prüfen, ob der Eintrag noch gilt), sonst die Zone fragen und mit Ablaufzeit speichern. Was entscheidet, ob du aufhörst oder zum nächsten Namen springst?

class DnsResolver:
    def __init__(self, zone):
        self.zone = zone
        self.cache = {}
        self.anfragen = 0

    def aufloesen(self, name, jetzt):
        for _ in range(8):
            eintrag = self.cache.get(name)
            if eintrag is None or jetzt >= eintrag[2]:
                self.anfragen += 1
                if name not in self.zone:
                    return None
                typ, wert, ttl = self.zone[name]
                eintrag = (typ, wert, jetzt + ttl)
                self.cache[name] = eintrag
            typ, wert, _ablauf = eintrag
            if typ == "A":
                return wert
            name = wert
        raise ValueError("CNAME-Schleife")

DnsResolver

Jeder Name hat seinen eigenen Ablauf: Der CNAME (TTL 300) überlebt den A-Record (TTL 60), dann kostet eine Auflösung nur noch eine Anfrage. Die Obergrenze von 8 Schritten verhindert die Endlosschleife bei a auf b auf a.

Konzept 3: TCP, Head-of-Line Blocking und HTTP/1.1, 2, 3

TCP im Detail

Der TCP-Handshake (three-way handshake) besteht aus SYN, SYN ACK und ACK. Erst danach fließen Daten. TCP garantiert, dass die Anwendung die Bytes in der richtigen Reihenfolge und vollständig bekommt. Geht ein Paket verloren, wartet TCP auf die Wiederholung (retransmit), und alles, was nach dem verlorenen Paket angekommen ist, bleibt zurückgehalten, bis die Lücke gefüllt ist.

Außerdem tastet sich TCP an die Leitungskapazität heran (Congestion Control, Überlastkontrolle). Am Anfang darf nur eine kleine Zahl von Segmenten ohne Bestätigung unterwegs sein (das Congestion Window, cwnd). Im Slow Start verdoppelt sich das Fenster pro Rundlauf, bis Verluste auftreten. Beispiel mit Startfenster 10 Segmenten (Annahme, bitte prüfen, ob das für dein System gilt), wie viele Rundläufe brauchen 100 Segmente?

Vier Rundläufe. Eine neue Verbindung ist also anfangs langsam, auch wenn die Leitung schnell wäre. Das ist ein weiterer Grund, Verbindungen wiederzuverwenden.

HTTP/1.1, HTTP/2 und HTTP/3

HTTP/1.1 HTTP/2 HTTP/3
Transport TCP TCP QUIC über UDP
Anfragen pro Verbindung eine nach der anderen viele gleichzeitig (Multiplexing, Streams) viele gleichzeitig (Streams)
Browser-Workaround mehrere Verbindungen pro Server meist eine Verbindung eine Verbindung
Verlust eines Pakets blockiert diese Verbindung blockiert alle Streams (TCP) blockiert nur den betroffenen Stream

Bei HTTP/1.1 blockiert eine langsame Antwort die Verbindung (Head-of-Line Blocking auf HTTP-Ebene), darum öffnen Browser mehrere Verbindungen. HTTP/2 löst das mit Multiplexing: Viele Streams teilen sich eine TCP-Verbindung, die Frames werden gemischt gesendet. Aber TCP sieht nur einen Bytestrom. Geht ein Paket verloren, hält TCP alle Daten dahinter zurück, auch die von Streams, die gar nicht betroffen sind. Das ist Head-of-Line Blocking auf TCP-Ebene. HTTP/3 läuft über QUIC (UDP). QUIC kennt Streams selbst und stellt pro Stream in Reihenfolge zu. Ein Verlust verzögert nur den Stream, zu dem das Paket gehörte.

Das Modell dazu, so klein, dass du es nachrechnen kannst. Die Pakete werden in der Reihenfolge der Liste gesendet, eines pro Tick. Ein Paket braucht 2 Ticks bis zum Empfänger. Ein verlorenes Paket kommt erst um eine RTT (4 Ticks) später an, weil der Sender den Verlust bemerken und wiederholen muss. TCP liefert an die Anwendung nur in Sendereihenfolge, QUIC nur innerhalb des Streams. Ein Stream ist fertig, wenn sein letztes Paket zugestellt ist.

Das verlorene Paket 4 gehört zu Stream B. Bei TCP verzögert es alle drei Streams auf Tick 10. Bei QUIC bleibt C bei 7 und A bei 8, nur B wird später. Auf einer Leitung ohne Verluste sind beide gleich schnell. Der Unterschied entsteht erst bei Paketverlust, also zum Beispiel in einem schlechten Mobilfunknetz, wo Pakete häufiger verloren gehen. Das ist der Kern der Antwort auf den zweiten Prüfstein. Dazu kommt, dass QUIC Transport und Verschlüsselung in einem Handshake erledigt (ein Rundlauf weniger, siehe Übung 1) und eine Verbindung auch dann behalten kann, wenn sich die IP-Adresse des Handys ändert (Wechsel von WLAN auf Mobilfunk, Connection Migration).

Übung 3: Wer wartet auf wen?

Gleiches Modell, anderes Szenario: Die Sendereihenfolge ist A B A C B B A C (Pakete 0 bis 7), und die Pakete 2 und 6 gehen verloren. Trage die Fertigstellungszeiten (Tick) der drei Streams ein, in dieser Reihenfolge als Tupel: (A bei TCP, B bei TCP, C bei TCP, A bei QUIC, B bei QUIC, C bei QUIC). Die Funktion fertig aus dem Text gibt es hier nicht, rechne von Hand oder baue sie dir selbst in die Zelle.

Schreibe zuerst für jedes Paket die Ankunftszeit auf (Index plus Laufzeit, bei Verlust plus RTT). Für TCP: Ab wann ist ein Paket zustellbar, wenn alles davor da sein muss? Für QUIC: Was muss nur innerhalb des Streams da sein?

antwort = (12, 8, 12, 12, 7, 9)
antwort

Ankunftszeiten der Pakete 0 bis 7: 2, 3, 8 (verloren), 5, 6, 7, 12 (verloren), 9. Bei TCP ist ein Paket erst zustellbar, wenn alle früheren da sind: Pakete 2 bis 5 warten auf Tick 8, ab Paket 6 auf Tick 12, Paket 7 ebenfalls auf 12. A (Pakete 0, 2, 6) wird 12, B (1, 4, 5) wird 8, C (3, 7) wird 12. Bei QUIC zählt nur der eigene Stream: A hängt an den verlorenen Paketen 2 und 6 und wird 12. B (Pakete 1, 4, 5 mit Ankunft 3, 6, 7) wartet nur auf eigene Pakete und ist bei 7 fertig. C (Pakete 3 und 7, Ankunft 5 und 9) ist bei 9 fertig. Der zweite Verlust (Paket 6) hängt bei TCP auch den Stream C an, der selbst nichts verloren hat.

Falle

  1. “Die Seite ist langsam” auf den Server schieben. Im Beispiel oben gehen 4 von 5 Rundläufen für DNS und Verbindungsaufbau drauf, bevor der Server überhaupt antwortet.
  2. DNS-Adresse ändern und sofort alles erwarten. Viele Nutzer sehen noch Stunden die alte Adresse, solange die TTL läuft.
  3. Für jede Anfrage eine neue Verbindung aufbauen. Handshakes kosten Rundläufe, und die neue Verbindung startet mit kleinem Congestion Window im Slow Start. Verbindungen wiederverwenden (Keep-Alive, HTTP/2 mit Multiplexing) spart beides.
  4. Nur im Büro testen. Eine Seite, die lokal schnell ist, wird im Mobilfunknetz zäh, weil Verluste und hohe RTT erst dort auftreten.

Merksatz

Zwischen Enter und Seite liegen DNS, TCP, TLS, HTTP und Rendern, und weil jeder Schritt Rundläufe kostet, gewinnt HTTP/3 vor allem bei hoher RTT und Paketverlust: QUIC blockiert nur den betroffenen Stream, wo TCP bei HTTP/2 alle aufhält.

Prüfstein

  1. Was passiert technisch zwischen Enter in der Adresszeile und der gerenderten Seite? Nenne die Schritte in der Reihenfolge, welche Caches den Weg abkürzen können und was jeder Schritt kostet.
  2. Warum kann eine Seite über HTTP/3 in einem schlechten Mobilfunknetz schneller laden als über HTTP/2?

Weiter mit Teil 2: TLS und Zertifikate, HTTP-Caching, Proxy und Load Balancer.


Quelle: quellen/konzeptuebersicht-software-grundlagen.md, Abschnitt “4. Betriebssystem und Netzwerk” (IP, Ports, DNS, TCP vs. UDP, Schichtenmodell, HTTP, Prüfstein zu Enter und gerenderter Seite); quellen/konzeptuebersicht-software-fortgeschritten.docx, Schicht 4 “Betriebssystem, Netzwerk und Performance” (TCP im Detail mit Handshake, Congestion Control, Slow Start und Head-of-Line Blocking; moderne Protokolle HTTP/2, HTTP/3 und QUIC; DNS-Auflösung und TTLs).

Über die Quelle hinaus (allgemeines Fachwissen): die Rundlaufzählung (DNS 1, TCP 1, TLS 1.2 mit 2, TLS 1.3 mit 1, QUIC mit 1 gemeinsamem Handshake, Anfrage 1), die Annahme eines Startfensters von 10 Segmenten (bitte prüfen), das Modell für Head-of-Line Blocking (Laufzeit, Wiederholung nach einer RTT, alles vereinfacht und ohne Verlust bei der Wiederholung), die Erklärung von CNAME-Ketten und der TTL als Trade-off, Connection Migration bei QUIC. Alle Zahlen im Text stammen aus dem Ausführen des Codes dieser Lektion, nicht aus Messungen realer Netze.