Protokollentwicklung: TCP vs. UDP

Published 11 November, 2012

Ein neues Netzwerkprotokoll muss her.

Vor dieser Aufgabe steht man öfters, wenn man eine Client/Server-Software schreibt und bestehende Lösungen nicht den Anforderungen entsprechen.

Die Auswahl, auf welchem Protokoll aus der Transportschicht (OSI-Modell) das neue Protokoll basiert, gehört zu einer der ersten Dinge bei der Entwicklung.

In den allermeisten (!) Fällen kommt hier TCP und UDP in Frage. In diesem Artikel beziehe ich mich ausschließlich auf POSIX-Sockets, da man fast ausschließlich diese benutzt (WinSock sind fast identisch zu den POSIX-Sockets).

TCP

TCP ist ein Protokoll, welches verwendet wird, wenn die Übertragung sicher sein muss. Mit sicher ist hierbei nicht etwa eine Verschlüsselung gemeint, sondern lediglich, dass die Daten die man sendet auch auf jeden Fall so ankommen, wie sie gesendet wurden. Das heißt: Komplett, in der richtigen Reihenfolge und nicht doppelt. TCP zeigt sich in Richtung des Programmierers auch nicht paketorientiert, sondern als Stream, ähnlich wie beim Zugriff auf eine lokale Datei. Auf der OSI-Schicht unterhalb von TCP/UDP wird natürlich nur von Paketen gesprochen, aber dies wird von TCP abstrahiert.

Um eine verlustfreie Kommunikation zu ermöglichen muss jedoch auch eine Verbindung aufgebaut werden. Hierfür muss der Server auf einem TCP-Port "horchen". Wenn ein Client versucht sich zu dem Server mit dem angegebenen Port zu verbinden, gibt das Betriebssystem des Servers nach erfolgreichem Verbindungsaufbau die Verbindung an die Serversoftware weiter. Der komplette Verbindungsaufbau (Three-Way-Handshake) ist in der Regel transparent für den Programmierer der Anwendungssoftware und wird vom Betriebssystem übernommen.

UDP

UDP hingegen ist ein verbindungsloses und paketorientiertes Protokoll. Das bedeutet, dass das Anwendungsprogramm nicht in einen Stream schreibt, sondern einzelne Pakete verschickt. Das sieht zwar beim Sender sehr ähnlich aus, macht aber einen großen Unterschied beim Empfänger. Das bedeutet gleichzeitig auch, dass Pakete durchaus in einer falschen Reihenfolge ankommen können, gar nicht ankommen oder gar doppelt ankommen. Bei TCP und UDP werden Pakete verworfen, wenn sie beschädigt sind. Dies wird über eine Prüfsumme sichergestellt. Wenn bei der Übertragung kleine Fehler toleriert werden sollen (beispielsweise bei Audioübertragungen) dann gibt es noch die Möglichkeit UDP Lite zu verwenden.

Ein weiterer Vorteil von UDP ist, dass keine Verbindung aufgebaut werden muss und sich somit auch für "kurze" Kommunikationen gut eignet. Denn wer will schließlich schon 4 Pakete Schicken, wenn lediglich in eine Richtung ein einziges Paket reichen würde?

Soviel dazu, was UDP und TCP ist. Aber wann genau verwendet man nun was?

Ganz einfache Regel: Wenn die Daten 1:1 ankommen müssen, aber kleine Verzögerungen kein Problem sind: TCP. Wenn die Daten schnell übertragen werden müssen, jedoch Informationsverluste in Kauf genommen werden können: UDP.

Beispiele

HTTP setzt auf TCP. Wenn Pakete in falscher Reihenfolge beim Empfänger ankommen würden, wären runtergeladene Dateien beschädigt oder aber ein HTML-Dokument nichtmehr brauchbar. Bei FTP ist dies der gleiche Fall. FTP setzt jedoch sogar auf mehrere TCP-Verbindungen gleichzeitig. Eine Verbindung für die allgemeine Steuerung und der Übertragung von beispielsweise Dateilisten (üblicherweise auf Port 21) und für eine Dateiübertragung wird nochmal eine weitere TCP-Verbindung aufgebaut.

