Dans ce tutorial, nous allons créer une application multi-utilisateurs permettant de dessiner à plusieurs sur un tableau.
Vous pouvez directement observer le résultat de ce tutorial
en ligne.
Dans un premier temps, nous allons nous occuper de la partie serveur toujours basée sur le serveur de
ce tutorial. Nos explications seront en php puisque il s'agit du langage le plus rependu.
L'équivalent en perl est aussi disponible au bas de cette page. Globalement, le perl et le php sont assez proches pour pouvoir passer de l'un a l'autre facilement.
Voici donc la base de notre serveur :
<?php
$mainsock = socket_create(AF_INET, SOCK_STREAM, 0);
socket_set_option($mainsock, SOL_SOCKET,SO_REUSEADDR, 1);
socket_bind($mainsock, "127.0.0.1", 22222) or die('Could not bind to address');
socket_listen($mainsock);
///////////////////
$clients=Array();
$compteur=0;
echo("En attente d'un client !\n");
while(true){
$toread=Array();
array_push($toread,$mainsock);
for ($i=0;$i<count($clients);$i++){ // pour tous les clients
array_push($toread,$clients[$i]["SOCKET"]);
}
socket_select($toread,$a=null,$a=null,$a=null);
if(in_array($mainsock, $toread)){// le mainsock est dans le tableau $toread.
// c'est notre mainsock donc un nouveau client
$sock=socket_accept($mainsock);
echo("Arrivée d'un nouveau client !\n");
$compteur++;
$nb=count($clients);
$clients[$nb]["SOCKET"]=$sock;
$clients[$nb]["UID"]=$compteur;
//////////////////////////////////////////////////Arrivée d'un client
//////////////////////////////////////////////////
}else{
// c'est un client qui dit quelque chose
for ($i=0;$i<count($clients);$i++){ // on cherche le client
if(in_array($clients[$i]["SOCKET"], $toread)) { // celui la est dans le tableau toread
$input = socket_read($clients[$i]["SOCKET"], 1024);
if($input==null){
/// deconnection du client !
for ($j=0;$j<count($clients);$j++){ // on le cherche dans le tableau
if($clients[$j]==$clients[$i]){ // trouvé
//////////////////////////////////////////////////Perte d'un client
//////////////////////////////////////////////////
echo("Deconnection du client ".$clients[$j]["UID"]."\n");
array_splice($clients,$j,1); // on le retire du tableau
$i--;
}
}
}else{
//////////////////////////////////////////////////Message d'un client
//////////////////////////////////////////////////
}
}
}
}
}
function sendToAll($sauf,$msg){
global $clients;
for($i=0;$i<count($clients);$i++){ // pour tous les clients sauf celui qui envoie
if($sauf!=$clients[$i]["UID"]){
socket_write($clients[$i]["SOCKET"],$msg.chr(0));
}
}
}
php?>
Dans le code ci-dessus nous avons mis en évidence (séparés par des ////) les trois zones dans lesquelles nous allons travailler. Ces trois zones représentent respectivement : la gestion d'arrivée d'un client, de déconnection et de réception de message.
L'ors q'un client se connecte, nous allons transmettre à tous le monde le nombre total de clients connectés. Pour cela nous allons utiliser la fonction déclarée
sendToAll() mais sans y passer le numéro de client qu'il faut éviter. En effet le client qui viens de se connecter doit aussi connaître le nombre total de users. Nous ajouterons donc la ligne suivante dans la première zone (Arrivée d'un client) :
sendToAll(null,"NBUSER=".count($clients));
Il nous faut faire la même chose si un client se déconnecte. En revanche, ici nous indiquerons à la fonction
sendToAll() qu'il ne faut pas envoyer le message au client qui s'est déconnecté. Nous ajouterons donc la ligne suivante :
sendToAll($clients[$i]["UID"],"NBUSER=".(count($clients)-1));
Nous allons maintenant traiter le message des clients. Chaque client nous envoie un message quand il faut dessiner une ligne sur notre tableau, l'effacer etc... Pour faire la distinction entre chaque commande, chaque message d'un client serra composé de l'action à faire suivie par des paramètres et le tout séparé par un caractère spécial (nous choisissons le signe "=").
Il nous faut d'abord décider du format des messages envoyés par un client. Voici un exemple des messages que le serveur peut recevoir :
SETCOLOR=1256C'est au démarrage du swf que nous déciderons de la couleur. Le client transmettra donc sa couleur au serveur. Ainsi, plus tard, le client n'aura qu'a envoyer les informations de positions pour dessiner la ligne sans envoyer la couleur. On y gagne en bande passante et en rapidité (même si dans ce cas, c'est franchement négligeable).
CLEARIci un client demande d'effacer le tableau.
LINE=10=20=100=100Un client viens de dessiner une ligne. Les deux premiers paramètres correspondent au premier point de la ligne en x et y, les deux suivants correspondent au second point en x et y.
Le message du client se trouve donc dans la variable
$input. Nous allons décomposer cette variable afin d'en connaître les composants et tester de quelle action il s'agit :
$comp=split("=", $input);
if($comp[0]=="SETCOLOR"){
}else if($comp[0]=="CLEAR"){
}else if($comp[0]=="LINE"){
}
Nous avons ici un léger problème. Comme expliqué dans
ce post , flash utilise un byte 0 pour terminer ses messages. Donc le message d'effacement du tableau sera équivalent à "CLEAR".chr(0)
Du coup notre "if" ne sera pas validé puisque il teste un simple "CLEAR". D'une manière générale, nous allons retirer tous caractères de fin envoyé par les clients. Pour cela nous allons utiliser la fonction php
trim() :
$input=trim($input);
$comp=split("=", $input);
if($comp[0]=="SETCOLOR"){
}else if($comp[0]=="CLEAR"){
}else if($comp[0]=="LINE"){
}
Il ne nous reste plus qu'a traiter chaque action.
$input=trim($input);
$comp=split("=", $input);
if($comp[0]=="SETCOLOR"){
$clients[$i]["COLOR"]=$comp[1];
}else if($comp[0]=="CLEAR"){
sendToAll($clients[$i]["UID"],"CLEAR");
}else if($comp[0]=="LINE"){
sendToAll($clients[$i]["UID"],"LINE=".$comp[1]."=".$comp[2]."=".$comp[3]."=".$comp[4]."=".$clients[$i]["COLOR"]);
}
Dans l'action
SETCOLOR nous nous contentons de définir la variable "COLOR" du client. Pour les autres actions, nous envoyons les messages aux autres clients. Pour l'action
LINE, nous reconstituons le message afin d'ajouter à la fin la couleur du client qui envoie la commande.
Il y'a ici de nombreuses façons de procéder : Nous pourrions informer chaque clients de la couleur de tous les autres une fois pour toutes dès le début afin de ne plus s'en préoccuper côté serveur. Nous pourrions aussi transmettre la couleur directement dans le message du client qui demande à dessiner une ligne, mais cela ferait des messages plus long et plus gourmands en bande passante.
Le code est donc :
<?php
$mainsock = socket_create(AF_INET, SOCK_STREAM, 0);
socket_set_option($mainsock, SOL_SOCKET,SO_REUSEADDR, 1);
socket_bind($mainsock, "127.0.0.1", 22222) or die('Could not bind to address');
socket_listen($mainsock);
///////////////////
$clients=Array();
$compteur=0;
echo("En attente d'un client !\n");
while(true){
$toread=Array();
array_push($toread,$mainsock);
for ($i=0;$i<count($clients);$i++){ // pour tous les clients
array_push($toread,$clients[$i]["SOCKET"]);
}
socket_select($toread,$a=null,$a=null,$a=null);
if(in_array($mainsock, $toread)){// le mainsock est dans le tableau $toread.
// c'est notre mainsock donc un nouveau client
$sock=socket_accept($mainsock);
echo("Arrivée d'un nouveau client !\n");
$compteur++;
$nb=count($clients);
$clients[$nb]["SOCKET"]=$sock;
$clients[$nb]["UID"]=$compteur;
//////////////////////////////////////////////////Arrivée d'un client
sendToAll(null,"NBUSER=".count($clients));
//////////////////////////////////////////////////
}else{
// c'est un client qui dit quelque chose
for ($i=0;$i<count($clients);$i++){ // on cherche le client
if(in_array($clients[$i]["SOCKET"], $toread)) { // celui la est dans le tableau toread
$input = socket_read($clients[$i]["SOCKET"], 1024);
if($input==null){
/// deconnection du client !
for ($j=0;$j<count($clients);$j++){ // on le cherche dans le tableau
if($clients[$j]==$clients[$i]){ // trouvé
//////////////////////////////////////////////////Perte d'un client
sendToAll($clients[$i]["UID"],"NBUSER=".(count($clients)-1));
//////////////////////////////////////////////////
echo("Deconnection du client ".$clients[$j]["UID"]."\n");
array_splice($clients,$j,1); // on le retire du tableau
$i--;
}
}
}else{
//////////////////////////////////////////////////Message d'un client
$input=trim($input);
$comp=split("=", $input);
if($comp[0]=="SETCOLOR"){
$clients[$i]["COLOR"]=$comp[1];
}else if($comp[0]=="CLEAR"){
sendToAll($clients[$i]["UID"],"CLEAR");
}else if($comp[0]=="LINE"){
sendToAll($clients[$i]["UID"],"LINE=".$comp[1]."=".$comp[2]."=".$comp[3]."=".$comp[4]."=".$clients[$i]["COLOR"]);
}
//////////////////////////////////////////////////
}
}
}
}
}
function sendToAll($sauf,$msg){
global $clients;
for($i=0;$i<count($clients);$i++){ // pour tous les clients sauf celui qui envoie
if($sauf!=$clients[$i]["UID"]){
socket_write($clients[$i]["SOCKET"],$msg.chr(0));
}
}
}
php?>
A ce stade le serveur est prêt. Nous allons maintenant nous occuper du client flash. Nous allons reprendre le code
de ce tutorial :
var socket = new XMLSocket()
socket.connect("127.0.0.1", 22222)
socket.onConnect = function(success){
}
socket.onData=function(msg){
}
Nous allons aussi installer les éléments sur la scène :
Une zone de dessin nommée "tableau".
Un bouton effacé nommée "bt_clear".
Un champ de texte dynamique pour afficher le résultat de connection nommée "txt_info".
Un champ de texte dynamique pour afficher le nombre de connectés nommée "txt_nbcon".
Vous trouverez le fla (fla_etape_1.fla) à cette étape en bas de la page.
Nous allons indiquer dans le
onConnect() si la connection s'est bien effectuée ou non :
socket.onConnect = function(success){
if(success){
txt_info.text="Connecté sur le port 22222 !"
}else{
txt_info.text="Impossible de se connecter au port 22222 !";
}
}
De la même façon, nous allons alerter l'internaute si la connection est perdue :
socket.onClose = function(){
txt_info.text="Connection perdue !"
}
Nous allons déterminer la couleur de ce client. Nous utiliserons la fonction
random() :
var selfColor=random(0xFFFFFF);
Quand le client se connecte, il doit informer le serveur de sa couleur :
var selfColor=random(0xFFFFFF);
socket.onConnect = function(success){
if(success){
socket.send("SETCOLOR="+selfColor);
txt_info.text="Connecté sur le port 22222 !"
}else{
txt_info.text="Impossible de se connecter au port 22222 !";
}
}
Nous allons gérer la commande "CLEAR" :
bt_clear.onRelease=function(){
socket.send("CLEAR");
tableau.clear();
}
Maintenant nous allons donner la possibilité au client de dessiner. Pour cela, quand on clique sur le tableau, il faut initialiser un gestionnaire "onEnterFrame()" et mémoriser la position du click. Dans le "onEnterFrame", on dessine la ligne, informe le serveur et modifie la valeur X et Y du premier point. Quand on relâche la souris, on stoppe le gestionnaire "onEnterFrame()" :
tableau.onPress=function(){
Xpos=this._xmouse;
Ypos=this._ymouse;
onEnterFrame=function(){
tableau.lineStyle(2,selfColor,100)
tableau.moveTo(Xpos,Ypos);
tableau.lineTo(tableau._xmouse,tableau._ymouse);
socket.send("LINE="+Xpos+"="+Ypos+"="+tableau._xmouse+"="+tableau._ymouse)
Xpos=tableau._xmouse;
Ypos=tableau._ymouse;
}
}
tableau.onRelease=tableau.onReleaseOutside=function(){
delete onEnterFrame;
}
Le code de la première clé du fla est maintenant :
var socket = new XMLSocket()
var selfColor=random(0xFFFFFF);
socket.connect("127.0.0.1", 22222)
socket.onClose = function(){
txt_info.text="Connection perdue !"
}
socket.onConnect = function(success){
if(success){
socket.send("SETCOLOR="+selfColor);
txt_info.text="Connecté sur le port 22222 !"
}else{
txt_info.text="Impossible de se connecter au port 22222 !";
}
}
socket.onData=function(msg){
}
bt_clear.onRelease=function(){
socket.send("CLEAR");
tableau.clear()
}
tableau.onPress=function(){
Xpos=this._xmouse;
Ypos=this._ymouse;
onEnterFrame=function(){
tableau.lineStyle(2,selfColor,100)
tableau.moveTo(Xpos,Ypos);
tableau.lineTo(tableau._xmouse,tableau._ymouse);
socket.send("LINE="+Xpos+"="+Ypos+"="+tableau._xmouse+"="+tableau._ymouse)
Xpos=tableau._xmouse;
Ypos=tableau._ymouse;
}
}
tableau.onRelease=tableau.onReleaseOutside=function(){
delete onEnterFrame
}
Nous allons maintenant nous occuper de ce que le serveur nous envoie. Comme pour le serveur, il nous faut d'abord décomposer le message afin d'en connaître l'action :
socket.onData=function(msg){
var comp=msg.split("=");
if(comp[0]=="CLEAR"){
}else if(comp[0]=="LINE"){
}else if(comp[0]=="NBUSER"){
}
}
Maintenant, chaque action :
socket.onData=function(msg){
var comp=msg.split("=");
if(comp[0]=="CLEAR"){
tableau.clear();
}else if(comp[0]=="LINE"){
tableau.lineStyle(2,comp[5],100);
tableau.moveTo(comp[1],comp[2]);
tableau.lineTo(comp[3],comp[4]);
}else if(comp[0]=="NBUSER"){
txt_nbcon.text=comp[1]+" connectés";
}
}
Et voila :-)
Au final, le code sur la première clé ressemble à ceci :
var socket = new XMLSocket()
var selfColor=random(0xFFFFFF);
socket.connect("127.0.0.1", 22222)
socket.onClose = function(){
txt_info.text="Connection perdue !"
}
socket.onConnect = function(success){
if(success){
socket.send("SETCOLOR="+selfColor);
txt_info.text="Connecté sur le port 22222 !"
}else{
txt_info.text="Impossible de se connecter au port 22222 !";
}
}
socket.onData=function(msg){
trace(msg)
var comp=msg.split("=");
if(comp[0]=="CLEAR"){
tableau.clear();
}else if(comp[0]=="LINE"){
tableau.lineStyle(2,comp[5],100);
tableau.moveTo(comp[1],comp[2]);
tableau.lineTo(comp[3],comp[4]);
}else if(comp[0]=="NBUSER"){
txt_nbcon.text=comp[1]+" connecté(s)";
}
}
bt_clear.onRelease=function(){
socket.send("CLEAR");
tableau.clear()
}
tableau.onPress=function(){
Xpos=this._xmouse;
Ypos=this._ymouse;
onEnterFrame=function(){
tableau.lineStyle(2,selfColor,100)
tableau.moveTo(Xpos,Ypos);
tableau.lineTo(tableau._xmouse,tableau._ymouse);
socket.send("LINE="+Xpos+"="+Ypos+"="+tableau._xmouse+"="+tableau._ymouse)
Xpos=tableau._xmouse;
Ypos=tableau._ymouse;
}
}
tableau.onRelease=tableau.onReleaseOutside=function(){
delete onEnterFrame
}
Vous pouvez télécharger le fla (fla_etape_2.fla) ou observer le résultat de ce tutorial
en ligne.