UDP nicht blockierend oder ein separater Thread zum Empfangen?

10

Ich erstelle ein Multiplayer-Spiel (für unter 64 Spieler). Ich habe mich bereits für einen separaten Thread für die Netzwerkschleife entschieden, habe mich aber gefragt, ob es besser ist, einen zusätzlichen Thread für den Empfang von UDP zu erstellen oder den Empfangssocket auf nicht blockierend zu setzen (ohne zusätzlichen Thread).

Oder ist es besser, eine andere Methode wie Asynchrone Sockets zu verwenden? Bessere Methoden sind immer willkommen!

Yannick Lange
quelle

Antworten:

6

Okay, zunächst einmal, wenn Sie etwas haben und es funktioniert, ist es normalerweise eine gute Idee, es so zu lassen. Warum reparieren, was nicht kaputt ist?

Aber wenn Sie Probleme haben und Ihren Netzwerkcode wirklich neu schreiben möchten, haben Sie meiner Meinung nach vier Hauptoptionen:

  1. Multithread-Blockierungscode (was Sie gerade tun)
  2. Nicht blockierende Sockets mit Level-Trigger-Benachrichtigung
  3. Nicht blockierende Steckdosen mit Benachrichtigung über Bereitschaftsänderungen
  4. Asynchrone Sockets

Nachdem ich viele Multiplayer-Clients und -Server (von Peer-to-Peer bis hin zu Massively Multiplayer) geschrieben habe, denke ich, dass Option 2 zu der geringsten Komplexität mit ziemlich guter Leistung sowohl für den Server- als auch für den Client-Teil des Spiels führt. Als knappe Sekunde würde ich Option 4 wählen, aber dafür müssen Sie normalerweise Ihr gesamtes Programm überdenken, und ich finde es meistens nützlich für Server und nicht für Clients.

Insbesondere möchte ich davon abraten, Sockets in einer Multithread-Umgebung zu blockieren, da dies normalerweise zu Sperr- und anderen Synchronisierungsfunktionen führt, die nicht nur die Komplexität des Codes erheblich erhöhen, sondern auch dessen Leistung beeinträchtigen können, da einige Threads darauf warten Andere.

Vor allem aber sind die meisten Socket-Implementierungen (und die meisten E / A-Implementierungen dazu) auf der niedrigsten Ebene nicht blockierend. Blockierungsvorgänge werden einfach bereitgestellt, um die Entwicklung trivialer Programme zu vereinfachen. Wenn ein Socket blockiert, ist die CPU in diesem Thread vollständig inaktiv. Warum also eine nicht blockierende Abstraktion über die blockierende Abstraktion über eine bereits nicht blockierende Aufgabe erstellen?

Die nicht blockierende Socket-Programmierung ist etwas entmutigend, wenn Sie sie nicht ausprobiert haben, aber es stellt sich heraus, dass sie recht einfach ist. Wenn Sie bereits Eingabeabfragen durchführen, haben Sie bereits die Einstellung, nicht blockierende Sockets durchzuführen.

Das erste, was Sie tun möchten, ist, den Socket auf nicht blockierend zu setzen. Das machst du mit fcntl().

Danach, bevor Sie zu tun send(), recv(), sendto(), recvfrom(), accept()( connect()ist ein bisschen anders) oder andere Anrufe, die den Thread blockieren könnten, rufen Sie select()an der Steckdose. select()Hier erfahren Sie, ob eine nachfolgende Lese- oder Schreiboperation für den Socket ausgeführt werden kann, ohne dass dieser blockiert. In diesem Fall können Sie den gewünschten Vorgang sicher ausführen, und der Socket wird nicht blockiert.

Dies in ein Spiel aufzunehmen ist ganz einfach. Wenn Sie bereits eine Spielschleife haben, zum Beispiel wie folgt:

while game_is_running do
    poll_input()
    update_world()
    do_sounds()
    draw_world()
end

Sie können es so ändern, dass es so aussieht:

while game_is_running do
    poll_input()
    read_network()
    update_world()
    do_sounds()
    write_network()
    draw_world()
end

wo

function read_network()
    while select(socket, READ) do
        game.net_input.enqueue(recv(socket))
    end
end

und

function write_network()
    while not game.net_output.empty and select(socket, WRITE) do
        send(socket, game.net_output.dequeue())
    end
