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:
- Code de blocage multithread (ce que vous faites en ce moment)
- Prises non bloquantes avec notification déclenchée par le niveau
- Sockets non bloquants avec notification de changement de disponibilité
- 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
où
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 .