Client FLash

Rien de bien compliqué, nous utilisons un objet de type XMLSocket qui va donc se connecter à notre serveur, lui envoyer des données sous forme XML et en recevoir...
Dans la mesure où - dans mon cas - le serveur Java va lire les données ligne par ligne, il est important d'envoyer nos données XML dans une chaîne de caractères (String) et placer un caractère de retour de chariot à la fin de notre chaîne (\r) :

var data:String = 
"<data>"+
"<type>creerPartie</type>"+
"<nomPartie>"+nomPartie+"</nomPartie>"+
"</data>";
socket.send(data+"\r");

On se connecte donc au serveur en instanciant un XMLSocket :

socket = new XMLSocket();
socket.connect("monserveur.com", port);

Le mieux est d'avoir un port supérieur à 1024 afin de régler certains problèmes de sécurité de Flash.

On ajoute nos écouteurs (connexion, déconnexion, réception de données, ...) :

socket.addEventListener(Event.CLOSE, handlerDeConnexion);
socket.addEventListener(Event.CONNECT, connectHandler);
socket.addEventListener(DataEvent.DATA, dataHandler);
socket.addEventListener(IOErrorEvent.IO_ERROR, ioErrorHandler);
socket.addEventListener(ProgressEvent.PROGRESS, progressHandler);
socket.addEventListener(SecurityErrorEvent.SECURITY_ERROR, securityErrorHandler);

Une fois connecté, nous envoyons donc un message au serveur qui se chargera de le dispatcher à tous les clients connectés :

var data:String = 
"<data>"+
"<type>connexion</type>"+
"<nom>"+nom_txt.text+"</nom>"+
"</data>";
socket.send(data+"\r");

Et enfin la réception qui se fait grâce à l'écouteur DataEvent.DATA :

private function dataHandler(event:DataEvent):void {
	var dataXML:XML = XML(event.data.toString());
	var type:String = dataXML.type.toString();
	if (type == "rejoindrePartie") {
		// code à exécuter
	}
}

Je parse ma donnée XML afin de récupérer le type. C'est à dire que dans mon cas, le type réprésente l'entête de mon paquet, afin de savoir à quoi il correspond et à qui je dois le faire parvenir. Par exemple, mon type peut être un envoi de position avec comme information le nom du joueur et ses coordonées x, y, z...

Tant que nous parlons de la partie Flash, il faut savoir que FlashPlayer, avant de se connecter au serveur par xmlSocket, va envoyer une données XML de ce type :

<policy-file-request/>

FlashPlayer va vérifier s'il a le droit de se connecter à l'hôte. Pour cela, il nous faut créer, comme citer dans un billet précédent, un fichier nommé crossdomain.xml à la racine de notre hébergement. Nous allons définir à l'intérieur de ce fichier les domaines qui peuvent accéder à notre serveur et les ports pouvant être utilisés. Généralement, le domaine qui héberge notre serveur se trouvant le même que celui qui héberge notre application flash, nous allons donc seulement y mettre notre nom de domaine :
<?xml version="1.0"?>
<!DOCTYPE cross-domain-policy 
  SYSTEM "http://www.macromedia.com/xml/dtds/cross-domain-policy.dtd">
<cross-domain-policy>
  <allow-access-from domain="votredomaine.com" />
  <allow-access-from domain="votredomaine.com" to-ports="xxxx" />
</cross-domain-policy>

Ce seront des choses à prévoir dans le développement...

Serveur Java Multi-Thread

Voyons voir maintenant comment créer notre serveur Java.

Nous créeons un serveur Multi-Thread, c'est à dire qui peut accepter plusieurs clients. Donc un Thread pour chaque client...

Premièrement, nous implémentons un socket :

try {
	sock = new ServerSocket(port);
	System.out.println("Serveur actif, en attente de clients");
	start(); // démarre le thread
} catch (IOException e) {
	System.err.println("Erreur au lancement du Serveur");
	System.exit(0);
}

Puis, nous acceptons les connexions clientes et créeons un Thread (ma classe Connexion) pour chacun d'eux :
public void run() {
	try {
		while (true) {
			Socket usager = sock.accept();
			String nomUsager = usager.getInetAddress().getHostName();
			System.out.println("Connexion depuis " + nomUsager);
			/* Nouveau Client */
			Connexion client = new Connexion(usager, nomUsager);
			/* Ajout du client dans la liste des clients */
			synchronized(clients) {
				clients.add(client); // clients est une ArrayList<Connexion>()
			}
		}
	} catch(IOException e) {
		System.err.println("Erreur de fonctionnement du Serveur !");
		System.exit(0);
	}finally {
		System.out.println("Serveur inactif");
	}
}

Nous remarquons l'emploi du terme synchronized, qui - comme son nom l'indique - synchronise l'ajout dans la liste. En effet, il ne vaut mieux pas que deux ajouts se fassent en même temps...

Voyons voir notre classe Connexion...
Nous créeons deux "streams" afin de lire les données et d'en envoyer :

private BufferedReader dataIn;			/* Lecteur de données sur la socket */
private PrintWriter dataOut;			/* Ecrivain de données sur la socket */

Nous appelons aussi la méthode start() afin de démarrer le thread.
public class Connexion extends Thread { // on étend la classe Thread...
// constructeur
	public Connexion(Socket sk, String nom) {
		try {
			dataIn = new BufferedReader(new InputStreamReader(sk.getInputStream()));
			dataOut = new PrintWriter(sk.getOutputStream(), true);
		} catch (IOException e) {
			System.err.println("Erreur d'E/S au niveau du serveur");
		}
		start();
	}
}

Et voici le thread en question, sans commentaires je pense :

public void run() {
	String msg;
	try {
		while ((msg = dataIn.readLine()) != null) { // lis chaque ligne qu'il reçoit
			synchronized(Serveur.clients) { 
				for (Connexion c:Serveur.clients) { // on parcours tous les clients
					if (c != null) c.send(msg); // méthode décrite plus bas
				}
			}
		}
	} catch (IOException e) {
		System.err.println(e.getMessage());
	} catch (SQLException e) {
		e.printStackTrace();
	}finally {
		/* deconnexion du client, on ferme les streams et le socket */
		sockClient.close();
		dataIn.close();
		dataOut.close();
		sockClient = null;
		// Suppresion du client de la liste des clients
		synchronized(Serveur.clients) {
			Serveur.clients.remove(this);
		}
	}
}

Il ne nous reste plus que la méthode send() à décrire :

public void send(String mess) {	
	// trim() permet la suppression des espaces blancs
	dataOut.println(mess.trim() + (char)0x00); 
	dataOut.flush();
}

Afin que Flash lise ce que le serveur lui envoie, il faut un caractère null à la fin de chaque ligne, d'où le (char)0x00.

Voilà la fin du tutoriel, j'espère qu'il vous sera utile, tous n'est pas implémenté, et pour le moment je n'ai pas le temps de créer des sources. La rédaction de ce billet m'a déjà pris assez de temps...
En cas de difficultés, laissez un commentaire ou contactez moi !
A bientôt !