Rapport d’outils Raknet








télécharger 76.47 Kb.
titreRapport d’outils Raknet
date de publication30.06.2017
taille76.47 Kb.
typeRapport
ar.21-bal.com > loi > Rapport


8 Mai 2009


INF 793 – Projet d’intégration en jeu vidéo

Loïc Devaux



Campus Ubisoft

Rapport d’outils - Raknet





Sommaire

Introduction 3

Architecture 4

Pair à Pair 4

Client / Serveur 5

Mise en contexte 5

Utilisation de Raknet 5

Code serveur 6

Code client 10

Astuces 12

Conclusion 13

Introduction


Dans le cadre du cours INF 793 – projet d’intégration en jeu vidéo, nous devions réaliser un jeu multi-joueurs. Le cote réseau du jeu devait être développé avec « Raknet », ce document est donc une explication de certains des principes de « Raknet » ainsi que de ses concepts fondamentaux et comment nous les avons exploités afin de créer notre jeu.

« Raknet » est une bibliothèque réseau multiplateforme développée en C++. Elle permet d’offrir plusieurs fonctionnalités pratiques telles que :

  • Connexions sécurisées

  • Compression réseau

  • Méthodes pour faire du RPC (Remote Procedure Call)

  • Etc.



Architecture


L’architecture d’un jeu multi-joueurs dépend principalement de la manière dont les programmeurs veulent que le jeu fonctionne. Par exemple, si le jeu doit être contrôlé par une seule machine qui prend le rôle de « maitre de jeu », si l’on ne souhaite pas que les joueurs puissent tricher, etc.

Pair à Pair


L’architecture P2P permet à chaque client de connaitre ceux qui l’entourent. Lorsqu’un message est envoyé, il l’envoie à chacun des autres clients. Il existe deux sortes de P2P, le P2P asynchrone et le P2P synchrone.

Dans le cas du P2P asynchrone, chaque client envoie ses messages aux autres clients sans que ceux-ci soient vérifiés. Le principal avantage de cette architecture est sa rapidité étant donné qu’aucune vérification ne s’effectue, mais pas contre cela ouvre facilement la porte à la triche. Un client malicieux peut très bien envoyer des messages erronés aux autres clients afin de prendre le dessus dans une partie.

Le P2P synchrone limite les possibilités de triche étant donné que les messages reçus sont vérifiés par chacun des clients. Plutôt que d’envoyer directement les résultats d’une action, c’est l’action elle même qui est envoyée aux autres clients. Cela permet donc à chacun des clients de traiter l’action et de vérifier sa validité. Ce mode implique par contre que chacun des joueurs doivent se connecter à une partie en même temps afin d’être toujours synchronisé.

Client / Serveur


L’architecture client/serveur permet de centraliser les données et les opérations à un seul et même endroit. Les clients ne possèdent plus qu’une seule connexion et celle-ci se trouve être celle avec le serveur. Lorsqu’un client effectue une action, il l’envoie au serveur qui lui, la traite (vérification), puis l’envoie à chacun des autres clients afin de les mettre à jour.

L’avantage de ce modèle est principalement la sécurité. Etant donné que le serveur s’occupe de toutes les vérifications, il n’y a peu de chances que des joueurs puissent tricher. Les seuls moyens pour tricher restent d’exploiter les données stockées sur le client. Par exemple, si le client stocke la position, vie, … de chacun des autres joueurs, il est possible de créer un « hack » permettant de voir la vie des joueurs, rendre les décors semi-transparents afin de voir les joueurs de loin suivant leur position, etc.

Par contre, le principal inconvénient de cette architecture est sa « lenteur ». Du fait que chacun des messages doit passer par le serveur pour être validé puis ensuite renvoyé à chacun des joueurs, cela implique une perte de temps par rapport à un modèle comme le P2P.

Mise en contexte


