0% ont trouvé ce document utile (0 vote)
2 vues15 pages

Serveur Java Multi-Thread Simple

serveurjava
Copyright
© All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd
0% ont trouvé ce document utile (0 vote)
2 vues15 pages

Serveur Java Multi-Thread Simple

serveurjava
Copyright
© All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd

Développement d'un serveur Java

multi-thread simple

par Julien DEFAUT

Date de publication : 14/03/2005

Dernière mise à jour : 27/03/2005

Cet article explique la réalisation pas à pas d'un serveur multithread en java
utilisant les sockets. Ce serveur se chargera de broadcaster les messages
lui arrivant à tous les clients qui y sont connectés. Téléchargez la version
pdf.
Développement d'un serveur Java multi-thread simple par Julien DEFAUT

Introduction
1 - Le fonctionnement
2 - Le développement
3 - La compilation
4 - Premier démarrage
Téléchargements

-2-
Les sources présentés sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page
de présentation de ces sources constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2005Julien
DEFAUT . Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes,
documents, images, etc sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et
jusqu'à 300 000 E de domages et intérets.
Développement d'un serveur Java multi-thread simple par Julien DEFAUT

Introduction

Nous allons, dans cet article, tenter de comprendre les mécanismes de base de la communication par
sockets via la construction d'un serveur java multiclient. Ce serveur s'attachera à simplement rediriger
les flux arrivant d'un client connecté vers tous les autres et ceci indépendemment du type du client et
du sytème l'hébergeant. En effet les sockets sont une couche suffisamment bas niveau pour être
inclus dans la plupart des systèmes et pour être supportés par une majorité des langages de
programmation.

Voici les fonctionnalités du serveur java que nous allons programmer, baptisé Blablaserveur :

- serveur en java, donc multiplateforme,

- multithread, avec donc la possibilité d'accepter plusieurs clients simultanément,

- action du serveur : reçoit un message du client, l'envoie alors à tous les autres connectés,

- possibilité d'interroger le serveur à travers des commandes serveurs.

Tout ceci peut sembler peu, mais ça implique une universalité du serveur. En plus d'être
multiplateforme, il va s'avérer trés simple de mettre en relation des clients de différents langages.
Ainsi, il sera possible de faire communiquer des clients de tous types entre eux (flash, java, c#, c++,
delphi, ...). L'inconvénient sera la sécurité puisque n'importe qui pourra développer un client pour
"géner" votre application. Mais il ne s'agit pas non plus de faire un serveur IRC, et BlablaServeur
pourra convenir pour de petites applications.

-3-
Les sources présentés sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page
de présentation de ces sources constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2005Julien
DEFAUT . Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes,
documents, images, etc sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et
jusqu'à 300 000 E de domages et intérets.
Développement d'un serveur Java multi-thread simple par Julien DEFAUT

1 - Le fonctionnement

Voici un aperçu de l'architecture de l'application que nous allons développer :

D'abord, définition : qu'est ce qu'un Thread ? Il s'agit d'un ensemble d'instructions qui va se partager le
processeur avec d'autres "morceaux" du programme. On a ainsi l'impression que plusieurs actions
sont effectuées en parallèle.

Ici, nous aurons trois types de thread :

- le thread principal : il va attendre les connexions en écoutant le port ouvert pour le serveur. A chaque
connexion il va lancer un nouveau thread de type client.

- le thread client : il y en a donc un associé à chaque client connecté, il est chargé d'attendre l'arrivée
d'un message. A chaque fois, il enverra le message reçu à tous les clients connectés.

[Link]
Développement d'un serveur Java multi-thread simple par Julien DEFAUT

- le thread des commandes : il attend que l'admin du serveur tape des commandes dans la console
pour faire une action (quitter le serveur par exemple).

Et en pratique, on fait ça comment ?

-5-
Les sources présentés sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page
de présentation de ces sources constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2005Julien
DEFAUT . Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes,
documents, images, etc sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et
jusqu'à 300 000 E de domages et intérets.
Développement d'un serveur Java multi-thread simple par Julien DEFAUT

2 - Le développement

Le langage choisi ici est le java, simplement parce qu'on pourra lancer le serveur aussi facilement
sous Unix, que sous Windows ou que sous Mac (et aussi parce que les sockets sont assez simples à
utiliser en java).

Nous allons créer 3 classes :

> BlablaServ, correspond au thread principal

Contient les méthodes suivantes :

- public static void main(String args[])