end

In Bezug auf die Ressourcen ist das einzige Buch, von dem ich denke, dass jeder es in seinen Bücherregalen haben muss, auch wenn es das einzige Buch ist, das er hat, " Unix Network Programming, Vol 1. " des verstorbenen Richard Stevens. Es spielt keine Rolle, ob Sie Windows oder ein anderes Betriebssystem oder eine Sprach-Socket-Programmierung durchführen. Denken Sie nicht, dass Sie Steckdosen verstehen, bis Sie dieses Buch gelesen haben.

Eine weitere Ressource, in der Sie einen allgemeinen Überblick über die verfügbaren Lösungen in Bezug auf die Mehrfach-Socket-Programmierung finden (hauptsächlich relevant für die Serverprogrammierung), ist diese Seite .

Panda Pyjama
quelle
Ich habe gerade meine Spiel-Engine gestartet, daher ist das Umschreiben kein Problem. Diese Antwort war sehr nützlich und ich danke Ihnen für die Buchempfehlung. Kennen Sie auch ein Buch, das sich spezifischer mit dem Networking in Spielen befasst?
Yannick Lange
3
@YannickLange: Nein, aber ich würde Ihnen nicht empfehlen, nach spielspezifischen Büchern zu suchen, da das Networking ziemlich genreunabhängig ist und auch kein anderes Buch dem Niveau von Stevens 'Buch entspricht. Ein großes Lob für die Verwendung von UDP, da die Verwendung eines nachrichtenorientierten Protokolls beim Schreiben von nachrichtenorientierten Programmen (wie bei den meisten Spielen) eine gute Idee ist. Viele Leute neigen dazu, eine Nachrichtenabstraktion über TCP zu schreiben, die selbst eine stromorientierte Abstraktion ist, die über ein nachrichtenorientiertes Protokoll (IP) aufgebaut ist.
Panda Pyjama
-1

Sie haben nicht geschrieben, welche Programmiersprache Sie verwenden, aber die meisten Sprachen bieten gute asynchrone Socket-Frameworks, die häufig Funktionen des zugrunde liegenden Betriebssystems verwenden.

Wenn Sie Ihre eigene Thread-Implementierung basierend auf dem Blockieren von Sockets schreiben (denken Sie daran: Wenn Sie mehrere Clients haben, benötigen Sie einen Thread für jeden einzelnen Socket), werden Sie nur neu erfinden, was das asynchrone Socket-Framework bereits bietet.

Daher würde ich Ihnen empfehlen, asynchrone Sockets zu verwenden.

Philipp
quelle
4
Warum braucht er für jede einzelne Steckdose einen Faden? Besonders bei asynchronen Steckdosen? Viele Heavyweight-Server verwenden ein "Thread-Pool" -Modell, um Socket-Daten zu verarbeiten, wobei jeder Thread N Clients bedient und sie nach Bedarf erzeugt oder beendet werden. Denken Sie nur, diese Antwort muss verbessert werden: D
Grimshaw
@Grimshaw Ich denke du hast meine Antwort falsch verstanden. Ich glaube, ich habe jetzt klargestellt, dass ein Multithread-Modell erforderlich wäre, wenn Sockets verwendet werden.
Philipp
@Philipp Ich habe nicht gesagt, welche Programmiersprache ich benutze, weil ich dachte, dass sie für die Frage nicht wirklich relevant ist (ich benutze übrigens C ++). Ich danke Ihnen für Ihre Empfehlung, einen asynchronen Socket zu verwenden. Kennen Sie ein empfohlenes Buch / eine Webseite zum Erlernen dieser Art von Methoden in Spielen (Engines)?
Yannick Lange
@Grimshaw: Und wie werden die einzelnen Thread- NClients bedient? Wenn Sie blockieren und sicherstellen möchten, dass jeder Socket seine Daten unabhängig vom Status der übrigen Sockets verarbeitet, müssen Sie einen Thread pro Socket haben. Das, oder verwenden Sie keine Sperrbuchsen.
Panda Pyjama
Mein Kommentar war zur ersten Version Ihrer Antwort. In der Zwischenzeit haben Sie sie bearbeitet und korrigiert. Vergessen Sie, was ich gesagt habe: D
Grimshaw,