Le jeu que nous avons réalisé est un peu le croisement entre un « gameplay » de MMORPG et de CTF (Capture the flag) pouvant être joué par plus d’une dizaine de joueurs en même temps. Pour un jeu de ce type nous avons préféré utiliser une architecture client/serveur afin de réduire les risques de triche. Afin de réduire le trafic sur le réseau, nous avons décidé de faire des pré-vérifications des actions sur chacun des clients afin de ne pas envoyer de messages inutiles au serveur. Nous envoyons juste les actions de chacun des joueurs au serveur (avancer, frapper, …) et celui-ci s’occupe de les valider pour les renvoyer à tout le monde, même au client qui a effectué l’action. Par exemple, si un client souhaite avancer, il ne peut le faire qu’une fois que le serveur a décidé que cela est possible.

Utilisation de Raknet


Je vais décrire en partie comment réaliser la communication entre plusieurs clients et un serveur. Le code qui va suivre n’est pas quelque chose de fixe, il est toujours possible d’y apporter des améliorations. C’est en partie le code de base pour la communication réseau dans notre jeu avec quelques légères modifications pour les besoins de ce document. Nous nous sommes inspiré des différents tutoriaux de « Raknet » qu’il est possible de trouver sur le site officiel http://www.jenkinssoftware.com/, puis nous les avons modifié pour nos besoins.

Code serveur


Notre serveur est un « singleton » étant donné qu’il ne peut exister qu’une seule fois :

class Server : public Sally::Singleton

{

friend Sally::Singleton;
typedef void (Server::*pt2Function)(const Packet*);
RakPeerInterface* mServer;
static const int mskMAX_CLIENTS = 20;

static const int mskPORT = 60000;
map mCharacters;
map mCommandMap;
void createCommandMapping();
Server();
void helloServer(const Packet* packet);

/*Suite du code, pas pertinent pour le document…*/

};

Cette classe nous permet de définir différentes structures de données ainsi que des variables pour la création de notre serveur. Par exemple, le nombre de clients maximum pouvant se connecter, le port du serveur (dans notre cas le port était fixe, mais il est possible de le décider à chaque lancement du serveur), etc.

Le serveur possède une fonction se nommant « helloServer », qui sera la méthode que nous tenterons d’appeler avec les clients. J’expliquerai par la suite pourquoi nous faisons une « map » de pointeurs de fonction.

Voici le code du constructeur de notre serveur :

Server::Server()

: mServer(0)

{

char ip[512];
cout << "Entrez l'IP du serveur ou Enter pour 127.0.0.1" << endl;
gets(ip);

if (ip[0] == 0)

{

strcpy(ip, "127.0.0.1");

}
mServer = RakNetworkFactory::GetRakPeerInterface();
mServer->Startup( Server::mskMAX_CLIENTS, 10, &SocketDescriptor(

Server::mskPORT, ip ), 1 );
mServer->SetMaximumIncomingConnections( Server::mskMAX_CLIENTS );
createCommandMapping();

cout << "Le serveur a demarre. Port " << Server::mskPORT << ", IP " <<

ip << endl;
}

Nous permettons à chaque lancement de serveur de donner le choix de l’IP du serveur. Cela est très pratique lorsqu’il faut lancer le serveur sur différentes machines. Il est possible d’en faire de même pour le port. Nous initialisons l’interface « Raknet », puis démarrons le serveur.

Ensuite nous utilisons une méthode pour traiter les paquets reçus par le serveur. Cette méthode est appelée à chaque tour de boucle du serveur :

void Server::receive()

{

Packet* packet = mServer->Receive();

while(packet)

{

processPacketData( packet );

mServer->DeallocatePacket(packet);

packet = mServer->Receive();

}

}

Quand un paquet est reçu, nous le traitons à l’aide de la méthode suivante :

void Server::processPacketData( const Packet* packet )

{

pt2Function function = mCommandMap[static_cast(

packet->data[0])];
if (function)

{

(this->*function)(packet);

}

}

C’est à ce moment que notre « map » de pointeurs de fonction rentre en compte. Il faut récupérer la fonction associée au bon index de la « map » en fonction de l’entête du paquet reçu. La plupart des exemples/tutoriaux de « Raknet » utilisent un « switch » afin de traiter les paquets reçus en fonction de leur entête. Cela n’est pas une très bonne idée, car si beaucoup de paquets différents peuvent être reçus, il est beaucoup plus facile/maintenable/rapide de passer par une « map ».

