Über das Netzwerkmodell in Spielen für Anfänger

Über das Netzwerkmodell in Spielen für Anfänger
In den letzten zwei Wochen habe ich an der Netzwerk-Engine für mein Spiel gearbeitet. Davor wusste ich überhaupt nichts über Networking in Spielen, also habe ich viele Artikel gelesen und viele Experimente durchgeführt, um alle Konzepte zu verstehen und meine eigene Networking-Engine schreiben zu können.

In diesem Leitfaden möchte ich Ihnen die verschiedenen Konzepte vorstellen, die Sie lernen müssen, bevor Sie Ihre eigene Spiel-Engine schreiben, sowie die besten Ressourcen und Artikel zum Erlernen dieser Konzepte.

Im Allgemeinen gibt es zwei Haupttypen von Netzwerkarchitekturen: Peer-to-Peer und Client-Server. In einer Peer-to-Peer-Architektur (p2p) werden Daten zwischen beliebigen Paaren verbundener Spieler übertragen, während in einer Client-Server-Architektur Daten nur zwischen den Spielern und dem Server übertragen werden.

Obwohl in einigen Spielen immer noch die Peer-to-Peer-Architektur verwendet wird, ist die Client-Server-Architektur der Standard: Sie ist einfacher zu implementieren, erfordert eine geringere Kanalbreite und erleichtert den Schutz vor Betrug. Daher konzentrieren wir uns in diesem Leitfaden auf die Client-Server-Architektur.

Insbesondere interessieren uns autoritäre Server: In solchen Systemen hat der Server immer Recht. Wenn der Spieler beispielsweise denkt, dass er bei (10, 5) ist, und der Server ihm mitteilt, dass er bei (5, 3) ist, sollte der Client seine Position durch die Position ersetzen, die der Server meldet, und nicht umgekehrt. Der Einsatz autoritativer Server erleichtert die Erkennung von Betrügern.

Es gibt drei Hauptkomponenten in Gaming-Netzwerksystemen:

  • Transportprotokoll: Wie Daten zwischen Clients und dem Server übertragen werden.
  • Anwendungsprotokoll: Was wird von Clients an den Server und vom Server an Clients übertragen und in welchem ​​Format.
  • Anwendungslogik: Wie die übertragenen Daten verwendet werden, um den Status von Clients und dem Server zu aktualisieren.

Es ist sehr wichtig, die Rolle jedes Teils und die damit verbundenen Schwierigkeiten zu verstehen.

Transportprotokoll

Der erste Schritt besteht darin, ein Protokoll für den Datentransport zwischen Server und Clients auszuwählen. Hierfür gibt es zwei Internetprotokolle: TCP и UDP. Sie können jedoch auf der Grundlage eines davon Ihr eigenes Transportprotokoll erstellen oder eine Bibliothek verwenden, die diese verwendet.

Vergleich von TCP und UDP

Sowohl TCP als auch UDP basieren auf IP. IP ermöglicht die Übertragung eines Pakets von einer Quelle zu einem Empfänger, garantiert jedoch nicht, dass das gesendete Paket früher oder später den Empfänger erreicht, dass es ihn mindestens einmal erreicht und dass die Paketfolge eintrifft die richtige Reihenfolge. Darüber hinaus kann ein Paket nur eine begrenzte Datengröße enthalten, die durch den Wert angegeben wird MTU.

UDP ist nur eine dünne Schicht über IP. Daher gelten die gleichen Einschränkungen. Im Gegensatz dazu verfügt TCP über viele Funktionen. Es stellt eine zuverlässige geordnete Verbindung zwischen zwei Knoten mit Fehlerprüfung bereit. Daher ist TCP sehr praktisch und wird in vielen anderen Protokollen verwendet, beispielsweise in HTTP, fTP и SMTP. Aber all diese Funktionen haben ihren Preis: verzögern.

