In einem Client-Server-Netzwerkmodell senden die Clients nur Befehle an den Server (dh Koordinaten eines Klicks, einer Feuerwaffe usw.), und der Server führt diese Befehle dann aus, um einen Spielstatus zu erzeugen.
Was aber, wenn der Client einen Befehl wie "Feuerwaffe" in einem Paket sendet, das verworfen wird? Hat der Benutzer die Erfahrung gemacht, dass er auf seinen Feuerknopf getippt hat, ohne dass etwas passiert, oder gibt es eine intelligente Möglichkeit, um sicherzustellen, dass solche Befehle vom Server unabhängig vom Paketverlust empfangen werden?
Das heißt, der alte Befehl "Feuerwaffe" wird mehrmals gesendet, um sicherzustellen, dass er zumindest irgendwann ausgelöst wird (Kennzeichnung des Befehls, damit der Server versteht, dass es sich um denselben Befehl handelt, der mehrmals gesendet wurde, und nicht um einen Befehl, der immer wieder ausgelöst wird). .
Ich habe mir die Dokumentation zu Quake3-, Unreal- und Source-Netzwerkmodellen angesehen, kann jedoch zu diesem speziellen Problem nichts finden. Ich glaube jedoch, dass sie es gelöst haben, weil ich mein AWP nie abgefeuert habe, ohne dass es tatsächlich geschossen hat. Wenn es eine Verzögerung gab, hat es nur eine kurze Zeit gedauert, bis es geschossen hat, aber ich denke, es hat nie den Befehl verloren.
BEARBEITEN: Ich habe hier eine Ressource gefunden, die einen Weg erklärt, wie Sie damit umgehen können: http://gafferongames.com/networking-for-game-programmers/reliability-and-flow-control/
Sie lösen es, indem sie eine Art loses ACK über UDP hinzufügen. Ich bin jedoch der Meinung, dass es einen klügeren Weg geben sollte, dies zu tun. Diese Art der Handhabung ist für den Server in Ordnung, denke ich, der hauptsächlich sicherstellen muss, dass er eine Live-Verbindung zum hat, und um Dinge wie Chat-Nachrichten zu behandeln (die nicht wirklich zeitkritisch sind, aber nicht verloren gehen dürfen), aber Ich glaube, dass ein ACK für den Client zu viel Verzögerung verursacht, wenn ein Paket verloren geht (zumindest eine volle Latenzzeit).
quelle
Antworten:
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 :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.
quelle
Um Paketverluste zu behandeln, müssen Sie zunächst entscheiden, welches Netzwerkprotokoll Sie verwenden möchten: TCP oder UDP.
TCP: ist zuverlässig, sendet aber nur langsam einfache Nachrichten (hat eine höhere Latenz)
UDP: hat eine geringe Latenz, aber um Ihr Ziel zu erreichen, dass der Benutzer einige Paketverluste nicht bemerkt, müssen Sie eine zusätzliche Schicht entwickeln, die Ihre Kommunikation verlangsamt.
Diese zusätzliche Schicht entspricht dem Unterschied zwischen UDP x TCP.
Sie müssen die Details des TCP-Protokolls studieren und viele Funktionen implementieren. Sie müssen mindestens ACK-Pakete und Paketbestellungen entwickeln. Wenn Ihre Ebene nicht gut entwickelt ist, kann sie sogar langsamer als TCP sein.
Vielleicht können Sie beide Protokolle im selben Spiel mischen.
UDP sollte nur verwendet werden, wenn Sie Daten liefern, die problemlos verloren gehen können. Wenn Sie beispielsweise das Paket "Feind 1 befindet sich auf Feld A" verpassen, ist es kein großes Problem, wenn Sie die Position des Feindes von Zeit zu Zeit wiederholen. Wenn Sie das zweite Paket mit "Feind 1 ist auf Feld A" oder "Feind 1 ist auf Feld B" erhalten, spielt es keine Rolle, wo es vorher war. Sie platzieren den Feind einfach auf dem zuletzt empfangenen Feld.
Wenn Sie nicht möchten, dass Ihr Player irgendwelche Schüsse verliert, verwenden Sie TCP (oder eine zuverlässige modifizierte UDP-Nachricht).
quelle
Sofern Sie nicht an den Details der Implementierung Ihrer eigenen Zuverlässigkeitsschicht mit UDP interessiert sind, würde ich die Verwendung von RakNet empfehlen , insbesondere da es Open Source ist. Dies ist der richtige Weg, wenn Sie sich darauf konzentrieren, ein Spiel und kein Netzwerkprotokoll zu erstellen.
Mit RakNet können Sie Pakete mit bestimmten Zuverlässigkeitsflags wie RELIABLE_ORDERED senden. Dies stellt sicher, dass jedes Paket ankommt (es wird erneut gesendet, wenn es nicht bestätigt wird) und verwendet ein zugrunde liegendes Bestellsystem, so dass die Pakete auf der Empfangsseite in der richtigen Reihenfolge verarbeitet werden. Dies wäre sehr wichtig, wenn Sie Eingaben für so etwas wie ein Kampfspiel senden, bei dem die Reihenfolge für die Berechnung von Zügen und dergleichen entscheidend ist. Dies alles ist mit einer Bibliothek wie RakNet sehr einfach zu erreichen.
Dies erhöht jedoch die Bandbreite, aber Sie können entsprechend auswählen, welche Pakete zuverlässig und geordnet sein müssen und welche Sie sich leisten können, zu verlieren.
Bearbeiten: Beachten Sie auch die folgenden Artikel, um einen schnellen Multiplayer- Modus zu implementieren. Mit einer leistungsstarken Bibliothek wie RakNet ist das ganz einfach.
quelle