UDP non bloquant ou un thread séparé pour la réception?

10

Je crée un jeu multijoueur (pour moins de 64 joueurs). J'ai déjà décidé d'avoir un thread séparé pour la boucle réseau, mais je me demandais s'il serait préférable de créer un thread supplémentaire pour recevoir UDP ou de définir le socket de réception sur non bloquant (sans thread supplémentaire).

Ou est-il préférable d'utiliser une autre méthode comme les sockets asynchrones? De meilleures méthodes sont toujours les bienvenues!

Yannick Lange
la source

Réponses:

6

D'accord, tout d'abord, si vous avez quelque chose et que ça marche, c'est généralement une bonne idée de le laisser comme ça. Pourquoi réparer ce qui n'est pas cassé?

Mais si vous rencontrez des problèmes et que vous souhaitez vraiment réécrire votre code réseau, je pense que vous avez quatre choix principaux:

  1. Code de blocage multithread (ce que vous faites en ce moment)
  2. Prises non bloquantes avec notification déclenchée par le niveau
  3. Sockets non bloquants avec notification de changement de disponibilité
  4. Prises asynchrones

Ayant écrit de nombreux clients et serveurs multijoueurs (de pair à pair à massivement multijoueurs), j'aime à penser que l'option 2 conduit à la moindre complexité, avec de très bonnes performances, tant pour le serveur que pour les parties clientes du jeu. En seconde, j'opterais pour l'option 4, mais cela nécessite généralement de repenser l'ensemble de votre programme, et je le trouve surtout utile pour les serveurs et non pour les clients.

En particulier, je voudrais déconseiller le blocage des sockets dans un environnement multithread, car cela conduit généralement au verrouillage et à d'autres fonctionnalités de synchronisation qui non seulement augmentent considérablement la complexité du code, mais peuvent également dégrader ses performances, car certains threads attendent autres.

Mais surtout, la plupart des implémentations de socket (et la plupart des implémentations d'E / S) ne sont pas bloquantes au niveau le plus bas. Les opérations de blocage sont simplement fournies pour simplifier le développement de programmes triviaux. Lorsqu'un socket bloque, le CPU de ce thread est complètement inactif, alors pourquoi construire une abstraction non bloquante sur l'abstraction bloquante sur une tâche déjà non bloquante?

La programmation des sockets non bloquantes est un peu intimidante si vous ne l'avez pas essayé, mais il s'avère que c'est assez simple, et si vous effectuez déjà une interrogation d'entrée, vous avez déjà la mentalité de faire des sockets non bloquantes.

La première chose que vous voulez faire est de définir le socket sur non bloquant. Tu fais ça avec fcntl().

Après cela, avant de le faire send(), recv(), sendto(), recvfrom(), accept()( connect()est un peu différent) ou d' autres appels qui pourraient bloquer le fil, vous appelez select()sur la prise. select()vous indique si une opération de lecture ou d'écriture ultérieure peut ou non être effectuée sur le socket sans le bloquer. Si tel est le cas, vous pouvez effectuer l'opération que vous souhaitez en toute sécurité et le socket ne se bloquera pas.

L'inclure dans un jeu est assez simple. Si vous avez déjà une boucle de jeu, par exemple comme ceci:

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

vous pouvez le changer pour ressembler à ceci:

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

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

et

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

En termes de ressources, le seul livre que je pense que tout le monde doit avoir dans ses étagères, même si c'est le seul livre qu'ils ont, est " Unix Network Programming, Vol 1. " de feu Richard Stevens. Peu importe si vous faites de la programmation Windows ou autre système d'exploitation ou socket de langue. Ne pensez pas que vous comprenez les prises avant d'avoir lu ce livre.

Une autre ressource où vous pouvez trouver un aperçu général des solutions disponibles en termes de programmation à plusieurs sockets (principalement pertinentes pour la programmation de serveur) est cette page .

Pyjama Panda
la source
Je viens de démarrer mon moteur de jeu, donc la réécriture n'est pas un problème. Cette réponse était vraiment utile et merci pour la recommandation du livre. Connaissez-vous également un livre plus spécifique sur le réseautage dans les jeux?
Yannick Lange
3
@YannickLange: Non, mais je ne vous recommanderais pas de rechercher des livres spécifiques au jeu, car la mise en réseau est à peu près indépendante du genre, et aucun autre livre n'est au niveau du livre de Stevens. Félicitations pour l'utilisation d'UDP, car l'utilisation d'un protocole orienté message est une bonne idée lors de l'écriture de programmes orientés message (comme la plupart des jeux). Beaucoup de gens ont tendance à finir par écrire une abstraction de message sur TCP, qui est en soi une abstraction orientée flux construite sur un protocole orienté message (IP).
Panda Pyjama
-1

Vous n'avez pas écrit le langage de programmation que vous utilisez, mais la plupart des langages fournissent de bonnes infrastructures de socket asynchrones qui utilisent souvent les fonctionnalités du système d'exploitation sous-jacent.

Lorsque vous écrivez votre propre implémentation filetée basée sur des sockets bloquants (rappelez-vous: lorsque vous avez plusieurs clients, vous avez besoin d'un thread pour chaque socket), vous allez juste réinventer ce que le framework de socket asynchrone fournit déjà.

Je vous recommande donc d'utiliser des sockets asynchrones.

Philipp
la source
4
pourquoi a-t-il besoin d'un fil pour chaque prise unique? Surtout avec des sockets asynchrones? De nombreux serveurs lourds utilisent un modèle de "pool de threads" pour traiter les données de socket, où chaque thread dessert N clients et ils sont générés ou tués selon les besoins. Pensez simplement que cette réponse a besoin d'une amélioration: D
Grimshaw
@Grimshaw Je pense que vous avez mal compris ma réponse. Je pense avoir maintenant précisé qu'un modèle multi-thread serait nécessaire lors de l'utilisation de sockets de blocage.
Philipp
@Philipp Je n'ai pas dit quel langage de programmation j'utilise parce que je pensais que ce n'était pas vraiment pertinent pour la question (j'utilise c ++ d'ailleurs). Je vous remercie de votre recommandation d'utiliser une prise asynchrone. Connaissez-vous un livre / une page Web recommandé pour apprendre ces types de méthodes dans le jeu (moteurs)?
Yannick Lange
@Grimshaw: Et comment fonctionne chaque Nclient de service de thread ? Si vous bloquez et que vous voulez vous assurer que chaque socket obtient ses données traitées quel que soit l'état des autres sockets, vous devez avoir un thread par socket. Cela, ou n'utilisez pas de sockets de blocage.
Panda Pyjama
Mon commentaire portait sur la première version de votre réponse, en attendant, vous l'avez modifiée et corrigée, oubliez ce que j'ai dit: D
Grimshaw