Um zu verstehen, warum diese Funktionen Latenz verursachen können, müssen wir verstehen, wie TCP funktioniert. Wenn der sendende Host ein Paket an den empfangenden Host sendet, erwartet er eine Bestätigung (ACK). Wenn er es nach einer bestimmten Zeit nicht erhält (weil das Paket oder die Bestätigung verloren gegangen ist oder aus einem anderen Grund), sendet er das Paket erneut. Darüber hinaus garantiert TCP, dass Pakete in der richtigen Reihenfolge empfangen werden. Bis zum Empfang eines verlorenen Pakets können alle anderen Pakete nicht verarbeitet werden, selbst wenn sie bereits vom empfangenden Knoten empfangen wurden.

Aber wie Sie wahrscheinlich wissen, ist die Latenz bei Multiplayer-Spielen sehr wichtig, insbesondere bei so aktiven Genres wie FPS. Aus diesem Grund verwenden viele Spiele UDP mit einem eigenen Protokoll.

Ein auf UDP basierendes natives Protokoll kann aus verschiedenen Gründen effizienter sein als TCP. Beispielsweise können einige Pakete als vertrauenswürdig und andere als nicht vertrauenswürdig markiert werden. Daher ist es ihm egal, ob das unzuverlässige Paket den Empfänger erreicht hat. Oder es kann mehrere Datenströme verarbeiten, sodass ein in einem Datenstrom verlorenes Paket andere Datenströme nicht verlangsamt. Beispielsweise könnte es einen Thread für Spielereingaben und einen weiteren Thread für Chatnachrichten geben. Wenn eine Chat-Nachricht, bei der es sich nicht um dringende Daten handelt, verloren geht, wird die Eingabe dringender Daten dadurch nicht verlangsamt. Oder ein proprietäres Protokoll implementiert die Zuverlässigkeit möglicherweise anders als TCP, um in einer Videospielumgebung effizienter zu sein.

Wenn TCP also scheiße ist, bauen wir dann unser eigenes Transportprotokoll auf Basis von UDP?

Alles ist etwas komplizierter. Auch wenn TCP für Gaming-Netzwerksysteme fast nicht optimal ist, kann es für Ihr spezielles Spiel recht gut funktionieren und Ihnen wertvolle Zeit sparen. Beispielsweise ist die Latenz möglicherweise kein Problem für ein rundenbasiertes Spiel oder ein Spiel, das nur in LAN-Netzwerken gespielt werden kann, wo Latenz und Paketverlust viel geringer sind als im Internet.

Viele erfolgreiche Spiele, darunter World of Warcraft, Minecraft und Terraria, verwenden TCP. Die meisten FPS verwenden jedoch ihre eigenen UDP-basierten Protokolle, daher werden wir weiter unten näher auf sie eingehen.

Wenn Sie sich für die Verwendung von TCP entscheiden, stellen Sie sicher, dass es deaktiviert ist Nagles Algorithmus, weil es Pakete vor dem Senden puffert, was bedeutet, dass es die Verzögerung erhöht.

Weitere Informationen zu den Unterschieden zwischen UDP und TCP im Kontext von Multiplayer-Spielen finden Sie im Artikel von Glenn Fiedler UDP vs. TCP.

Proprietäres Protokoll

Sie möchten Ihr eigenes Transportprotokoll erstellen, wissen aber nicht, wo Sie anfangen sollen? Sie haben Glück, denn Glenn Fiedler hat zwei tolle Artikel darüber geschrieben. Sie werden darin viele kluge Ideen finden.

Erster Artikel Networking für Spieleprogrammierer 2008, einfacher als der zweite Aufbau eines Spielnetzwerkprotokolls 2016. Ich empfehle Ihnen, mit dem älteren zu beginnen.

Beachten Sie, dass Glenn Fiedler ein großer Befürworter der Verwendung Ihres eigenen, auf UDP basierenden Protokolls ist. Und nachdem Sie seine Artikel gelesen haben, werden Sie wahrscheinlich seine Meinung übernehmen, dass TCP in Videospielen schwerwiegende Nachteile hat, und Sie werden Ihr eigenes Protokoll implementieren wollen.

