P
'
t
i
t
e
C
h
a
t
t
e
 
spacer~ ALWAYS REMEMBER YOU'RE UNIQUE, JUST LIKE EVERYONE ELSE Articles | Connexion
 
~Les sockets serveurs

Précédent  
  Java  
  Suivant
 Présentation

Chose promise, chose due. Les arcanes des sockets clients n'ayant plus de secrets pour nous, il ne nous reste plus qu'à apprendre comment programmer nous mêmes ces fameux serveurs sur lesquels nous connections nos logiciels clients.
Avant d'aborder l'aspect technique du sujet, intéressons nous tout d'abord au fonctionnement général d'un serveur logiciel. Nous savons que les clients contiennent des routines ouvrant une connexion sur le port d'un serveur pour y demander et récupérer des informations. Un serveur logiciel aura donc pour but d'ouvrir un port sur la machine hôte et de gérer tous les clients se connectant à ce port. Cette gestion peut varier d'un type de serveur à un autre: simple réponse (un serveur HTTP), distribution aux autres clients (par exemple pour un jeu), mise en relation directe des clients (ICQ ou IRC en mode DCC), et ainsi de suite...
 Sommaire


 Le Socket serveur

A l'instar des Socket que nous nommerons "simples", c'est à dire les sockets clients, les sockets serveurs proposent un flux d'entrée et un flux de sortie. Pour employer un socket serveur en Java, il suffit d'utiliser une instance de la classe ServerSocket du package java.net. La classe ServerSocket offre trois constructeurs parmi lesquels un seul nous intéresse:

ServerSocket(int port)
       
      
JextCopier dans Jext | Jext | Plugin Codegeek
Comme vous pouvez le constater, le constructeur du ServerSocket n'attend qu'un seul et unique argument: un entier. Cet entier joue un peu le même rôle que pour les sockets simples. Il permet de spécifier un port. Mais dans ce cas, cet entier définira le numéro d'identification du port à créer. Vous êtes par ailleurs entièrement libre du choix du numéro du port dès lors que celui-ci est compris dans les limites permises par le type int (de 0 à 2 147 483 647, c'est à dire 2 ^ 32 / 2 - 1 car nous ne pouvons utiliser que des entiers). Toutes les applications réseaux utilisant des ports spécifiques, il vous faudra faire attention à ne pas utiliser de numéro déjà pris. Si votre serveur est destiné à être utilisé sur une machine personnelle, il est peu probable que l'utilisation des ports 25 (envoi de mails) ou 8080 (HTTP) soit gênante. Cependant, ceci est vivement déconseillé, surtout étant donné que la plage de valeurs allouée est largement suffisante pour que vous puissiez y trouver votre bonheur. Dans notre exemple, nous utiliserons le port numéro 1705.

Afin d'illustrer cet article, nous allons concevoir un logiciel permettant de répondre à des questionnaires simples via un réseau. Ces questionnaires seront une succession de questions à choix multiples pour lesquelles chaque bonne réponse donnera un point. Le logiciel se nomme LoginQuiz et se trouve sur le CD accompagnant le magazine. La particularité de LoginQuiz est d'utiliser le format XML pour la définition de ses questionnaire. Une grande partie du code est ainsi propre à la création des questionnaires depuis des fichiers XML. Nous n'aborderons pas cet aspect de LoginQuiz mais sachez que les fichiers Answer.java, Question.java, QuestionSetHandler.java et QuestionSetReader.java sont dédiés à cette tâche. Toute la partie réseau de notre application est facilitée, comme nous allons le voir, par l'utilisation d'une API nommée Caffeine.


 Définition du serveur

Implémenter un serveur demande, outre du code, un minimum de réflexion quant à la manière dont le serveur transmet et reçoit les informations. Notre application ne nécessitant pas d'envois rapides et répétés (à l'inverse de Quake 3 par exemple) de données, nous nous contenterons d'un système fort simple dont voici la description:

Réception:
? : le client demande l'envoi de la question suivante
$answer : le client propose la réponse answer et en demande la vérification

Envoi:
% : code terminal, le questionnaire est fini
$0 : mauvaise réponse
$1 : bonne réponse
?libellé#ID1$réponse1#ID2$réponse2... :
libellé est la question elle même,
IDx représente l'identifiant de la réponse
(transmis pour vérification) et réponseX est le libellé de la réponse
       
      
JextCopier dans Jext | Jext | Plugin Codegeek
Hormis l'encodage de la question, notre "protocole" est vraiment très simple. L'intérêt de séparer chaque réponse par le caractère # permettra au client de les séparer très facilement en utilisant l'objet java.util.StringTokenizer.