- static private void printWelcome(Integer port)

- synchronized public void sendAll(String message,String sLast)

- synchronized public void delClient(int i)

- synchronized public int addClient(PrintWriter out)

- synchronized public int getNbClients()

> BlablaThread, correspond au thread client

Contient les méthodes suivantes :

- BlablaThread(Socket s, BlablaServ blablaServ)

- public void run()

> Commandes, correspond au thread des commandes

Contient les méthodes suivantes :

- Commandes(BlablaServ blablaServ)

- public void run()

Créez un répertoire "blabla" et placez y trois fichiers de type textes vides : [Link],

[Link]
Développement d'un serveur Java multi-thread simple par Julien DEFAUT

[Link], [Link].

Dans les explications nous ne détaillerons pas trop le code, il semble suffisamment commenté pour
comprendre chaque ligne.

La classe BlablaServ

Mettez dans le fichier [Link], le code suivant :

[Link]
import [Link].*;
import [Link].*;
import [Link].*;

//** Classe principale du serveur, gère les infos globales **


public class BlablaServ
{
private Vector _tabClients = new Vector(); // contiendra tous les flux de sortie vers les clients
private int _nbClients=0; // nombre total de clients connectés

//** Methode : la première méthode exécutée, elle attend les connections **


public static void main(String args[])
{
BlablaServ blablaServ = new BlablaServ(); // instance de la classe principale
try
{
Integer port;
if([Link]<=0) port=new Integer("18000"); // si pas d'argument : port 18000 par défaut
else port = new Integer(args[0]); // sinon il s'agit du numéro de port passé en argument

new Commandes(blablaServ); // lance le thread de gestion des commandes

ServerSocket ss = new ServerSocket([Link]()); // ouverture d'un socket serveur sur port


printWelcome(port);
while (true) // attente en boucle de connexion (bloquant sur [Link])
{
new BlablaThread([Link](),blablaServ); // un client se connecte, un nouveau thread client est lancé
}
}
catch (Exception e) { }
}

//** Methode : affiche le message d'accueil **


static private void printWelcome(Integer port)
{
[Link]("--------");
[Link]("BlablaServeur : Par Minosis - Julien Defaut");
[Link]("Copyright : 2004 - [Link]");
[Link]("Derniere version : 10/04/2004");
[Link]("--------");
[Link]("Demarre sur le port : "+[Link]());
[Link]("--------");
[Link]("Quitter : tapez \"quit\"");
[Link]("Nombre de connectes : tapez \"total\"");
[Link]("--------");
}

-7-
Les sources présentés sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page
de présentation de ces sources constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2005Julien
DEFAUT . Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes,
documents, images, etc sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et
jusqu'à 300 000 E de domages et intérets.
Développement d'un serveur Java multi-thread simple par Julien DEFAUT

[Link]
//** Methode : envoie le message à tous les clients **
synchronized public void sendAll(String message,String sLast)
{
PrintWriter out; // declaration d'une variable permettant l'envoi de texte vers le client
for (int i = 0; i < _tabClients.size(); i++) // parcours de la table des connectés
{
out = (PrintWriter) _tabClients.elementAt(i); // extraction de l'élément courant (type PrintWriter)
if (out != null) // sécurité, l'élément ne doit pas être vide
{
// ecriture du texte passé en paramètre (et concaténation d'une string de fin de chaine si besoin)
[Link](message+sLast);
[Link](); // envoi dans le flux de sortie
}
}
}

//** Methode : détruit le client no i **


synchronized public void delClient(int i)
{
_nbClients--; // un client en moins ! snif
if (_tabClients.elementAt(i) != null) // l'élément existe ...
{
_tabClients.removeElementAt(i); // ... on le supprime
}
}

//** Methode : ajoute un nouveau client dans la liste **


synchronized public int addClient(PrintWriter out)
{
_nbClients++; // un client en plus ! ouaaaih
_tabClients.addElement(out); // on ajoute le nouveau flux de sortie au tableau
return _tabClients.size()-1; // on retourne le numéro du client ajouté (size-1)
}

//** Methode : retourne le nombre de clients connectés **


synchronized public int getNbClients()
{
return _nbClients; // retourne le nombre de clients connectés
}

La fonction statique main() est exécutée en premier au lancement de l'application. Les paramètres
passés au démarrage de l'application se trouvent stockés dans le tableau de strings args[]. Le seul
argument que nous gérons est le numéro de port. Si l'argument est absent, alors le port sera 18000.
Si le port est déjà pris par une autre application, ou un problème survient lors de la création du
ServerSoket, une exception est levée et un message d'erreur est affiché.

Juste après l'instanciation du ServerSocket, un message d'accueil est lancé ainsi que le thread
d'attente des commandes.

Puis une boucle infinie attend les connexions des clients. Dès qu'une requète arrive, [Link]() est
débloqué et une nouvelle instance de BlablaThread est créée. L'objet BlablaServer est passé en
paramètre pour pouvoir utiliser, dans les threads clients, ses méthodes de gestion du vecteur
_tabClients. On observe que ces méthodes sont "synchronised", cela signifie que chaque thread,
voulant en exécuter une, devra attendre qu'un thread ait fini de l'utiliser. Ceci évite ainsi des accés
simultanés aux variables communes (_tabClients et _nbClients).

[Link]
Développement d'un serveur Java multi-thread simple par Julien DEFAUT

La classe BlablaThread

Mettez dans le fichier [Link], le code suivant :

[Link]
import [Link].*;
import [Link].*;

//** Classe associée à chaque client **


//** Il y aura autant d'instances de cette classe que de clients connectés **
//implémentation de l'interface Runnable (une des 2 méthodes pour créer un thread)
class BlablaThread implements Runnable
{
private Thread _t; // contiendra le thread du client
private Socket _s; // recevra le socket liant au client
private PrintWriter _out; // pour gestion du flux de sortie
private BufferedReader _in; // pour gestion du flux d'entrée
private BlablaServ _blablaServ; // pour utilisation des méthodes de la classe principale
private int _numClient=0; // contiendra le numéro de client géré par ce thread

//** Constructeur : crée les éléments nécessaires au dialogue avec le client **


BlablaThread(Socket s, BlablaServ blablaServ) // le param s est donnée dans BlablaServ par [Link]()
{
_blablaServ=blablaServ; // passage de local en global (pour gestion dans les autres méthodes)
_s=s; // passage de local en global
try
{
// fabrication d'une variable permettant l'utilisation du flux de sortie avec des string
_out = new PrintWriter(_s.getOutputStream());
// fabrication d'une variable permettant l'utilisation du flux d'entrée avec des string
_in = new BufferedReader(new InputStreamReader(_s.getInputStream()));
// ajoute le flux de sortie dans la liste et récupération de son numero
_numClient = [Link](_out);
}
catch (IOException e){ }

_t = new Thread(this); // instanciation du thread


_t.start(); // demarrage du thread, la fonction run() est ici lancée
}

//** Methode : exécutée au lancement du thread par [Link]() **


//** Elle attend les messages en provenance du serveur et les redirige **
// cette méthode doit obligatoirement être implémentée à cause de l'interface Runnable
public void run()
{
String message = ""; // déclaration de la variable qui recevra les messages du client
// on indique dans la console la connection d'un nouveau client
[Link]("Un nouveau client s'est connecte, no "+_numClient);
try
{
// la lecture des données entrantes se fait caractère par caractère ...
// ... jusqu'à trouver un caractère de fin de chaine
char charCur[] = new char[1]; // déclaration d'un tableau de char d'1 élement, _in.read() y stockera le char lu
while(_in.read(charCur, 0, 1)!=-1) // attente en boucle des messages provenant du client (bloquant sur _in.read())
{
// on regarde si on arrive à la fin d'une chaine ...
if (charCur[0] != '\u0000' && charCur[0] != '\n' && charCur[0] != '\r')
message += charCur[0]; // ... si non, on concatène le caractère dans le message
else if(![Link]("")) // juste une vérification de principe
{
if(charCur[0]=='\u0000') // le dernier caractère était '\u0000' (char de terminaison nulle)

-9-
Les sources présentés sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page
de présentation de ces sources constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2005Julien
DEFAUT . Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes,
documents, images, etc sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et
jusqu'à 300 000 E de domages et intérets.
Développement d'un serveur Java multi-thread simple par Julien DEFAUT

[Link]
// on envoi le message en disant qu'il faudra concaténer '\u0000' lors de l'envoi au client
_blablaServ.sendAll(message,""+charCur[0]);
else _blablaServ.sendAll(message,""); // sinon on envoi le message à tous
message = ""; // on vide la chaine de message pour qu'elle soit réutilisée
}
}
}
catch (Exception e){ }
finally // finally se produira le plus souvent lors de la deconnexion du client
{
try
{
// on indique à la console la deconnexion du client
[Link]("Le client no "+_numClient+" s'est deconnecte");
_blablaServ.delClient(_numClient); // on supprime le client de la liste
_s.close(); // fermeture du socket si il ne l'a pas déjà été (à cause de l'exception levée plus haut)
}
catch (IOException e){ }
}
}
}

Le socket créé par [Link]() est ici reçu en paramètre, à partir de _s, on crée ainsi les buffers gérant
le flux d'entrée et le flux de sortie.

Le thread est ensuite lancé (méthode run()). Une attente en boucle des données a lieu. Dès qu'un
caractère arrive en entrée, il est concaténé à la chaine _message. Le message est envoyé dés qu'un
caractère de fin de chaine est détecté. Si il s'agit de '\u0000', celui ci sera concaténé au message lors
de l'envoi (méthode sendAll()). Ceci est nécessaire, essentiellement à cause des clients Flash pour
lesquels le message reçu doit se finir par le zéro terminal (cf doc Flash partie onXML). Les autres
types de clients sont, en général, plus tolérants. Au moins, BlablaServeur fonctionnera
indépendamment du type de clients.

Enfin, aprés tout événement entraînant la fin de la boucle, le bloc finally{} sera exécuté pour mettre fin
proprement à l'existence du thread client.

La classe Commandes

Mettez dans le fichier [Link], le code suivant :

[Link]
import [Link].*;

//** Classe qui gère les commandes tapées dans la console **


// implémentation de l'interface Runnable (une des 2 méthodes pour créer un thread)
class Commandes implements Runnable
{
BlablaServ _blablaServ; // pour utilisation des méthodes de la classe principale
BufferedReader _in; // pour gestion du flux d'entrée (celui de la console)
String _strCommande=""; // contiendra la commande tapée
Thread _t; // contiendra le thread

//** Constructeur : initialise les variables nécessaires **

[Link]
Développement d'un serveur Java multi-thread simple par Julien DEFAUT

[Link]
Commandes(BlablaServ blablaServ)
{
_blablaServ=blablaServ; // passage de local en global
// le flux d'entrée de la console sera géré plus pratiquement dans un BufferedReader
_in = new BufferedReader(new InputStreamReader([Link]));
_t = new Thread(this); // instanciation du thread
_t.start(); // demarrage du thread, la fonction run() est ici lancée
}

//** Methode : attend les commandes dans la console et exécute l'action demandée **
public void run() // cette méthode doit obligatoirement être implémentée à cause de l'interface Runnable
{
try
{
// si aucune commande n'est tapée, on ne fait rien (bloquant sur _in.readLine())
while ((_strCommande=_in.readLine())!=null)
{
if (_strCommande.equalsIgnoreCase("quit")) // commande "quit" detectée ...
[Link](0); // ... on ferme alors le serveur
else if(_strCommande.equalsIgnoreCase("total")) // commande "total" detectée ...
{
// ... on affiche le nombre de clients actuellement connectés
[Link]("Nombre de connectes : "+_blablaServ.getNbClients());
[Link]("--------");
}
else
{
// si la commande n'est ni "total", ni "quit", on informe l'utilisateur et on lui donne une aide
[Link]("Cette commande n'est pas supportee");
[Link]("Quitter : \"quit\"");
[Link]("Nombre de connectes : \"total\"");
[Link]("--------");
}
[Link](); // on affiche tout ce qui est en attente dans le flux
}
}
catch (IOException e) {}
}
}

Cette classe n'est pas indispensable dans le fonctionnement du serveur mais apporte la possibilité
d'une administration propre même si minimaliste.

La classe est instanciée dès le chargement du serveur (méthode main() de BlablaServ). Elle crée dès
le départ un buffer d'entrée à partir du flux provenant du système (texte tapé dans la console en
général, dans une fenêtre Dos sous Windows ou le terminal sous Unix/Linux). L'activité principale
(dans run()) est d'attendre les instructions et de les exécuter. La méthode readLine() est ici plus
adaptée et plus simple que le read() utilisé dans le thread client. Bien sûr la méthode readLine() n'était
pas intéressante dans le thread client puisque le zéro terminal devait être géré.

