Für ein Spiel, das ich mache, sende ich eine ganze Menge Daten von und zu einem Server.
Ich sende momentan Standortdaten wie diese:
sendToClient((("UID:" + cl.uid +";x:" + cl.x)));
sendToClient((("UID:" + cl.uid +";y:" + cl.y)));
sendToClient((("UID:" + cl.uid +";z:" + cl.z)));
Offensichtlich werden die entsprechenden X-, Y- und Z-Werte gesendet.
Wäre es effizienter, solche Daten zu senden?
sendToClient((("UID:" + cl.uid +"|" + cl.x + "|" + cl.y + "|" + cl.z)));
networking
joehot200
quelle
quelle
Antworten:
Ein TCP-Segment hat ziemlich viel Overhead. Wenn Sie eine 10-Byte-Nachricht mit einem TCP-Paket senden, senden Sie tatsächlich Folgendes:
Dies führt zu 42 Byte Datenverkehr für den Transport von 10 Byte Daten. Sie nutzen also nur weniger als 25% Ihrer verfügbaren Bandbreite. Und das erklärt noch nicht den Overhead, den die Protokolle auf niedrigerer Ebene wie Ethernet oder PPPoE verbrauchen (aber diese sind schwer abzuschätzen, weil es so viele Alternativen gibt).
Außerdem stellen viele kleine Pakete eine größere Belastung für Router, Firewalls, Switches und andere Netzwerkinfrastrukturgeräte dar. Wenn Sie, Ihr Dienstanbieter und Ihre Benutzer nicht in hochwertige Hardware investieren, kann dies zu einem weiteren Engpass führen.
Aus diesem Grund sollten Sie versuchen, alle verfügbaren Daten auf einmal in einem TCP-Segment zu senden.
Hinweise zum Umgang mit Paketverlust : Wenn Sie TCP verwenden, müssen Sie sich darüber keine Gedanken machen. Das Protokoll selbst stellt sicher, dass alle verlorenen Pakete erneut gesendet und in der richtigen Reihenfolge verarbeitet werden. Sie können also davon ausgehen, dass alle von Ihnen gesendeten Pakete auf der anderen Seite eintreffen und in der Reihenfolge, in der Sie sie senden. Der Preis dafür ist, dass Ihr Player bei Paketverlust eine erhebliche Verzögerung erleidet, da ein verworfenes Paket den gesamten Datenstrom anhält, bis es erneut angefordert und empfangen wurde.
Wenn dies ein Problem ist, können Sie immer UDP verwenden. Aber dann müssen Sie Ihre eigene Lösung für verlorene und nicht mehr funktionierende Nachrichten finden (dies garantiert zumindest, dass die Nachrichten, die ankommen, vollständig und unbeschädigt ankommen).
quelle
Ein großer ist besser.
Wie Sie sagten, ist Paketverlust der Hauptgrund. Pakete werden in der Regel in Frames fester Größe gesendet. Es ist daher besser, einen Frame mit einer großen Nachricht als 10 Frames mit 10 kleinen Frames aufzunehmen.
Bei Standard-TCP ist dies jedoch kein wirkliches Problem, es sei denn, Sie deaktivieren es. (Es heißt Nagles Algorithmus , und für Spiele sollten Sie ihn deaktivieren.) TCP wartet auf eine feste Zeitüberschreitung oder bis das Paket "voll" ist. Wo "voll" ist, wäre eine leicht magische Zahl, die teilweise durch die Rahmengröße bestimmt wird.
quelle
recv()
für jedensend()
Anruf einen Anruf erhalten. Dies ist das, wonach die meisten Menschen suchen. Verwenden Sie ein Protokoll, das dies garantiert, wie es UDP tut. "Wenn alles, was Sie haben, TCP ist, sieht alles aus wie ein Stream"Alle vorherigen Antworten sind falsch. In der Praxis spielt es keine Rolle, ob Sie einen langen
send()
Anruf oder mehrere kleinesend()
Anrufe tätigen.Wie Phillip feststellt, hat ein TCP-Segment einen gewissen Overhead, aber als Anwendungsprogrammierer haben Sie keine Kontrolle darüber, wie Segmente generiert werden. In einfachen Worten:
Es steht dem Betriebssystem völlig frei , alle Ihre Daten zu puffern und in einem Segment zu senden, oder das lange zu nehmen und es in mehrere kleine Segmente aufzuteilen.
Dies hat mehrere Implikationen, aber die wichtigste ist, dass:
Der Grund dafür ist, dass TCP ein Stream- Protokoll ist. TCP behandelt Ihre Daten als einen langen Strom von Bytes und hat absolut kein Konzept von "Paketen". Wenn
send()
Sie diesem Stream Bytes hinzufügen undrecv()
Bytes von der anderen Seite entfernen. TCP puffert und teilt Ihre Daten aggressiv, wo immer es angebracht erscheint, um sicherzustellen, dass Ihre Daten so schnell wie möglich auf die andere Seite gelangen.Wenn Sie "Pakete" mit TCP senden und empfangen möchten, müssen Sie Paketstartmarkierungen, Längenmarkierungen usw. implementieren. Wie wäre es stattdessen mit einem nachrichtenorientierten Protokoll wie UDP? UDP garantiert, dass ein
send()
Anruf in ein gesendetes Datagramm und in einenrecv()
Anruf übersetzt wird!quelle
recv()
, müssen Sie eine eigene Pufferung vornehmen, um dies auszugleichen. Ich würde es in die gleiche Schwierigkeit wie die Implementierung von Zuverlässigkeit über UDP einordnen.while(1) { uint16_t size; read(sock, &size, sizeof(size)); size = ntoh(size); char message[size]; read(sock, buffer, size); handleMessage(message); }
(Aus Gründen der Kürze wird auf Fehlerbehandlung und partielle Lesevorgänge verzichtet, es ändert sich jedoch nicht viel). Dadurch ist eineselect
nicht viel mehr Komplexität hinzuzufügt und wenn Sie TCP verwenden , müssen Sie wahrscheinlich sowieso die Teilnachricht puffern. Die Implementierung einer robusten Zuverlässigkeit über UDP ist viel komplizierter.Viele kleine Pakete sind in Ordnung. Wenn Sie sich Sorgen über den TCP-Overhead machen, fügen Sie einfach einen ein
bufferstream
, der bis zu 1500 Zeichen enthält (oder was auch immer Ihre TCP-MTUs sind, am besten dynamisch anfordern), und lösen Sie das Problem an einem Ort. Auf diese Weise sparen Sie den Overhead von ~ 40 Bytes für jedes zusätzliche Paket, das Sie sonst erstellt hätten.Trotzdem ist es immer noch besser, weniger Daten zu senden und größere Objekte zu erstellen. Natürlich ist das Senden kleiner
"UID:10|1|2|3
als das SendenUID:10;x:1UID:10;y:2UID:10;z:3
. Auch an diesem Punkt sollten Sie das Rad nicht neu erfinden. Verwenden Sie eine Bibliothek wie protobuf , die solche Daten auf eine 10-Byte-Zeichenfolge oder weniger reduzieren kann.Das Einzige, was Sie nicht vergessen sollten, ist das Einfügen von
Flush
Befehlen in Ihren Stream an den relevanten Stellen, denn sobald Sie aufhören, Daten zu Ihrem Stream hinzuzufügen, wartet er möglicherweise unendlich, bevor er etwas sendet. Wirklich problematisch, wenn Ihr Client auf diese Daten wartet und Ihr Server nichts Neues sendet, bis der Client den nächsten Befehl sendet.Paketverlust ist etwas, das Sie hier geringfügig beeinflussen können. Jedes von Ihnen gesendete Byte kann möglicherweise beschädigt werden, und TCP fordert automatisch eine erneute Übertragung an. Kleinere Pakete bedeuten eine geringere Wahrscheinlichkeit, dass jedes einzelne Paket beschädigt wird. Da sich die Gesamtkosten jedoch summieren, senden Sie noch mehr Bytes, wodurch die Wahrscheinlichkeit eines verlorenen Pakets noch größer wird. Wenn ein Paket verloren geht, puffert TCP alle nachfolgenden Daten, bis das fehlende Paket erneut gesendet und empfangen wird. Dies führt zu einer großen Verzögerung (Ping). Während der Gesamtverlust an Bandbreite aufgrund von Paketverlust vernachlässigbar sein kann, wäre der höhere Ping für Spiele unerwünscht.
Fazit: Senden Sie so wenig Daten wie möglich, senden Sie große Pakete und schreiben Sie keine eigenen Methoden auf niedriger Ebene, sondern verlassen Sie sich auf bekannte Bibliotheken und Methoden wie
bufferstream
und protobuf, um das schwere Heben zu bewältigen.quelle
bufferstream
ist trivial, seine eigenen zu schreiben. Deshalb habe ich es eine Methode genannt. Sie möchten immer noch alles an einem Ort erledigen und Ihre Pufferlogik nicht in Ihren Nachrichtencode integrieren. Was die Objektserialisierung angeht, bezweifle ich sehr, dass Sie etwas Besseres erhalten als die Tausenden von Arbeitsstunden, die andere dort einbringen, auch wenn Sie es dennoch versuchen. Ich rate Ihnen dringend, Ihre Lösung mit bekannten Implementierungen zu vergleichen.Obwohl ich selbst ein Neuling in der Netzwerkprogrammierung bin, möchte ich meine begrenzten Erfahrungen mit ein paar Punkten teilen:
In Bezug auf Messungen sollten folgende Metriken berücksichtigt werden:
Wie bereits erwähnt, sollten Sie dies tun, wenn Sie feststellen, dass Sie in gewisser Hinsicht nicht eingeschränkt sind und UDP verwenden können. Es gibt einige UDP-basierte Implementierungen, sodass Sie weder das Rad neu erfinden noch gegen jahrelange Erfahrung und nachgewiesene Erfahrung ankämpfen müssen. Solche erwähnenswerten Implementierungen sind:
Fazit: Da eine UDP-Implementierung eine TCP-Implementierung (um den Faktor 3) übertreffen könnte, ist es sinnvoll, sie in Betracht zu ziehen, sobald Sie Ihr Szenario als UDP-freundlich identifiziert haben. Sei gewarnt! Es ist immer eine schlechte Idee, den vollständigen TCP-Stack über UDP zu implementieren.
quelle