La « map » est remplie de la manière suivante :

mCommandMap[ID_HELLO_SERVER] = &Server::helloServer;

« Raknet » possède ses propres « ID » en fonction de ce que font les clients ou le serveur. Les « ID » de « Raknet » se trouvent dans le fichier « messageidentifiers.h ». Il faut donc bien faire attention de ne pas créer d’identifiant étant déjà dans « Raknet ». « Raknet » a été pensé afin de pouvoir rajouter nos propres « ID », pour cela rien de plus simple :

enum Commands

{

ID_HELLO_SERVER = ID_USER_PACKET_ENUM,

/* Autres messages possibles */

};

La valeur « ID_USER_PACKET_ENUM » est un identifiant de « Raknet » servant aux utilisateurs pour continuer l’énumération des identifiants possibles des paquets. Tous les identifiants se trouvant avant celui-ci sont des identifiants réservés pour « Raknet ».

Ensuite il ne manque plus qu’à faire la méthode « helloServer » qui sera appelée par le client :

void Server::helloServer(const Packet* packet)

{

std::cout << "Hello !" << std::endl;

}

Pour finir avec le serveur, il ne faut surtout pas oublier le destructeur (très important !!) :

Server::~Server() throw()

{

mServer->Shutdown( 10 );

RakNetworkFactory::DestroyRakPeerInterface( mServer );

...

}

Il permet de couper et de détruire l’interface « Raknet ».

Code client


Le code client est sensiblement identique à celui sur le serveur. C’est aussi un singleton avec certaines méthodes, la même « map » avec des pointeurs de fonction, juste les fonctions ne sont pas les mêmes car elles dépendent des messages que peut envoyer le serveur aux différents clients.

Voici le constructeur du client :

Client::Client()

: mClient(0)

{

mClient = RakNetworkFactory::GetRakPeerInterface();

SocketDescriptor socketDesc(0, "");

mClient->Startup(1, 30, &socketDesc, 1);

mClient->SetOccasionalPing(true);
createCommandMapping();

}

Le constructeur de notre client permet juste d’initialiser l’interface « Raknet », car nous laissons au joueur le choix de l’IP du serveur dans le menu principal de notre jeu, donc la connexion se fait plus tard. La connexion se fait de la manière suivante :

void Client::connect(const std::string& address, unsigned short port)

{

mClient->Connect(address.c_str(), port, 0, 0);
/*Suite du code...*/

}

Le client possède les mêmes méthodes que sur le serveur pour recevoir et traiter des paquets, c'est-à-dire les méthodes « processPacketData » et « receive », le code est exactement le même que sur le serveur.

Il faut aussi penser à faire le destructeur du client (très important une fois de plus !) :

Client::~Client()

{

mClient->Shutdown(50, 0);

RakNetworkFactory::DestroyRakPeerInterface(mClient);

}

Ensuite il ne reste plus qu’à coder la méthode permettant d’envoyer le message au serveur (sur appui d’une touche par exemple) :

void Client::sendHello ()

{

RakNet::BitStream bs;
bs.Write(static_cast(ID_HELLO_SERVER));

mClient->Send(&bs, HIGH_PRIORITY, RELIABLE, 0, mServerAddress, false);

}

Quand le serveur recevra le paquet du client, il verra que le premier « byte » correspond à l’identifiant « ID_HELLO_SERVER », il ira donc chercher le pointeur de fonction dans la « map » et appellera la fonction.

Astuces


Voici quelques petites astuces qui peuvent être très pratique pour différentes raisons lors du développement du réseau pour un jeu.

float Client::getPing()

{

return mClient->GetAveragePing(mServerAddress);

}

Cette méthode permet de récupérer le « ping » entre le client et le serveur, cela permet de pouvoir interpoler les mouvements des personnages si tout passe par le serveur. Par exemple, si un client à commencer à se déplacer il y a 100ms, en connaissant le « ping », il est possible de faire démarrer son déplacement avec 100ms de plus afin qu’il soit la ou il doit être.