Deux commandes sont implémentées : le "quit" qui appelle un simple [Link](0) et le "total" qui
informe sur la quantité de clients connectés.

- 11 -
Les sources présentés sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page
de présentation de ces sources constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2005Julien
DEFAUT . Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes,
documents, images, etc sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et
jusqu'à 300 000 E de domages et intérets.
Développement d'un serveur Java multi-thread simple par Julien DEFAUT

3 - La compilation

Le code est écrit, il reste maintenant à compiler nos .java et exécuter notre serveur.

Nous partons du principe que le jdk version 1.4.1 ou plus est installé sur votre machine. Comme il y a
peu de packages évolués d'utilisés dans notre code, il ne devrait pas y avoir trop de problème sur des
jdk inférieurs. Je n'ai cependant pas testé.

Ouvrez une console (Dos, xTerm, ... selon votre OS). Placez vous dans le repertoire "blabla" et faites
"javac *.java". Normalement, un message indique que l'on utilise un élément déprécié de l'API. Il s'agit
de fermeture sauvage du thread client _t.stop(). Nous ne tiendrons pas compte de ce message donc
son apparition indique que la compilation s'est bien déroulée. 3 fichiers .class avec les même noms
que les .java doivent être apparues dans le répertoire "blabla". Dans le cas contraire vérifiez que vous
avez effectué toutes les tâches correctement ...

