1 IntroductionLe but de cette étape est de terminer le projet en écrivant les programmes principaux correspondant au client et au serveur, communiquant entre eux via le réseau. Show
1.1 Client et serveurComme cela a été expliqué dans l'introduction au projet, la mise en œuvre du jeu XBlast est séparée en deux programmes distincts :
Dans une partie comportant n joueurs, un total de 1 + n programmes s'exécutent : un serveur, et n clients. Chaque joueur humain dispose d'un ordinateur sur lequel il exécute une copie du client, tandis que le serveur s'exécute soit sur un des ordinateurs des joueurs, soit sur un ordinateur séparé. 1.2 Protocole client/serveurLa communication entre le serveur et les clients est découpée en deux phases distinctes :
Ces deux phases de communication et les différents messages échangés par un serveur s et deux clients c1 et c2 au début d'une partie sont illustrés dans la figure 1. Le temps s'y écoule de gauche à droite, et les messages échangés sont représentés par des flèches allant de l'expéditeur du message à son destinataire. Les types de messages sont identifiés par les couleurs des flèches et les étiquettes attachées, qui ont la signification suivante :
Figure 1 : Messages échangés entre un serveur et deux clients Tous les messages transmis par les clients au serveur ne contiennent qu'un octet, qui spécifie l'action que le client désire effectuer. Cet octet n'est rien d'autre que l'index de l'action en question dans l'énumération Les messages transmis par le serveur sont constitués de :
1.3 Communication via InternetLa communication entre le serveur et les clients se fait via Internet. Les différents appareils connectés à Internet — généralement appelée hôtes (host) — communiquent entre eux au moyen d'un grand nombre de protocoles distincts, dont la description complète sort du cadre de ce cours. Néanmoins, une rapide introduction est nécessaire et les personnes intéressées par plus de détails peuvent suivre les liens vers Wikipedia inclus ci-dessous, et/ou lire le livre Java Network Programming, très complet et disponible en ligne. Les protocoles d'Internet sont organisés en différentes couches, correspondant à différents niveaux d'abstraction. Les protocoles d'une couche donnée sont toujours basés sur ceux de la couche juste en-dessous. Trois de ces couches et plusieurs protocoles importants de chacune d'entre elles sont illustrées sur la figure 2. Figure 2 : Organisation en couches de quelques protocoles d'Internet Comme nous le verrons plus loin, le protocole du jeu, désigné par XBlast dans cette figure, est basé sur le protocole UDP de la couche inférieure. Beaucoup de protocoles connus — p.ex. HTTP qui est le protocole du Web, ou SMTP et IMAP qui sont liés au courrier électronique — sont quant à eux basés sur un autre protocole de la couche inférieure, TCP. 1.3.1 IPA la couche la plus basse qui nous intéresse ici se trouve le protocole de base d'Internet, nommé Internet Protocol et généralement abrégé IP. Ce protocole permet à deux hôtes connectés à Internet de s'échanger des messages, appelés paquets (packets), d'une taille maximale fixe. Aucune garantie n'est offerte quant à cette transmission, et il est donc possible que les paquets soient corrompus, perdus ou arrivent dans le désordre. Bien entendu, l'échange de tels messages suppose l'existence d'un mécanisme permettant de désigner individuellement chacun des hôtes connecté au réseau, au même titre que l'expédition de lettres et paquets physiques suppose l'existance d'un mécanisme permettant de désigner une boîte aux lettres, l'adresse postale. Dans le cas d'Internet, ce rôle est joué par ce que l'on nomme l'adresse IP (IP address). A l'origine,
une adresse IP était un entier de 32 bits, généralement représenté par les quatre octets le composant, en base 10, séparés par un point. Par exemple, Ces dernières années, au vu de la rapide augmentation du nombre d'hôtes connectés à Internet, il a été nécessaire d'augmenter la taille des adresses, 32 bits ne suffisant plus. Un nouveau format d'adresses, un entier de 128 bits, a donc été introduit et est appelé adresse IPv6. Ces adresses sont généralement représentées par huit groupes représentant chacun 16 bits, en base 16 et séparés par un deux-points. Par exemple, Etant donné qu'il
n'est pas facile pour les êtres humains de se souvenir d'adresses IP (v4 et encore moins v6) numériques, Internet est doté d'un service de noms nommé DNS (Domain Name System) et permettant d'utiliser des noms alphanumériques au lieu d'adresses numériques. Par exemple, le nom Le protocole IP n'est presque jamais utilisé directement par des applications, mais sert de base à d'autres protocoles de plus haut niveau. Les deux principaux protocoles construits directement sur IP sont UDP et TCP. La différence principale entre UDP et TCP est que UDP ne garantit pas que les données envoyées par l'expéditeur soient effectivement reçues par le destinataire, tandis que TCP offre cette garantie. 1.3.2 UDPUDP (User Datagram Protocol) n'ajoute que très peu de choses au protocole IP, sur lequel il est basé. En particulier, tout comme IP, il ne donne aucune garantie quant à la transmission des paquets, qui peuvent être corrompus, perdus ou réordonnés lors du transfert. Malgré cette absence de garanties, UDP sert de base à la plupart des protocoles pour lesquels la perte de données n'est pas fatale mais les performances sont importantes. C'est le cas par exemple des protocoles utilisés pour la téléphonie (p.ex. celui de Skype), la vidéoconférence et la plupart des jeux. Dans toutes ces applications, la perte de quelques données n'est pas très importante — l'humain auxquelles elles sont destinées étant généralement capable de compenser leur absence — mais il est par contre important que le délai entre l'envoi et la réception des données soit aussi petit que possible. Dans de telles situations, UDP est un meilleur choix que TCP, car lorsque le réseau est congestionné, TCP peut retarder considérablement la transmission de certaines données afin de garantir qu'elles soient toutes transmises. UDP, tout comme TCP, augmente la notion d'adresse fournie par IP en lui ajoutant un numéro de port, un entier 16 bits non signé — compris donc entre 0 et 65'535. Ce numéro de port permet à plusieurs applications de se partager une même adresse IP pour peu qu'elles utilisent un numéro de port différent, un peu comme différentes personnes peuvent se partager une même adresse de bâtiment pour peu qu'une information additionnelle (p.ex. un numéro d'appartement) permette de les distinguer. Les ports dont la valeur est inférieure à 1'024 sont réservés à des protocoles précis, et ne doivent pas être utilisées par d'autres applications. Par exemple, le port 25 du protocole TCP est réservé au protocole SMTP, qui sert à l'envoi de courriels. 1.3.3 Protocole XBlastLe protocole du jeu XBlast se base sur UDP. Etant donné qu'un message du serveur vers le client contient toujours la totalité de l'état du jeu, la perte de l'un d'entre eux n'est pas dramatique et se traduit par une saccade de l'affichage, nettement préférable à un ralentissement de la transmission, qui se traduit par un délai rendant rapidement le jeu injouable. Cela dit, en pratique, lorsqu'une partie a lieu entre plusieurs joueurs qui sont physiquement proches les uns des autres, et donc probablement sur le même réseau local, il est très rare que des paquets UDP se perdent ou même arrivent dans le désordre. 2 Mise en œuvre JavaLa mise en œuvre du client et du serveur implique l'utilisation de quelques classes de la bibliothèque Java qui n'ont pas été vue au cours et avec lesquelles il est nécessaire de se familiariser. Une brève introduction à ces classes est donnée ci-dessous, mais il est très important de bien lire leur documentation avant d'essayer de les utiliser. 2.1 Classes Buffer et ByteBuffer La classe
Les principaux attributs que
Ces deux attributs, bien décrits dans la documentation
de la classe Les méthodes de la classe
L'extrait de code ci-dessous montre comment créer un tampon d'une capacité de 100 éléments, y insérer les octets de 0 à 9 aux dix premières positions au moyen d'écritures relatives, remplacer le premier élément par 50 au moyen d'une écriture absolue, puis finalement afficher ces éléments. ByteBuffer b = ByteBuffer.allocate(100); for (int i = 0; i < 10; ++i) b.put((byte)i); // écriture relative b.put(0, (byte)50); // écriture absolue b.flip(); while (b.hasRemaining()) System.out.println(b.get()); // 50, 1, 2, …, 9 2.2 Classes SocketAddress et InetSocketAddress La classe
Les adresses obtenues au moyen du second constructeur ne possédant qu'un numéro de port, elles ne sont utilisables que comme argument à la méthode L'utilisation de ces deux constructeurs est illustrée dans les extraits de code de la section ci-après. 2.3 Classes Channel et DatagramChannel La classe
Les canaux de type Les méthodes de la classe
L'extrait de programme suivant montre comment utiliser un tel canal pour envoyer un paquet UDP constitué d'un seul octet valant 4 sur le port UDP 2016 de l'hôte local, c-à-d l'ordinateur sur lequel s'exécute le programme. DatagramChannel channel = DatagramChannel.open(StandardProtocolFamily.INET); SocketAddress address = new InetSocketAddress("localhost", 2016); ByteBuffer buffer = ByteBuffer.allocate(1); buffer.put((byte)4); buffer.flip(); channel.send(buffer, address); L'extrait de programme suivant montre quant à lui comment utiliser un tel canal pour attendre l'arrivée d'un paquet UDP sur le port 2016 et afficher son contenu à l'écran. DatagramChannel channel = DatagramChannel.open(StandardProtocolFamily.INET); channel.bind(new InetSocketAddress(2016)); ByteBuffer buffer = ByteBuffer.allocate(1); SocketAddress senderAddress = channel.receive(buffer); System.out.printf("Reçu l'octet %d de %s%n", buffer.get(0), senderAddress); En plaçant ces deux extraits chacun dans un programme séparé et en lançant d'abord le second, puis le premier, le second devrait afficher à l'écran un message ressemblant à ceci, mais avec un numéro de port différent de 65347 : Reçu l'octet 4 de /127.0.0.1:65347 Ces deux extraits de programme représentent respectivement un embryon de client et un embryon de serveur, et constituent donc un bon point de départ pour vous. 2.4 Serveur La classe Le serveur prend un seul argument, optionnel, qui donne le nombre de clients qui doivent se connecter avant que la partie ne démarre. Si cet argument n'est pas spécifié, il vaut 4 par défaut. A noter que cet argument ne détermine pas le nombre de joueurs présents sur le plateau, qui vaut toujours 4, mais uniquement le nombre de clients qui doivent se connecter avant que la partie ne démarre. Lorsque ce nombre de clients est inférieur à 4, les joueurs auquel aucun client ne correspond sont simplement inertes. Ce comportement est utile pour le test, puisqu'en spécifiant qu'un seul client est nécessaire au démarrage de la partie, il est possible de lancer à la fois le client et le serveur sur le même ordinateur puis de contrôler le premier joueur via le clavier. Si vous ne savez pas ou plus comment passer des arguments à un programme exécuté depuis Eclipse, vous pouvez consulter notre petit guide à ce sujet. Le serveur attend les messages de la part des clients sur le port UDP 2016, comme l'embryon ci-dessus. 2.4.1 PhasesLe comportement du serveur est très différent dans chacune des deux phases du protocole décrites plus haut. Dans la première phase, il boucle en attendant que le nombre requis de clients (distincts) lui ait envoyé un message exprimant leur intention de se joindre à la partie. Ce faisant, il construit une table associant les adresses des clients — retournées par la méthode
Dans la deuxième phase, et tant que la partie n'est pas terminé, il boucle en envoyant l'état actuel à chacun des clients, dormant jusqu'à l'heure du prochain coup d'horloge (voir ci-après), récoltant les messages reçus des clients durant ce temps afin d'en déterminer les événements, puis calculant le nouvel état. A la fin de la partie, l'identité du vainqueur est affichée à l'écran, s'il y en a un. 2.4.2 Gestion du temps La méthode Ainsi, si \(T_s\) est le temps du début de la partie retourné par La méthode Notez qu'il est possible — et même probable en début de partie — que le temps restant jusqu'au prochain coup d'horloge soit négatif, auquel cas il faut calculer le prochain état sans attendre. Attention à ne pas faire systématiquement dormir le serveur durant 50 ms entre chaque coup d'horloge, car cela est incorrect ! En effet, le temps nécessaire au serveur pour calculer le prochain état doit être pris en compte, faute de quoi la durée d'un coup d'horloge est supérieure à ce qu'il devrait être, et peut varier en fonction du travail que le serveur effectue. 2.4.3 Réception des messages des clients Juste avant de calculer le prochain état du jeu, le serveur doit consulter la totalité des messages reçus des clients durant son « sommeil ». Cela peut se faire facilement à condition que le
canal de réception soit mis en mode non bloquant : dans ce cas, la méthode 2.5 Client La classe Le client prend un seul argument, optionnel, qui donne le nom de l'hôte sur lequel se trouve le serveur. Si cet argument n'est pas spécifié, il vaut A noter que cet argument peut simplement être passé au
constructeur de la classe 2.5.1 PhasesTout comme le serveur, le client a un comportement très différent dans chacune des deux phases du protocole décrit plus haut. Dans la première phase, il boucle en envoyant à intervalles réguliers (p.ex. toutes les secondes) un message au serveur, exprimant son intention de se joindre à la partie. Il fait cela tant et aussi longtemps qu'il n'a pas reçu le premier état du jeu de la part du serveur. Une fois le premier état du jeu reçu, le client le désérialise, l'affiche à l'écran, puis attend (de manière bloquante) le prochain état de la part du serveur et recommence. 2.5.2 Fils d'exécution Comme toute application Swing, le client comporte deux fils d'exécution (threads) qui s'exécutent de manière concurrente : le fil principal (main thread), qui exécute la méthode Le fil principal doit commencer par faire en sorte que l'interface graphique soit construite par le fil Swing. Comme d'habitude, cela peut se faire en appelant la méthode Cela fait, le fil principal se charge de la plupart de la communication avec le serveur. Comme cela a été expliqué plus haut, dans un premier temps il lui communique son intention de se joindre à la partie, et dès qu'il reçoit pour la première fois un état de la part du serveur, il le transmet au fil Swing, puis attend — de manière bloquante — le prochain état, et ainsi de suite. Le fil Swing, de son côté, affiche l'état du jeu lorsque le fil principal le lui demande via un appel à la méthode 2.6 Tests Pour tester votre client et votre serveur sur une seule machine, vous pouvez lancer tout d'abord le serveur, en lui passant En plus de ces tests locaux, il est fortement conseillé d'essayer d'utiliser votre client avec le serveur d'un autre groupe, et inversément. Cela vous permettra de vous assurer que vous avez bien respecté le protocole et le format de sérialisation de l'état spécifiés dans les étapes précédentes. Pour jouer une partie avec plusieurs joueurs utilisant des ordinateurs séparés, il faut tout d'abord déterminer l'adresse IP de l'ordinateur sur laquelle le serveur sera exécuté. Il existe de très nombreux moyens de l'obtenir, le plus simple étant probablement d'utiliser un site comme ipinfo. En cliquant sur ce lien, vous verrez s'afficher l'adresse IP de votre ordinateur, que vous pouvez communiquer oralement aux autres joueurs, pour peu bien entendu que ce soit l'ordinateur exécutant le serveur. Pour faire ces tests, assurez-vous d'être connecté au réseau de l'EPFL, soit par Wifi, soit par le réseau câblé. Pour différentes raisons — en particulier l'utilisation systématique de NAT — il est très peu probable que vous soyez en mesure d'échanger des données entre un serveur et un client situés sur des machines accédant à Internet via un fournisseur d'accès Internet commercial (Swisscom, Cablecom, etc.). Si vous désirez néanmoins effectuer des tests depuis l'extérieur de l'EPFL, connectez-vous dans un premier temps au VPN de l'EPFL. 3 RésuméPour cette étape, vous devez :
Attention : votre projet terminé sera testé « manuellement » par des correcteurs, et il est donc capital que les classes principales du client et du serveur soient nommées conformément aux instructions ci-dessus, et que les deux programmes principaux acceptent les arguments optionnels décrits, avec les valeurs par défaut données. Les groupes rendant un projet ne respectant par l'une ou l'autre de ces règles perdront la totalité des points attribués au test de leur projet. Faites très attention à ce point, il y a malheureusement chaque année quelques groupes qui perdent de nombreux points suite au non respect de telles règles. Comment communiqué le client et le serveur dans le web ?Les navigateurs web communiquent avec les serveurs web avec le protocole HTTP : HyperTextTransfer Protocol. Quand vous cliquez un lien sur une page, soumettez un formulaire ou lancez une recherche, le navigateur envoie une requête HTTP (HTTP Request) au serveur.
Comment connecter un client au serveur ?Un client initie une connexion en ouvrant une connexion TCP sur l'adresse IP et le numéro de port du serveur. Une fois la connexion TCP établie et prête pour la lecture, le client doit envoyer son message de connexion.
Quel protocole de communication utilisé la relation clientLe protocole HTTP
HTTP est le protocole de communication du web. Ce protocole client-serveur permet l'accès aux sites et aux données du world wide web par le biais d'un navigateur Internet.
Qu'estDans une relation client/serveur, un programme (le client) demande un service ou une ressource à un autre programme (le serveur). Le modèle client/serveur peut être utilisé par des programmes d'un même ordinateur, mais le concept est surtout utile dans le cadre d'un réseau.
|