Es frustriert mich immer wieder, wie viele Leute die Unterschiede zwischen TCP und UDP zu stark vereinfachen, da "TCP zuverlässig, aber langsam ist, während UDP schnell, aber unzuverlässig ist".
Darum geht es bei TCP und UDP nicht. TCP und UDP sind zwei verschiedene Abstraktionen über IP und werden für verschiedene Zwecke verwendet. Beide sehr gut auf ihrem Gebiet.
TCP ist ein Stream- orientiertes Protokoll. Sie verwenden TCP, wenn Sie viele sequentielle Daten von einem Punkt zu einem anderen übertragen möchten, um den Durchsatz zu maximieren (dh die Zeit zu minimieren, die Sie zum Senden aller Daten benötigen). Das automatische erneute Senden verlorener Datagramme und das Neuordnen sind eine der Funktionen von TCP, aber TCP bietet auch Stateful-Verbindungen, Flusskontrolle und Überlastungskontrolle. Diese Funktionen machen TCP zu einem wirklich guten Werkzeug für die Übertragung großer Datenmengen von einem Punkt zum anderen. Protokolle wie HTTP und FTP, bei denen Sie so schnell wie möglich große Datenmengen senden möchten, verwenden TCP.
UDP hingegen ist ein nachrichtenorientiertes Protokoll. Sie verwenden UDP, wenn Sie Nachrichten von einem Punkt zu einem oder mehreren Punkten senden möchten, um die Latenz zu minimieren ( dh die Zeit, die zwischen dem Senden einer Nachricht bis zum Empfang benötigt wird). Um die Latenz zu minimieren, implementiert UDP keine erneuten Übertragungen oder Neuordnungen von Datagrammen. Wenn Sie jedoch möchten, dass Ihre nachrichtenbasierte Anwendung Neuübertragungen und / oder Neuordnungen unterstützt, können Sie dies selbst implementieren, und das ist eigentlich ganz einfach! Darüber hinaus gibt es eine Reihe lustiger Dinge, die Sie in UDP tun können, die Sie mit TCP einfach nicht tun können, wie Multicast- und UDP-Locher.
TCP und UDP sind beide großartige Technologien, die für verschiedene Zwecke gedacht sind. Sie möchten sich also fragen, ob die Kommunikation in Ihrem Programm besser als Stream von Bytes oder als eine Reihe von Nachrichten dargestellt wird, und eine gute Faustregel ist:
Wenn es in Ihrem Programm um das Senden von Nachrichten geht, verwenden Sie UDP.
Wenn Ihr Programm einen Bytestrom senden soll, verwenden Sie TCP.
So sollte die Entscheidung zwischen TCP und UDP aussehen, nicht über Zuverlässigkeit, was nur einer der vielen Unterschiede zwischen ihnen ist und meiner Meinung nach der am wenigsten wichtige von allen.
So wie es möglich ist, eine Stream-Abstraktion über UDP durchzuführen, ist es möglich (und leider sehr häufig), eine Nachrichtenabstraktion über TCP durchzuführen.
Wenn Sie sich für eine Nachrichtenabstraktion über TCP entscheiden, denken Sie daran, dass Sie TCP so verwenden müssen, wie es nicht beabsichtigt war, was bedeutet, dass Sie auf einige Schwierigkeiten stoßen werden. Bestimmtes:
Nachrichtengrenzen: TCP behandelt Daten als einen sehr langen Bytestrom und hat daher kein Konzept für Pakete oder Nachrichten. Wenn Sie Nachrichten über TCP senden möchten, müssen Sie eine Art Nachrichtenkopf erstellen, der die Länge jeder Nachricht enthält.
Pufferung beim Senden: TCP erwartet, dass Sie Daten so schnell wie möglich produzieren können. TCP-Datagramme können sehr groß sein. Wenn die Netzwerkbedingungen gut sind, puffert ein TCP-Stapel Ihre Bytes, bis ein sehr großes Datagramm gesendet werden kann, wodurch der Durchsatz maximiert wird. Dies ist eine gute Sache für Streams, aber nicht so sehr für Nachrichten. Einmal zu oft wird der Puffer bei jedem Sendevorgang geleert oder es werden Optionen wie und verwendet TCP_NODELAY
, und selbst dann haben Sie keine Garantie. Um den verstorbenen Richard Stevens zu zitieren :
2.11. Wie kann ich einen Socket zwingen, die Daten in seinem Puffer zu senden?
Du kannst es nicht erzwingen. Zeitraum. TCP entscheidet selbst, wann es Daten senden kann. Normalerweise sendet TCP beim Aufrufen von write () an einem TCP-Socket zwar ein Segment, es gibt jedoch keine Garantie und keine Möglichkeit, dies zu erzwingen. Es gibt viele Gründe, warum TCP kein Segment sendet: Ein geschlossenes Fenster und der Nagle-Algorithmus sind zwei Dinge, die sofort in den Sinn kommen.
Pufferung beim Empfang: Selbst wenn Sie es schaffen, den Puffer bei jedem Senden zu leeren, gibt es keine Garantie dafür, dass Sie für jede von Ihnen gesendete Spülung einen Empfang erhalten. Es ist durchaus möglich, dass Sie mehrere Nachrichten in einem einzigen recv
Anruf erhalten, da diese am Empfangsende gepuffert wurden (insbesondere in Netzwerken mit hohen Paketverlustraten). Sie müssen dann eine Nachrichtenempfangswarteschlange implementieren, was ziemlich dumm ist, wenn Sie TCP verwenden.
Heartbeats und Erkennung von Verbindungsabbrüchen: TCP verfügt über einen eigenen Mechanismus zum Erkennen von Verbindungsabbrüchen, die jedoch für Anwendungen zur Nachrichtenübermittlung normalerweise zu langsam sind (das nicht konfigurierbare Keep-Alive-Datagramm wird etwa alle zwei Stunden gesendet). Einmal zu oft implementieren die Leute ihre eigenen Hearbeat-Protokolle im selben TCP-Stream und fragen dann "wie man das Schließen von TIME_WAIT erzwingt", "wie man eine geschlossene Verbindung erkennt" oder ähnliches.
Keines dieser Probleme tritt auf, wenn Sie Ihr nachrichtenorientiertes Programm mit UDP erstellen:
Nachrichtengrenzen: Jede Nachricht ist ein separates UDP-Datagramm.
Pufferung beim Senden: Existiert nicht. Ein sendto
Anruf sendet sofort ein Datagramm.
Pufferung beim Empfang: Existiert nicht. Sie erhalten nur ein Datagramm pro recvfrom
Anruf.
Erkennung von Verbindungsabbrüchen: UDP ist nicht verbindungsorientiert. Sie können ein einfaches Timeout mit Ihren eigenen Parametern implementieren, um zu berücksichtigen, dass ein Client nicht mehr vorhanden ist.
Wenn Sie TCP für die Nachrichtenübermittlung verwenden möchten, weil Sie der Meinung sind, dass dies einfach ist, tun Sie dies nicht.
Für das gefürchtete Zuverlässigkeitsproblem können Sie nun eine einfache Zuverlässigkeit implementieren, indem Sie zwei Felder in jede Nachricht einfügen: Das erste ist eine permanent ansteigende Nachrichten-ID und das zweite ist die größte Nachrichten-ID, die Sie von einem bestimmten Client erhalten haben. Wenn sich Ihre interne Anzahl zu stark von der vom Kunden angegebenen Anzahl unterscheidet, senden Sie sie erneut ab der ersten Nachricht, die der Kunde nicht erhalten hat. Mit Wrapover können Sie dies mit nur zwei zusätzlichen Bytes pro Nachricht tun.
Das Beste an Ihrer eigenen Zuverlässigkeitsschicht ist, dass Sie sie auf Ihre Bedürfnisse abstimmen können: Einige Nachrichtentypen sind möglicherweise zuverlässig, andere ohne. Sie können verschiedene Kanäle haben, jeder mit seinen eigenen Sequenznummern, Sie können diese Nummern verwenden, um Verzögerungen vorherzusagen, und viele andere großartige Dinge.
Lauf nicht vor UDP weg, weil andere dir gesagt haben, "es ist schwer". Sie haben wahrscheinlich nie (ernsthaft) UDP verwendet und haben wahrscheinlich nicht einmal (ernsthaft) TCP verwendet. Tatsächlich ist UDP viel einfacher und verständlicher als TCP.
Lesen Sie Stevens 'Unix-Netzwerkprogrammierung. Und dann noch einmal lesen.