[Link]
Développement d'un serveur Java multi-thread simple par Julien DEFAUT

4 - Premier démarrage

Aprés avoir téléchargé le serveur (ou suivi les chapitres précédents), décompressez le zip dans le
répertoire de votre choix (nous l'appelerons "blabla/").

Pour lancer le serveur sous n'importe quel système, tapez en ligne de commande dans le répertoire
blabla : "java -jar [Link] 18000". Sous windows, la commande "java BlablaServ" peut suffire
mais je n'ai pas réussi à lancer le serveur de cette manière sous Unix. Un fichier jar exécutable est
donc contenu dans le zip et permet d'avoir un serveur qui fonctionne partout. Un jar exécutable est
une simple archive zip renommée qui contient les classes ainsi qu'un manifest identifiant la classe
principale. Décompressez le jar si vous souhaitez en comprendre la structure.

Encore plus simplement, dans le zip vous trouverez deux fichiers pour lancer le serveur sur le port
18000, éditez les pour changer le port :

Windows : double-cliquez sur [Link]

Unix/Linux : tappez "sh [Link]" ou "./[Link]"

En cas de problème vérifiez que la version de votre JRE d'installée est la 1.4.1 minimum pour être sûr
du fonctionnement du serveur. Avec des versions antérieures, le serveur n'a pas été testé mais peut
sûrement fonctionner. Pour consulter la version, lancez une fenêtre console (Invite msdos par
exemple) et tapez "java -version". Si vous voulez installer la version la plus récente du JRE,
rendez-vous sur le site de Sun. Vérifiez aussi que le port 18000 n'est pas pris.

Logiquement à ce stade un message apparaît en indiquant que le serveur est démarré sur le port
18000 :

- 13 -
Les sources présentés sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page
de présentation de ces sources constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2005Julien
DEFAUT . Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes,
documents, images, etc sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et
jusqu'à 300 000 E de domages et intérets.
Développement d'un serveur Java multi-thread simple par Julien DEFAUT

On voit qu'en tapant le mot "total" (puis Entrer), le nombre de connectés apparaît. Logiquement, pour
l'instant le nombre est 0.

Pour quitter proprement le serveur, veillez à taper "quit".

Pour tester le serveur, l'archive propose un programme en Flash, il suffit d'ouvrir le fichier
[Link] de l'archive. Vous pouvez choisir l'adresse du serveur. Mais pour modifier le port de
connexion 18000 par défaut, éditez [Link] et changez la ligne "[Link](_root.ipserveur,
18000);" avec le port qui vous convient.

[Link]
Développement d'un serveur Java multi-thread simple par Julien DEFAUT

Téléchargements

Téléchargez la version pdf de cet article.

Téléchargez les sources et packages du projet (inclus client Flash de test).

- 15 -
Les sources présentés sur cette pages sont libres de droits, et vous pouvez les utiliser à votre convenance. Par contre la page
de présentation de ces sources constitue une oeuvre intellectuelle protégée par les droits d'auteurs. Copyright © 2005Julien
DEFAUT . Aucune reproduction, même partielle, ne peut être faite de ce site et de l'ensemble de son contenu : textes,
documents, images, etc sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à 3 ans de prison et
jusqu'à 300 000 E de domages et intérets.

Vous aimerez peut-être aussi