Aber wenn Sie neu im Netzwerkbereich sind, tun Sie sich selbst einen Gefallen und verwenden Sie TCP oder eine Bibliothek. Um Ihr eigenes Transportprotokoll erfolgreich umzusetzen, müssen Sie im Vorfeld viel lernen.

Netzwerkbibliotheken

Wenn Sie etwas Effizienteres als TCP benötigen, sich aber nicht die Mühe machen möchten, Ihr eigenes Protokoll zu implementieren und auf viele Details einzugehen, können Sie die Netzbibliothek verwenden. Da sind viele von denen:

Ich habe sie nicht alle ausprobiert, aber ich bevorzuge ENet, weil es einfach zu bedienen und zuverlässig ist. Darüber hinaus verfügt es über eine klare Dokumentation und ein Tutorial für Anfänger.

Schlussfolgerung des Transportprotokolls

Zusammenfassend gibt es zwei Haupttransportprotokolle: TCP und UDP. TCP verfügt über viele nützliche Funktionen: Zuverlässigkeit, Beibehaltung der Paketreihenfolge, Fehlererkennung. UDP verfügt nicht über all das, aber TCP weist naturgemäß eine hohe Latenz auf, die für einige Spiele nicht akzeptabel ist. Das heißt, um eine geringe Latenz zu gewährleisten, können Sie Ihr eigenes Protokoll auf Basis von UDP erstellen oder eine Bibliothek verwenden, die das Transportprotokoll auf UDP implementiert und für Multiplayer-Videospiele angepasst ist.

Die Wahl zwischen TCP, UDP und der Bibliothek hängt von mehreren Faktoren ab. Zunächst zu den Anforderungen des Spiels: Benötigt es eine niedrige Latenz? Zweitens aus den Anforderungen des Anwendungsprotokolls: Braucht es ein zuverlässiges Protokoll? Wie wir im nächsten Teil sehen werden, ist es möglich, ein Anwendungsprotokoll zu erstellen, für das ein unzuverlässiges Protokoll durchaus geeignet ist. Schließlich müssen Sie auch die Erfahrung des Netzwerk-Engine-Entwicklers berücksichtigen.

Ich habe zwei Tipps:

  • Abstrahieren Sie das Transportprotokoll so weit wie möglich vom Rest der Anwendung, damit es problemlos ersetzt werden kann, ohne den gesamten Code neu schreiben zu müssen.
  • Überoptimieren Sie nicht. Wenn Sie kein Netzwerkexperte sind und nicht sicher sind, ob Sie ein eigenes UDP-basiertes Transportprotokoll benötigen, können Sie mit TCP oder einer Bibliothek beginnen, die Zuverlässigkeit bietet, und dann die Leistung testen und messen. Wenn Sie Probleme haben und sicher sind, dass es sich um ein Transportprotokoll handelt, ist es möglicherweise an der Zeit, ein eigenes Transportprotokoll zu erstellen.

Am Ende dieses Teils empfehle ich Ihnen, es zu lesen Einführung in die Programmierung von Multiplayer-Spielen Brian Hook, der viele der hier besprochenen Themen abdeckt.

Anwendungsprotokoll

Da wir nun Daten zwischen Clients und dem Server austauschen können, müssen wir entscheiden, welche Daten in welchem ​​Format übertragen werden sollen.

Das klassische Schema besteht darin, dass Clients Eingaben oder Aktionen an den Server senden und der Server den aktuellen Spielstatus an die Clients sendet.

Der Server sendet nicht den vollständigen, sondern den gefilterten Status mit Entitäten, die sich in der Nähe des Players befinden. Er tut dies aus drei Gründen. Erstens ist der Gesamtzustand möglicherweise zu groß, um mit hoher Frequenz übertragen zu werden. Zweitens interessieren sich Kunden vor allem für visuelle und akustische Daten, da der Großteil der Spiellogik auf dem Spieleserver simuliert wird. Drittens muss der Spieler in manchen Spielen bestimmte Daten, etwa die Position des Gegners auf der anderen Seite der Karte, nicht kennen, da er sonst Pakete erschnüffeln und genau wissen kann, wohin er sich bewegen muss, um ihn zu töten.

