9.10. TCP – ein zuverlässiger Strom von Bytes

TCP, das Transmission Control Protocol, ist der andere Dienst der Transportschicht, der auf IP aufsetzt. Während sich UDP am besten mit „sende ein Paket und hoffe“ beschreiben lässt, bedeutet TCP „öffne eine Verbindung zwischen zwei Endpunkten und behandle sie als eine bidirektionale Byte-Leitung, bei der die andere Seite die Daten definitiv erhält, in der richtigen Reihenfolge und genau einmal“. Der Großteil des Internetverkehrs nutzt es, und auch das meiste, was die Kamera im Netzwerk tut, läuft darüber.

9.10.1. Was TCP zu IP hinzufügt

TCP leistet deutlich mehr als UDP. Es fügt hinzu:

  • Eine Verbindung. Bevor Daten fließen, tauschen die beiden Endpunkte einen kurzen Handshake aus, um sich darauf zu einigen, dass sie miteinander kommunizieren. Die Verbindung hat einen Zustand – „offen“, „halb geschlossen“, „geschlossen“ – den beide Seiten verfolgen.

  • Zuverlässige Zustellung. Jedes Byte, das der Sender einspeist, wird vom Empfänger bestätigt. Alles, was nicht innerhalb eines Zeitlimits bestätigt wird, wird erneut gesendet. Die Anwendung sieht niemals ein verlorenes Byte; sie sieht nur eine Verzögerung, während das Protokoll erneut sendet.

  • Zustellung in der richtigen Reihenfolge. Bytes treffen in derselben Reihenfolge ein, in der sie gesendet wurden. Selbst wenn Pakete am Empfänger in falscher Reihenfolge ankommen, ordnet TCP sie neu, bevor die Anwendung sie liest.

  • Flusskontrolle. Wenn der Empfänger langsam ist, wird dem Sender mitgeteilt, langsamer zu werden; die Verbindung passt sich an die Rate des schwächeren Endes an.

  • Überlastkontrolle. Wenn das Netzwerk dazwischen anfängt, Pakete zu verwerfen, hält sich der Sender zurück, bis sich die Lage erholt. Das verhindert, dass eine einzelne Verbindung eine ausgelastete Leitung lahmlegt.

All das geschieht automatisch. Die Python-API, die die Anwendung verwendet, ist schlicht send(bytes) und recv(n); TCP erledigt alles andere darunter.

9.10.2. Der Handshake

Eine TCP-Verbindung wird mit einem dreistufigen Austausch geöffnet, bevor überhaupt Daten durchgelassen werden:

Ein Diagramm mit zwei Spalten, beschriftet mit "client" und "server". Ein Pfeil von client zu server mit der Beschriftung "SYN", dann ein Pfeil von server zu client mit der Beschriftung "SYN-ACK", dann ein Pfeil von client zu server mit der Beschriftung "ACK". Darunter ein dicker Pfeil mit der Beschriftung "data flowing both ways".

Der Drei-Wege-Handshake. Sobald beide Seiten bestätigt haben, ist die Verbindung offen und Daten können fließen.

Der Client sendet ein SYN-Paket (synchronise), das anfragt, eine Verbindung zu öffnen. Der Server antwortet mit SYN-ACK (synchronise + acknowledge) und nimmt an. Der Client sendet ein abschließendes ACK zur Bestätigung. Nach diesem Hin und Her sind sich beide Seiten einig, dass die Verbindung offen ist, und haben ihre Zähler zur Verfolgung der gesendeten und empfangenen Bytes synchronisiert.

Der Handshake kostet eine Round-Trip-Time an Latenz, bevor das erste nützliche Byte durchkommt. Für lokale Netzwerke ist das eine Millisekunde; für interkontinentale Verbindungen sind das ungefähr 100 ms. Das ist der Hauptkostenfaktor von TCP und der Grund, warum kurze, einmalige Nachrichten manchmal besser mit UDP bedient sind.

9.10.2.1. Der schließende Handshake

TCP-Verbindungen werden ebenfalls mit einem Austausch geschlossen (ein FIN von jeder Seite). Jedes Ende kann seine Hälfte der Verbindung unabhängig schließen; ein halb geschlossener Zustand, bei dem eine Seite mit dem Senden fertig ist, die andere aber noch spricht, ist zulässig, wenn auch ungewöhnlich. Die Anwendung ruft normalerweise einfach close() auf und überlässt dem Protokoll die Abwicklung der Abschlusssequenz.

9.10.3. Was TCP nicht garantiert

Ein paar Dinge, von denen manchmal angenommen wird, dass TCP sie tut, was aber nicht der Fall ist:

  • Nachrichtengrenzen. Die Anwendung sendet einen Strom von Bytes, keinen Strom von Nachrichten. Zwei send(b"hello")-Aufrufe könnten als ein einzelnes recv() mit b"hellohello" ankommen oder als zwei recv()s unterschiedlicher Größe. Wenn die Anwendung ein Nachrichten-Framing möchte, muss sie das Framing selbst hinzufügen (einen Zeilenumbruch, ein Längenpräfix, was auch immer). Das Senden von JSON-Dokumenten über TCP erfordert beispielsweise, dass jedes Dokument durch einen Zeilenumbruch oder einen anderen Marker getrennt ist.

  • Verschlüsselung. TCP überträgt die Bytes, die ihm die Anwendung gegeben hat, im Klartext, den ganzen Weg über. Wenn die Inhalte vertraulich sein müssen, muss die Anwendung die Verbindung in TLS einhüllen (siehe Verschlüsselte Sockets und TLS).

  • Authentifizierung. TCP stellt sicher, dass die Bytes unversehrt angekommen sind. Es sagt nichts darüber aus, wer sie gesendet hat. Authentifizierung ist ebenfalls ein Anliegen einer höheren Schicht.

  • Lebendigkeit bei einer ruhigen Verbindung. Wenn keine Seite über längere Zeit Daten sendet, ist die Verbindung technisch zwar noch offen, kann aber nicht erkennen, dass das andere Ende abgestürzt oder verschwunden ist. Keepalive-Probes können am Socket aktiviert werden, um dies zu beheben, wenn es darauf ankommt.

9.10.4. Wann TCP zu verwenden ist

TCP ist die richtige Antwort für nahezu jede Konversation, die in das Schema „ein Client öffnet eine Verbindung zu einem Server, sie tauschen einige Bytes aus, die Verbindung schließt sich, wenn sie fertig sind“ passt. HTTP- und HTTPS-Anfragen, SSH-Sitzungen, Datenbankabfragen, Dateiübertragungen, Bild-Uploads – alles über TCP.

Greifen Sie nur dann zu UDP, wenn die Konversation nicht in dieses Schema passt: unabhängige, in sich geschlossene Nachrichten, bei denen Verlust akzeptabel ist, Multicast-Verkehr, Zeitsynchronisierung, Namensauflösungen oder extrem latenzempfindliche Fälle, in denen die Kosten des Handshakes untragbar sind.

Mit Ports, UDP und TCP auf dem Tisch ist die Geschichte der Transportschicht abgeschlossen. Die Python-API, die beide bereitstellt, befindet sich auf der nächsten Seite: Socket-Objekte.