Salut,
Depuis quelques jours, je me suis lancé dans un petit projet fun sur mon temps libre. Un petit jeu en réseau. Pour la partie graphique et gestion des inputs, j'utilise Irrlicht. Ca me convient bien.
Je commence à me demander ce que je vais utiliser pour la partie réseau. Il s'agit d'un jeu 3v3, qui communique par l'intermédiaire d'un serveur. Le serveur doit vérifier les validités des actions et des mouvements pour éviter la triche, et informer tous les clients des modifications qu'il y a eu.
Le jeu se joue sur une map relativement petite, et le jeu est en temps réel.
Il est codé en C++, mais je peux envisager d'écrire la partie réseau en autre chose si nécessaire. (je suis même pas spécialement fan du C++, mais bon...).
Qu'est-ce que vous utiliseriez vous pour ce genre de taches ? Je pensais à utiliser des bêtes Sockets TCP en C++, mais c'est surement parce que je ne connais pas grand chose d'autre.
Je me demande même si tout devrait passer en TCP, ou si il y a des parties qui devraient utiliser UDP pour les perfs... Hum quoi que non, ça semble pas raisonnable, j'ai besoin des garanties offertes par TCP...
Des idées là dessus ? Mes connaissance en prog réseau sont plutôt limitées. (j'ai jamais rien codé de plus que ce que j'avais du faire à la fac, ça m’intéressais pas particulièrement comme domaine).
Merci !
Pour réduire le temps de développement et amélioré la maintenabilité du code source j'aurais utilisé deux languages ![]()
Déjà, le serveur je l'aurais fais entièrement en C++.
Toute les GUI en C++ également
Et le jeu en lui même avec un autre langage, le Lua
bref, toi tu le fais en C++ dommage.
J'aurais fais le réseau avec le Lua, il y a une bibliothèque dédier à ça nommé lua socket qui est extrèmement facile à utiliser, la partie réseau sera donc extrèmement rapide à faire. ![]()
Avec le C++ tu peux prendre 50 lignes minimum pour faire un serveur TCP, meme avec une librairie. Avec le lua t'en prend quoi, 20, 10 lignes ![]()
J'utiliserais également des sockets ![]()
Après, tkt les TCP est un protocol fiable mais si les joueurs joue sur un réseau qui n'est pas un LAN alors là le jeu laguera, en raison du temps que prend le réseau, et les clients qui ont une vieille co lagueront...Faudrait envisager l'UDP. Même si il est compliqué à mettre en place ça en vaut le coup pour ses performances ![]()
Concernant LUA, je connais pas trop. A voir. J'ai rien contre à priori.
Pourquoi est-ce que tu penses que des sockets TCP feront inévitablement lagguer ? C'est trop violent pour un jeu en temps réel ?
Le truc qui m'ennuie avec l'UDP, c'est que à parti si je re programme moi meme les garanties dont j'ai besoin (ce qui ne sera pas nécessairement plus efficace), je n'aurais pas de garantie sur l'ordre d'arrivée des messages, sur la potentielle corruptions de données par le réseau, sur le fait qu'une donnée a bien été reçue, etc...
J'ai quand meme vraiment l'impression que l'UDP est plut adapté aux choses comme le streaming, ou tu t'en fout que 2 pixels changent de couleur de temps en temps.
Pour un jeu précis, j'ai quand même l'impression que TCP est nécessaire pour les garanties qu'il offre. Je me trompe ?
Pour la partie réseau, vous savez ce qu'utilise les jeux commerciaux (disons, Starcraft II)? Un mélange d'UDP et de TCP suivant les morceaux ?
le jeu est en temps réel, il échangera en permanence des données avec le serveurs, ça fait une grande quantité de donnée à traiter.
Pense au client qui aura une co pas térrible et qui voudra jouer ton jeu ![]()
UDP spécialement fait, pour traiter de grande quantité de donnée et avec un peu d'ingéniosité, on peut en faire un protocol sûre.
Starcraft utilise le TCP et le UDP ![]()
Ok, mais ce qui m'intéresse, c'est quelles parties écrire en TCP, et quelles parties écrire en UDP.
Concernant la grande quantité de données, je sais pas...
Imaginons que je veux au moins 10 refreshs de la situation par seconde, si un refresh demande d'envoyer et de recevoir 10ko (ça me semble super large), ça veut dire que la connexion du client doit supporter au moins :
Dowload : 100 ko par seconde
Upload : 100ko par seconde
latence : 100ms pour avoir mes 10 refreshs par seconde
Ca vous semble clairement trop demander ?
salegauss, je pense que ca depend beaucoup du jeu. En particulier de si tu peux te permettre de perdre des paquets ou pas. Si tu ne peux pas perdre de paquet, ca veut dire que tu va te retrouver a implementer de la correction d'erreur au dessus d'udp. Et c'est difficile de faire ca en gardant de meilleur perf que tcp. (pas impossible, mais difficile.)
Donc mon approche serait de commencer par tcp et de voir apres.
10 refresh par seconde ca me semble tout a fait possible avec TCP. ca depend aussi de quelles garantie tu veux sur tes inputs. Si tu veux faire ton jeu pour que les inputs du refresh x soit prise en compte exactement a x+1 ou si ca te va de les prendre en compte a x+2 ou plus tard. et si tu veux avoir les info du refresh de x avant d'envoyer les input de x. Parceque ca, moralement va couper ton besoin de latence en 2. Et 50ms ca pourrait devenir chaud a longue distance. (un aller retour cote est/cote ouest prends 85ms.)
Merci pour les réponses.
Concernant la question "est-ce que je peux me permettre de perdre des paquets", j'ai du mal à y répondre. Sans trop creuser, j'aurais envie de dire non. Je suis à peu près sur que si je prends de l'UDP, je vais devoir recoder moi même certaines des garanties de TCP. Peut être pas toutes, mais j'ai bien peur de devoir en refaire une certaine partie. Au minimum la détection et correction d'erreur comme tu l'as dit Godrik.
Et puis même, en général, le fait de ne pas avoir d'ack me semble problématique. Je sens que je vais finir par ré-implémenter TCP. Et n'étant pas un fin connaisseur du domaine, j'ai pas la prétention que mon implémentation sera plus efficace que ce qui existe déjà. Ok, je pourrai peut-être arriver à un truc plus spécifique pour les besoins exacts de mon application, et donc utiliser des propriétés de mon appli, mais ça me semble risqué, source d'erreurs, et sans réelle garantie que ce que j'aurais à la fin sera effectivement plus rapide.
Et surtout, que ça marchera bien. Tout le temps...
Si je pars du principe que ce qui est envoyé arrivera bien , sans erreur, dans le bonne ordre, avec des acks, j'ai l'impression (peut-être naïve) que ça me simplifie un peu le problème.
Quant à savoir quelle est ma tolérance quant à avoir des actions qui ne sont prises en compte que plus tard... Hum, pas évident.
Au départ, j'ai failli écrire "ça me va, tant que les actions sont bien prises en compte dans le même ordre que ce qu'elles ont été réalisées par les différents clients". Mais ça signifierait que si un client lag, on fait lagguer tout le monde (genre ralentissement de SC2).
Étant donné que je veux les décisions du serveur soit authoritative, est-ce que ça serait raisonnable d'avoir chaque client X qui récupère les inputs du joueur X, qui recalcule le monde à partir de ça (juste ses actions), qu'il envoie au serveur ce qu'il vient de faire, puis qu'il reçoive en retour du serveur les informations (éventuellement corrigées) du monde actuel ?
Quelque chose comme :
boucle de jeu pour le client X
{
Client reçoit actions clavier/souris
Client calcule les nouvelles positions, etc à partir de ces actions (locales)
Client envoie nouvelles positions/actions effectuées au serveur
Client affiche le résultat sur l'écran (se base sur les infos disponibles localement)
Client reçoit du serveur les nouvelles positions/actions (éventuellement corrigées par le serveur)
Le client ée-affiche le nouveau résultat corrigé
}
Je me demande même si je ne pourrai pas paralléliser tout ça de manière sympa. Un thread qui s'occuperait d'envoyer les actions que le joueur courant vient de faire, un thread qui s'occupe de recevoir du serveur les données "validées" les plus fraiches, et un thread qui recalcule/affiche sur l'écran. Je les laisse tous tourner aussi vite que possible, en lisant toujours les données les plus récentes produites par le thread de reception des infos serveur.
Je sens que je vais galérer :D
Si tu débutes tu peux faire tes sockets a la main.
Sinon perso j'utilise la lib Boost ASIO pour la gestion des sockets c'est pas mal mais ça demande un peu plus de travail pour le mettre en place.
Après pour la gestion des packets ya des lib comme protobuf (qui est pas mal).
Mais tu peux très bien le faire a la main. ![]()
La librairie Qt mets à disposition un module pour le réseau ![]()
Je me rends compte que le pseudo code que j'avais écrit avant de parler de parallélisme n'avait aucun sens, car du coup le nombre de refresh que je fais par le réseau était égal au nombre d'itérations de ma boucle de jeu. C'est pas ce que je veux dans l'absolu. SI le mec fait tourner le jeu à 150 ips, je veux pas que ça implique 150 envoies/receptions.
La solution avec différents threads me semble intéressante. Mon thread "Calcul/Affichage" se base sur les inputs clavier/souris qu'il récupère, et sur les données les plus récentes obtenues par le thread "Reception". Il en profite aussi pour envoyer au thread d'"Envoie" une query de mise à jour pour le serveur.
- Le thread Reception reçoit (disons #NB_REFRESH_NETWORK_SEC fois par seconde) une update complète du jeu que le joueur devrait connaitre et les tiens à dispositions dans uns "stack de refreshs" que le thread de Calcul/affichage peut consulter pour faire son recalcule/réaffichage.
- Le thread d'Envoie reçoit des requêtes de "s'il te plais, envoie ça au serveur" envoyées par le thread de Calcul, et envoie de lui même toutes les (1/NB_REFRESH_NETWORK_SEC) sec les actions qui n'ont pas été envoyées depuis le dernier envoie. Ça marche comme un buffer d'updates à envoyer en définitive.
Je peux envisager de laisser la possibilité de configurer ce paramètre de nombre de refresh serveur par le client de jeu, en fonction de la qualité de sa connexion (bien sur, avec une limite maximale, pour ne pas juste flooder le serveur). On peut aussi imaginer qu'il essayer de trouver une bonne valeur tout seul. Par exemple, il essaye avec 10, et il augmente un peu plus tant que ça marche bien.
Je pense que je vais partir sur un truc comme ça (j'écris ça au fil du vent, sans y avoir pensé avant). Je vais voir si j'arrive à en faire quelque chose.
Sinon regarde du coté de boost ASIO avec la gestion asynchrone des sockets ![]()
http://www.boost.org/doc/libs/1_57_0/doc/html/boost_asio/examples/cpp11_examples.html
Dans un jeu comme quake, voila essentiellement ce qu'il se passe.
Periodiquement le serveur envoye l'etat de tout le jeu (ou de tout le jeu observable) au client. a ce moment la, le client met a jour son etat interne pour prendre en compte les dernieres nouvelles.
Le client va extrapoler la position de tous les objets en fonction de leur vitesse (inclu dans les info envoye par le serveur)
Les informations envoye par le serveur ont un timestamp qui sert a corriger les horloges, a extrapoler correctement, et a ignorer tous les messages qui n'arrivent pas dans l'ordre.
Le client envoye periodiquement les position des boutons du client au serveur et suppose dans son extrapolation locale que les changements sont prise en compte en temps reel.
Le serveur prends en compte a chaque rafraichissement seulement les boutons qu'il a recu le plus recement.
C'est pour ca que dans quake, quand ca laggue tu as des personnage qui se teleportent. C'est par ce que la difference entre l'extrapolation du client et la realite du serveur est tres differente.
En passant, un post de blog sur la synchro de client dans un RTS: http://www.codeofhonor.com/blog/the-making-of-warcraft-part-3
Merci pour les remarques. Au final, ça commence à prendre forme sur le papier et dans ma tête. Quand j'aurai le design un peu plus précis de la partie réseau, je reviendrai en discuter ici, particulièrementt si j'ai des dilemmes.
@ Godrik : Pour l'instant, ça te semble raisonnable d'utiliser du full TCP pour ça ? J'ai remarqué que pas mal de jeux utilisent du TCP et de l'UDP, mais sans savoir pour quels morceaux ils utilisent l'UDP. Dans mon jeu, j'arrive pas à identifier des fonctions qui ne nécessiteraient pas les garanties de detection/correction d'erreurs, de resending, d'acks, d'arrivée dans le bon ordre...
Est-ce que ça te semble fou furieux de partir sur un full TCP pour un jeu où le volume de donnée est sensé être relativement faible (mais temps réel) ? Je ne me rends pas bien compte, n'étant absolument pas dans le domaine.
Mais pour l'instant, je comptes partir sur quelque chose comme ça. J'espère arriver à programmer un prototype assez rapidement.
Merci à tous.
Ah oui, evite les rafraichissements, privilège l'asynchrone ![]()
Pour des raisons de performances et de praticité ![]()
Je commencerais par TCP.
Utilise du full tcp pour commencer ![]()
Ok, je vais partir sur du full TCP, et je verrai plus tard si c'est problématique.
Merci !
@Rangerprice : tu peux être plus précis stp ?
Concernant le mix de tcp et de udp, j'avais lu à plusieurs endroit que ca pouvait poser plusieurs problèmes.
Sinon, y a aussi la solution d'utiliser udp avec une bibliothèque qui implémante certaines fonctionnalités de tcp pour les messages qui en ont besoin.
Après pour le choix de tcp/udp ca dépend surtout des contraintes en temps réel et de la fiabilité nécessaire pour certaines données.
Par exemple : pour l'actualisation de la position d'un personnage, imaginons que tu utilise tcp et que tu perde un packet, il faudra que tu attende que le client se rende compte qu'il ne reçoit pas d'ack pour qu'il puisse ensuite renvoyer le packet avant de pouvoir actualiser la position et ceux même si entre temps les packets contenant les positions suivant sont arrivés. Alors qu'avec udp, si un packet contenant la position n'arrive pas à destination, tu attend juste le prochain pour actualiser la position (il faudra juste faire attention à ignorer les packets qui arrivent en retard).
Par contre, si tu as absolument besoin de recevoir toute tes données et dans l'ordre, là effectivement il vaut mieux utiliser tcp.
J'ai remarqué que dans certains jeux le TCP est utilisé pour gérer la partie tchat.
Certes, pas tous, et ça dépend comment le jeu est fait oui.
Hum... J'avoue que dans le cas d'un paquet qui s'est perdu, ça fait chier d'au final attendre pour recevoir une information qui sera trop ancienne.
Du coup je me dis que :
1) Le client envoie au serveur les actions réalisées en TCP, parce que je veux que les actions arrivent dans le bon ordre, et je ne veux pas que certaines soient zapées.
2) Mais le serveur peut envoyer à tous les clients la situation complete (ou visible pour eux) en UDP. Si ca n'arrive pas, tant pis, on attend la prochaine frame...
Ca serait pas mal, parce que les actions envoyées du client au serveur sont plutot légeres, donc TCP ne devrait pas trop poser problème ici.
Par contre, les "états" du jeux qui transitent du Serveur vers les Clients seront plus gros, et ça serait pas mal d'avoir de l'UDP pour ça. Particulièrement parce qu'on se fout de traiter un état de jeu trop ancien.
(bon, ok, on pourrait ne pas envoyer les états complets, mais juste ce qui a changé depuis la dernière notification, et du coup utiliser du TCP, mais ça me semble plus casse guelle de calculer ces différences plutot que d'envoyer l'état du jeu complet/visible)
Par contre, il va quand meme me falloir une detection/correction d'erreur pour 2). Je ne veux pas recevoir l'info que le joueur X est mort si ce n'est pas le cas, par ce que sinon ça va devenir incompréhensible si 1 seconde après il est toujours vivant car ce n'était qu'un bit qui a changé de valeur pendant le transfert....
J'avais implémenté de la détection d'erreur à la main y'a longtemps, mais il doit y avoir des libs pour ça j'imagine.
Notch, tu sais quelles librairies utilisent de l'UDP mais proposent certaines garanties "à la demande" stp ? (dont le fait que les données doivent arriver intact, pas corrompues).
Merci à tous !