Penchons nous maintenant sur le coeur du serveur qui est inscrit dans le fichier QuestionSetServer.java. La création du serveur passe par le constructeur CaffeineServer(int port, String nom, String motDePasse, int nombreConnexionsMax) de la classe parente. Ici, nous utilisons donc le port 1705, n'utilisons pas de mot de passe et définissons 25 comme étant le nombre maximum de clients. La seule autre particularité de cette classe est la méthode serverEvent(ServerEvent evt) appelé à chaque réception de données. Nous allons maintenant laisser un instant cette classe pour regarder de plus près la fameuse CaffeineServer.


 Implémentation de ServerSocket

Les deux parties de la classe CaffeineServer que nous allons étudier sont les méthodes startServer() et getClient(). La première méthode prend en charge la création du serveur même:

try
{
  server = new ServerSocket(serverPort);
  server.setSoTimeout(1000);
} catch (IOException ioe) {
  System.err.println("[Cannot initialize Server]\n" + ioe);
  System.exit(1);
}
       
      
JextCopier dans Jext | Jext | Plugin Codegeek
L'instanciation d'un socket serveur est très simple et demande une seule ligne de code. La deuxième ligne permet de définir un délai au bout duquel une tentative de lecture des sockets connectés est considérée comme un échec. Cela pour éviter de bloquer le serveur en cas de problèmes de transmission. Vous constaterez également que lors de la création d'un serveur, une exception peut être rencontrée, auquel cas nous arrêtons tout. La suite de la méthode startServer() n'offre que peu d'intérêt si ce n'est de montrer de quelle manière est-il possible de connaître l'adresse IP de la machine.

La seconde méthode, getClient() est placée dans la boucle d'un thread et attend tout simplement que quelqu'un daigne bien tenter de se connecter au serveur:

client = server.accept();
new Authorizer(client, password);
       
      
JextCopier dans Jext
Ces deux lignes font tout le travail. L'appel à server.accept() va effectivement attendre qu'un client se connecte. Si tel est le cas, nous récupérons alors un objet Socket. Ensuite, nous créons un nouvel objet Authorizer. Cet objet un peu spécial est une classe interne de CaffeineServer. Cette classe va démarrer un thread qui se chargera d'administrer la demande de connexion. L'usage d'un thread permet d'éviter au serveur de bloquer les demandes des autres clients en attendant la résolution de la demande en cours. Comme nous n'utilisons pas de mot de passe, la seule partie de Authorizer qui nous est utile est la suivante:

addClient(new CaffeineSocket(CaffeineServer.this, client));
       
      
JextCopier dans Jext
Ce code va simplement créer un nouvel objet CaffeineSocket et l'ajouter à notre liste de clients. CaffeineSocket est un objet gérant entièrement les Socket clients. En effet, cette classe va créer, de la même manière que nous l'avions fait pour notre expéditeur de mails, des flux d'E/S pour faciliter la communication avec le client. Cet objet crée aussi un thread dans lequel nous nous contentons d'attendre des données avant de les transmettre au serveur:

if ((request = reader.readLine()) != null)
  parent.fireEvent(request, this);
       
      
JextCopier dans Jext
Nous n'irons pas plus avant dans les explications concernant la lecture de données puisque c'est exactement la même méthode que celle utilisée pour les sockets clients. Sachez seulement que l'appel à fireEvent() est celui qui permettra à la classe QuestionSetServer de recevoir les données par le biais de la méthode serverEvent().


 Une réponse pour la 259.162.11.20, une !

Revenons maintenant à la classe QuestionSetServer. Celle-ci, grâce au système d'événements mis en place par l'API Caffeine, n'a pas à se soucier de la lecture des données en provenance des clients, mais seulement de leur envoi. Chaque expédition de données est réalisée ainsi:

client.getOut().println("données");
client.getOut().flush();
       
      
JextCopier dans Jext | Jext | Plugin Codegeek
L'objet client désigne un objet CaffeineSocket, la méthode getOut() renvoie un flux PrintWriter que nous utilisons exactement comme pour l'envoi de mail en faisant appel à println(). La méthode flush() permet de s'assurer que tous les caractères du message ont étés effectivement envoyés.

L'exemple proposé ici, LoginQuiz, étant un petit peu complexe, une étude attentive du code source, commenté, vous permettra de saisir toutes les, rares, subtilités et notamment d'approfondir votre expérience des sockets clients.



par Romain Guy
romain.guy@jext.org
http://www.jext.org
Dernière mise à jour : 14/10/2006


Précédent  
  Java  
  Suivant

 
#ProgX©2005 Mathieu GINOD - Romain GUY - Erik LOUISE