Serialisierung

Der erste Schritt besteht darin, die Daten, die wir senden möchten (Eingabe oder Spielstatus), in ein für die Übertragung geeignetes Format zu konvertieren. Dieser Vorgang wird aufgerufen Serialisierung.

Mir kommt sofort die Idee in den Sinn, ein für Menschen lesbares Format wie JSON oder XML zu verwenden. Dies wäre jedoch völlig ineffizient und würde umsonst den größten Teil des Kanals beanspruchen.

Stattdessen empfiehlt sich die Verwendung des Binärformats, das deutlich kompakter ist. Das heißt, die Pakete enthalten nur wenige Bytes. Hier müssen wir das Problem berücksichtigen Byte-Reihenfolge, die auf verschiedenen Computern unterschiedlich sein kann.

Um Daten zu serialisieren, können Sie beispielsweise eine Bibliothek verwenden:

Stellen Sie einfach sicher, dass die Bibliothek tragbare Archive erstellt und sich um Endianness kümmert.

Eine alternative Lösung wäre, es selbst zu implementieren. Das ist nicht so schwer, insbesondere wenn Sie in Ihrem Code einen datenzentrierten Ansatz verwenden. Darüber hinaus können Sie damit Optimierungen durchführen, die bei Verwendung der Bibliothek nicht immer möglich sind.

Glenn Fiedler hat zwei Artikel zum Thema Serialisierung geschrieben: Pakete lesen und schreiben и Serialisierungsstrategien.

Kompression

Die zwischen Clients und Server übertragene Datenmenge wird durch die Bandbreite des Kanals begrenzt. Durch die Datenkomprimierung können Sie in jedem Snapshot mehr Daten übertragen, die Aktualisierungsrate erhöhen oder einfach den Bandbreitenbedarf reduzieren.

Bit-Packung

Die erste Technik ist das Bit-Packen. Es besteht darin, genau die Anzahl an Bits zu verwenden, die zur Beschreibung des gewünschten Wertes erforderlich ist. Wenn Sie beispielsweise eine Aufzählung haben, die 16 verschiedene Werte haben kann, können Sie anstelle eines ganzen Bytes (8 Bits) nur 4 Bits verwenden.

Wie man das umsetzt, erklärt Glenn Fiedler im zweiten Teil des Artikels. Pakete lesen und schreiben.

Das Packen von Bits funktioniert besonders gut mit der Diskretisierung, die das Thema des nächsten Abschnitts sein wird.

Probenahme

Probenahme ist eine verlustbehaftete Komprimierungstechnik, die nur eine Teilmenge möglicher Werte verwendet, um einen Wert zu kodieren. Der einfachste Weg, die Diskretisierung zu implementieren, ist das Runden von Gleitkommazahlen.

Glenn Fiedler (wieder!) zeigt in seinem Artikel, wie man Diskretisierung in der Praxis anwenden kann Snapshot-Komprimierung.

Komprimierungsalgorithmen

Die nächste Technik werden verlustfreie Komprimierungsalgorithmen sein.

Hier sind meiner Meinung nach die drei interessantesten Algorithmen, die Sie kennen müssen:

  • Huffman-Codierung mit vorberechnetem Code, der extrem schnell ist und gute Ergebnisse liefern kann. Es wurde zum Komprimieren von Paketen in der Quake3-Netzwerk-Engine verwendet.
  • zlib ist ein Allzweck-Komprimierungsalgorithmus, der niemals die Datenmenge erhöht. Wie kann man sehen hierEs wurde in einer Vielzahl von Anwendungen eingesetzt. Für die Aktualisierung von Zuständen kann es überflüssig sein. Es kann jedoch nützlich sein, wenn Sie Assets, Langtexte oder Gelände vom Server an Clients senden müssen.
  • Lauflängen kopieren ist wahrscheinlich der einfachste Komprimierungsalgorithmus, aber er ist für bestimmte Datentypen sehr effizient und kann als Vorverarbeitungsschritt vor zlib verwendet werden. Es eignet sich besonders zum Komprimieren von Gelände, das aus Kacheln oder Voxeln besteht, in denen sich viele benachbarte Elemente wiederholen.