Das DNS-Protokoll basiert hingegen (üblicherweise) auf UDP, da die Kommunikation zwischen Client und DNS-Server eher sehr dünn ausfällt: Der Client sendet einen Request und der Server antwortet darauf. Da man bei UDP selber darauf achten muss, dass die Pakete nicht verloren gehen, versucht der DNS-Client nach einer gewissen Zeit nochmal die Anfrage zu schicken, sofern es keine Antwort nach eine gewissen Zeit gab. Die Anzahl der Versuche und der Timeout lässt sich in der Regel konfigurieren. Die DNS-Spezifikation sieht jedoch auch einen Fallback vor, falls die Pakete zu groß werden: Dann kann der Client sich auch direkt per TCP zum DNS-Server verbinden.

Nagle Algorithmus

Ein weiteres interessantes Beispiel ist SSH. SSH basiert auch auf TCP, jedoch ist es notwendig, dass die Übertragung schnell ist. Das liegt daran, dass die Zeichen, die man in die Shell eingibt nicht sofort vom Client dargestellt werden, sondern die Tasteneingabe an den SSH-Daemon geschickt werden, welcher die entsprechenden Zeichen wieder zurück zum Client schickt, damit er sie anzeigt. Das ist zwar durchaus etwas langsam, ermöglicht aber so tolle Funktionen, wie beispielsweise die Autovervollständigung beim Drücken der TAB-Taste.

Der Nagle Algorithmus einer TCP-Implementierung sorgt jedoch dafür, dass ein Paket erst gesendet wird, wenn es sich lohnt. Angenommen, man schreibt in einen TCP-Stream das Byte 0x05 und wenige Millisekunden später nochmal 0x06, dann werden beide Bytes höchstwahrscheinlich in demselben Paket übertragen. Das hat die Folge, dass das erste Paket mit einer deutlichen Verzögerung ankommt, dafür aber Bandbreite gespart wurde, weil es nur einen TCP-, einen IP und einen Ethernet/Ring/WLAN-Header geben musste.

POSIX-konforme TCP-Implementierungen erlauben es aber den Nagle-Algorithmus trotzdem abzuschalten. Dies geht wie folgt:

int flag = 1;
setsockopt(socket, IPPROTO_TCP, TCP_NODELAY, (char *) &flag, sizeof(int));

Das ist auch bei SSH der Fall, wodurch die Zeichen mit nur wenigen Millisekunden Verzögerung dargestellt werden können (natürlich ist das Abhängig von der Übertragungsrate- und Qualität, dem Server und der Entfernung zwischen Server und Client).

Wofür nun was?

Oft wird gesagt, dass (schnelle) Multiplayerspiele stets über UDP laufen, weil es dort auf Geschwindigkeit ankommt (besonders bei Ego-Shootern kann es sehr nervig sein, wenn der Stand der anderen Spieler zu stark von dem eigenen abweicht). Ein Verlust von einem Paket, welches die Richtungsänderung eines Spielers beinhaltet, ist zwar sicherlich nicht schön, kann aber toleriert werden, weil wenige Zehntelsekunden die nächste Richtungsänderung inkl. der Positionsdaten des Spielers kommt. Hier sieht man den Spieler kurz in der virtuellen Welt "springen", bzw.er macht vermutlich eine sehr unnatürliche Bewegung um doch noch an die korrekte Position gesetzt zu werden. Was aber passiert mit Chat-Nachrichten? Hier kann es sehr nervig sein, wenn nur 80% aller Nachrichten ankommen, oder etwa in der falschen Reihenfolge.

Hierfür müsste man nun eine eigene Schicht schreiben, welche doch noch eine eigene Erkennung von Paketverlusten (durch das Bestätigen der Pakete), falschen Reihenfolgen (durch das Durchnummerieren der Pakete), usw. mitbringt. Hier ist es sinnvoll zwischen "wichtig" und "nicht wichtig" zu unterscheiden, damit es trotzdem noch möglich ist, über das eigene Protokoll schnelle und unsichere Nachrichten zu schicken, welche durchaus verloren gehen dürfen.

In der Praxis wählt man dann jedoch oft einfach TCP als Übertragungsprotokoll mit deaktivierten Nagle Algorithmus. Beispielsweise verwendet World Of Warcraft ausschließlich TCP für die Übertragung und läuft dennoch flüssig.

Ich hoffe, dass ich grob erklären konnte, wann es sinnvoll ist TCP und wann es sinnvoll ist UDP zu verwenden und auch die allgemeine Angst vor dem "langsamen, nicht echtzeitfähigen TCP" nehmen konnte. :-)