|
|
Présentation du site Cours Erlang Articles Projets Autour d'Erlang-fr Liens English Area Miroir: Documentation Erlang Miroir: Archives Erlang/OTP Recherche ![]()
|
ErlyStage: Erlang est-il adapté pour le développement d'un jeu de rôle massivement multi-joueurs ?par Thierry « Shaman » Mallard Traduction de l'article en Anglais Worldforge: Un projet de serveur de jeu en ligneWorldforge est un projet de développement d'un serveur de jeu de rôle en ligne, massivement multi-joueurs, c'est-à-dire visant à rassembler plusieurs milliers de joueurs dans un même univers. Les exigences nécessaires au développement d'un tel jeu sont importantes. Il faut être capable de gérer un grand de nombre de connexions réseau simultanées et capable de réagir aux événements entrants très rapidement. Il faut également pouvoir calculer les interactions entre les personnages et renvoyer le résultat à tous les clients. La principale caractéristique d'une telle plateforme est donc:
Ces deux éléments font qu'il faut, dès l'origine, prévoir la distribution du système. Stage et Erly-StageL'environnement de développement Erlang possède des caractéristiques intrinsèques qui permettent d'assurer cette distribution de manière plus aisée qu'avec la plupart des langages. Il offre également des possibilités qui seront très précieuses pour un tel serveur, comme par exemple la possibilité de mettre à jour le système sans interruption du service. Enfin, Erlang est utilisé par Ericsson dans des routeurs, dont l'objectif est de gérer un grand nombre de connexions et d'acheminer de l'information. A ce titre, l'analogie avec la gestion d'un grand nombre de connexion que devra gérer le serveur Worldforge est frappante. L'idée d'essayer d'utiliser Erlang pour développement un tel serveur est naturelle. J'ai donc décidé de tenter l'expérience. Au travers d'une série d'articles, je me propose de décrire l'approche adoptée pour ce développement. Concrètement, nous nous basons ici sur STAGE, l'un des serveurs envisagés pour le développement de Worldforge. STAGE a été concu originellement par Bryce Herrington, et prévu pour être développé en C++. Nous allons ici développer une version Erlang, judicieusement appelé Erly-Stage ;-) , tout en tachant :
Clairement, il s'agit bien ici de permettre aux programmes clients de se connecter indifféremment à un serveur STAGE ou ErlyStage. Cet article est l'occasion d'explorer la manière dont est architecturée Erly-Stage. L'architecture originale de STAGE revue en ErlangSTAGE est construit autour d'un ensemble d'agents et de boites à outils.
ThorCet agent fédère l'ensemble des modules applicatifs. Il est responsable du lancement et de la supervision de chacun d'entre eux. Dans ErlyStage, le code ressemble à ceci :
start( ThorNode, MercuryNode ) ->
?INFO("[Thor] starting on node ~p ~n", [ThorNode]),
muse:start(),
PegasusProcess = pegasus:start(ThorNode),
MercuryProcess = mercury:start(MercuryNode, ?PORT,
PegasusProcess),
shepherd:start(),
echo:start(),
io:format("~n[Thor] starting script finished ~n").
Il s'agit là d'une fonction Erlang très classique, je ne m'y attarde donc pas. Le seul point à observer est que chacun des agents est prévu pour être lancé sur un noeud Erlang différent. Nous anticipons la distribution applicative de ce programme dès le début. Muse est le serveur de media, que nous observerons dans un prochain article, tout comme Shepherd et Echo, qui sont respectivement responsables du mouvement des entités, et de la persistance -c'est à dire du stockage- de l'univers du jeu. Dans un premier temps, nous allons voir comment Mercury remplit son rôle de serveur réseau. MercuryCet agent a pour rôle d'accepter des connexions TCP, puis de gérer celles-ci. Éventuellement, il est tout en fait envisageable de déléguer cette gestion à un autre agent, ce que nous verrons plus tard. En Erlang, nous avons ici le code principal du processus actif remplissant cette gestion :
{ ok, SSocket } = gen_tcp:listen(
ServerPort,
[ list,
{ packet, 0 },
{ active, false },
{ nodelay, true },
{ keepalive, true},
{ reuseaddr, true} ] ),
io:format("[Mercury] Listening socket is ~p on node ~p~n",
[SSocket, node()]),
server_accept(SSocket, PegasusAgent ).
Nous déclarons ici être en attente de connexions TCP sur le port ServerPort. Lorsqu'une connexion se présente, nous acceptons celle-ci et lancons un processus dédié qui aura à sa charge le contrôle de cette socket ainsi créée.
server_accept( SSocket, PegasusAgent ) ->
{ ok, CSocket } = gen_tcp:accept( SSocket ),
io:format("[Mercury] new connection : ~p~n", [ CSocket ]),
Pid = spawn(node(), ?MODULE, connection_loop,
[CSocket, PegasusAgent]),
gen_tcp:controlling_process( CSocket, Pid ),
%% And we loop to accept other connections...
?MODULE:server_accept(SSocket, PegasusAgent ).
Remarquez bien que le gen_tcp:accept est le point bloquant. C'est à ce moment-là que ce processus est en réellement en attente d'une connexion. L'autre point à observer est que la boucle se fait en spécifiant le module (?MODULE:server_accept), afin de permettre un changement de code à chaud, utile pendant les tests et debuggage ultérieur. À ce point, nous avons donc un serveur TCP qui prend des connexions et lance des process gérant celles-ci. Ce processus va recevoir les données et les traiter :
case gen_tcp:recv( CSocket, 0 ) of
{ ok, Bs0 } ->
... traitement des données recues ...
... on boucle
{ error, closed } ->
... fin de la connexion
end.
Les données arrivent par blocs de taille indéfinie. Nous écrirons donc un dispatcher, qui aura pour rôle de formater ces données en une liste de chaines. Ensuite nous étudierons Atlas, le protocole chargé de l'échange de données entre le client et le serveur. Tout ceci fera l'objet du prochain article de cette série. Remerciements à Mickaël Rémond pour sa relecture :-) |
Pour tout commentaire: erlang@erlang-fr.org
Dernière modification: 2005-11-11 18:48:8