Delta-Komprimierung

Die letzte Komprimierungstechnik ist die Delta-Komprimierung. Es liegt darin, dass nur die Unterschiede zwischen dem aktuellen Spielstand und dem letzten vom Client empfangenen Stand übermittelt werden.

Es wurde erstmals in der Quake3-Netzwerk-Engine verwendet. Hier sind zwei Artikel, die die Verwendung erklären:

Glenn Fiedler verwendete es auch im zweiten Teil seines Artikels. Snapshot-Komprimierung.

Шифрование

Darüber hinaus müssen Sie möglicherweise die Übertragung von Informationen zwischen Clients und dem Server verschlüsseln. Dafür gibt es mehrere Gründe:

  • Datenschutz/Vertraulichkeit: Nachrichten können nur vom Empfänger gelesen werden und kein anderer Netzwerk-Sniffer kann sie lesen.
  • Authentifizierung: Eine Person, die in die Rolle eines Spielers schlüpfen möchte, muss seinen Schlüssel kennen.
  • Cheat-Prävention: Es wird für böswillige Spieler viel schwieriger, ihre eigenen Cheat-Pakete zu erstellen, sie müssen das Verschlüsselungsschema replizieren und den Schlüssel finden (der sich bei jeder Verbindung ändert).

Ich empfehle dringend, hierfür eine Bibliothek zu verwenden. Ich empfehle die Verwendung libnatrium, weil es besonders einfach ist und tolle Tutorials enthält. Besonders interessant ist das Tutorial zu Schlüsselaustausch, wodurch Sie bei jeder neuen Verbindung neue Schlüssel generieren können.

Anwendungsprotokoll: Fazit

Damit ist das Bewerbungsprotokoll abgeschlossen. Ich glaube, dass die Komprimierung völlig optional ist und die Entscheidung, sie zu verwenden, nur vom Spiel und der erforderlichen Bandbreite abhängt. Verschlüsselung ist meiner Meinung nach Pflicht, im ersten Prototyp kann man aber darauf verzichten.

Anwendungslogik

Wir sind jetzt in der Lage, den Status im Client zu aktualisieren, es kann jedoch zu Latenzproblemen kommen. Nachdem der Spieler eine Eingabe gemacht hat, muss er auf eine Aktualisierung des Spielstatus vom Server warten, um zu sehen, welche Auswirkungen diese auf die Welt hatte.

Darüber hinaus ist die Welt zwischen zwei Statusaktualisierungen völlig statisch. Wenn die Statusaktualisierungsrate niedrig ist, sind die Bewegungen sehr ruckartig.

Es gibt verschiedene Techniken, um die Auswirkungen dieses Problems zu mildern, und ich werde sie im nächsten Abschnitt besprechen.

Verzögerungsglättungstechniken

Alle in diesem Abschnitt beschriebenen Techniken werden in der Serie ausführlich besprochen. Rasanter Multiplayer Gabriel Gambetta. Ich kann die Lektüre dieser hervorragenden Artikelserie nur wärmstens empfehlen. Es enthält auch eine interaktive Demo, um zu sehen, wie diese Techniken in der Praxis funktionieren.

Die erste Technik besteht darin, das Eingabeergebnis direkt anzuwenden, ohne auf eine Antwort vom Server zu warten. Das heißt clientseitige Vorhersage. Wenn der Client jedoch ein Update vom Server erhält, muss er überprüfen, ob seine Vorhersage korrekt war. Wenn dies nicht der Fall ist, muss er lediglich seinen Status entsprechend dem, was er vom Server erhalten hat, ändern, da der Server autoritär ist. Diese Technik wurde erstmals in Quake verwendet. Mehr dazu können Sie im Artikel lesen. Überprüfung des Quake Engine-Codes Fabien Sanglars [Übersetzung auf Habré].