Quelque chose de très pratique aussi qui n’est pas expliqué dans les différents tutoriaux de « Raknet » est la méthode « RakSleep ». Au début, notre serveur envoyait des paquets à chacun des clients à chaque tour de boucle, un tour de boucle était si rapide à faire que les paquets s’accumulaient sur le serveur celui-ci étant incapable de tous les envoyer. Les paquets s’accumulaient et la mémoire prise par le serveur augmentait jusqu'à ce qu’il plante. La solution à ça est la méthode suivante :

RakSleep(10);

Cette méthode permet de faire un « sleep » et pendant ce temps tous les paquets sont envoyés. Donc dans le cas ou le serveur a un « memory leak » énorme pour une raison inconnue, cette méthode peut résoudre le problème.

Le site officiel http://www.jenkinssoftware.com/ possède tous ce qu’il faut pour bien démarrer, et le forum est aussi assez fourni. Il faut bien se tenir au courant des différentes versions car des fonctionnalités s’ajoutent mais des fois des bugs s’ajoutent avec. Je conseille de commencer par coder un petit « chat » avec plusieurs clients et un serveur pour apprendre le fonctionnement de « Raknet », car une fois ceci réalisé, il est plus facile de penser à l’architecture pouvant s’adapter à un jeu vidéo.

Conclusion


« Raknet » permet de proposer tous ce qu’il faut pour avoir un bon code réseau pour un jeu vidéo. Que ce soit des « sockets » jusqu'à la création d’un lobby avec une base de données, …

Il faut surtout bien penser à comment sera l’architecture du jeu avant de se lancer la dedans. Dans notre cas, nous avons d’abord commencé à développer notre client pendant quelques semaines avant d’attaquer le code réseau. A partir de notre architecture de base, nous avons réussi à adapter notre code afin que cela fonctionne parfaitement.

Il faut aussi bien faire attention à ce que propose « Raknet » et dans quels contextes ses fonctionnalités peuvent être utilisées. Par exemple, nous avons préféré développer entièrement le côté réseau de notre jeu en limitant « Raknet » au strict minimum (socket et envoi de message). Cela nous a permis de faire un code s’adaptant au jeu et à choisir nous même la taille des paquets, la fréquence d’envoi, leurs contenus, … cela a pris plus de temps à faire mais s’est trouvé au final être plus flexible et performant que si nous avions utilisé des fonctionnalités comme le « Replica Manager » qu’offre « Raknet ».


similaire:

Rapport d’outils Raknet iconRapport d’outil RakNet
«le joueur y perd 65,535 points de vie» même si le joueur se trouve hors d’atteinte. Dans le modèle d’architecture de Diablo, les...

Rapport d’outils Raknet iconBts se r V ice s I n f or m a t I qu e s a u X o r g a n I s at I on s
Dans tous les cas, les candidats doivent se munir des outils et ressources techniques nécessaires au déroulement de l’épreuve. Ils...

Rapport d’outils Raknet iconLes outils de développement de Telelogic mettent l’industrie automobile sur la voie du succès
«Les outils Tau de Telelogic ont déjà fait leurs preuves dans le secteur des télécommunications où le zéro défaut est une devenue...

Rapport d’outils Raknet iconActeurs et outils de l’aménagement et de l’urbanisme

Rapport d’outils Raknet iconConcepts, actions et outils linguistiques

Rapport d’outils Raknet iconVente à 11h00 – Outils de jardin de collection

Rapport d’outils Raknet iconNotes et outils d'archéologie sous-marine

Rapport d’outils Raknet iconAtelier de génie logiciel (case tools), outils de modélisation

Rapport d’outils Raknet iconPremier editeur commercial majeur d’outils de modelisation a passer a l’open source

Rapport d’outils Raknet iconCourses 1000 X 500 X 400 avec diviseur numérique magasin 30 outils centre d’usinage








Tous droits réservés. Copyright © 2016
contacts
ar.21-bal.com