Der zweite Satz von Techniken wird verwendet, um die Bewegung anderer Entitäten zwischen zwei Statusaktualisierungen zu glätten. Es gibt zwei Möglichkeiten, dieses Problem zu lösen: Interpolation und Extrapolation. Bei der Interpolation werden die letzten beiden Zustände eingenommen und der Übergang von einem zum anderen dargestellt. Der Nachteil besteht darin, dass es nur einen kleinen Teil der Verzögerung verursacht, da der Client immer sieht, was in der Vergangenheit passiert ist. Bei der Extrapolation geht es darum, basierend auf dem letzten vom Client empfangenen Status vorherzusagen, wo sich die Entitäten jetzt befinden sollten. Der Nachteil besteht darin, dass es zu einem großen Fehler zwischen der Prognose und der tatsächlichen Position kommt, wenn das Unternehmen die Bewegungsrichtung vollständig ändert.

Die letzte, fortschrittlichste Technik, die nur in FPS nützlich ist, ist Verzögerungskompensation. Bei der Verwendung der Verzögerungskompensation berücksichtigt der Server die Verzögerungen des Clients, wenn dieser auf das Ziel feuert. Wenn ein Spieler beispielsweise einen Kopfschuss auf seinem Bildschirm ausführt, sich sein Ziel jedoch aufgrund der Verzögerung in Wirklichkeit an einem anderen Ort befindet, wäre es unfair, dem Spieler aufgrund der Verzögerung das Recht zum Töten zu verweigern. Daher spult der Server die Zeit bis zu dem Zeitpunkt zurück, an dem der Spieler geschossen hat, um zu simulieren, was der Spieler auf seinem Bildschirm gesehen hat, und um zu prüfen, ob es zu einer Kollision zwischen seinem Schuss und dem Ziel gekommen ist.

Glenn Fiedler hat (wie immer!) 2004 einen Artikel geschrieben Netzwerkphysik (2004), in dem er den Grundstein für die Synchronisation von Physiksimulationen zwischen Server und Client legte. 2014 verfasste er eine neue Artikelserie Vernetzungsphysik, in dem er andere Techniken zur Synchronisierung physikalischer Simulationen beschrieb.

Es gibt auch zwei Artikel im Valve-Wiki: Quell-Multiplayer-Netzwerk и Latenzkompensierende Methoden beim Design und der Optimierung von Client/Server-In-Game-Protokollen Umgang mit Verspätungsentschädigung.

Betrugsprävention

Es gibt zwei Haupttechniken zur Betrugsprävention.

Erstens wird es für Betrüger schwieriger, bösartige Pakete zu versenden. Wie oben erwähnt, ist die Verschlüsselung eine gute Möglichkeit, dies zu implementieren.

Zweitens sollte der autorisierende Server nur Befehle/Eingaben/Aktionen empfangen. Der Client sollte nicht in der Lage sein, den Status auf dem Server anders als durch das Senden von Eingaben zu ändern. Dann muss der Server jedes Mal, wenn er Eingaben erhält, diese auf Gültigkeit prüfen, bevor er sie anwendet.

Anwendungslogik: Fazit

Ich empfehle Ihnen, eine Methode zur Simulation hoher Latenz und niedriger Bildwiederholraten zu implementieren, damit Sie das Verhalten Ihres Spiels unter schlechten Bedingungen testen können, selbst wenn Client und Server auf demselben Computer ausgeführt werden. Dies vereinfacht die Implementierung von Verzögerungsglättungstechniken erheblich.

Weitere nützliche Ressourcen

Wenn Sie weitere Netzwerkmodellressourcen erkunden möchten, finden Sie diese hier:

  • Glenn Fiedlers Blog — Es lohnt sich, seinen gesamten Blog zu lesen, er enthält viele hervorragende Artikel. Hier Alle Artikel zu Netzwerktechnologien werden gesammelt.
  • Tolles Game-Networking von M. Fatih MAR ist eine umfassende Liste von Artikeln und Videos zu Netzwerk-Engines für Videospiele.
  • В Wiki-Subreddit r/gamedev Es gibt auch viele nützliche Links.

Source: habr.com

Kommentar hinzufügen