Communication entre animations Flash, grâce à XMLSocket
Il existe plusieurs manières de permettre à des animations Flash de communiquer entre elles, de la solution la plus lourde impliquant des flux divers (datas, vidéos, audio…) au simple échange de fragments de texte.
Macromedia, en son temps, avait ouvert la voix avec Flash Communication Server (devenu Adobe Media Server). Au fil du temps, d’autres solutions sont venus compléter cette proposition. Côté artillerie lourde, la passerelle AMF PHP est venu de manière gratuite, proposer ses services et permet également la gestion de flux audio et vidéo.
Mais il est vrai, que bien souvent, il n’est pas nécessaire de déménager des montagnes afin de permettre à deux animations Flash, d’échanger simplement quelques bribes d’informations. Tout n’est alors question que de contexte :
Les animations se trouvent-elles sur la même machine, ou du moins dans le même environnement d’exploitation, ou sont-elles déployées au travers du réseau sur des machines distinctes ?
Dans le premier cas, nous pourrons avoir recours à la classe local connexion, ou tout autre dérivé basé sur AsBroadcaster, pour l’autre, nous pouvons nous appuyer sur l’objet XML Socket.v1.11 Fichier temporaire du développement – XMLSocket
L’objet et les serveurs XML Socket
La classe XML Socket a été mise en place aussi bien en AS2 qu’en AS3, pour permettre à des animations Flash d’utiliser des Sockets de communication. Ces sockets sont en fait des mécanismes qui permettent des échanges d’informations textuelles, en entrée et en sortie, se basant sur le protocole TCP/IP.
Afin de permettre le bon fonctionnement des ces échanges, il est nécessaire d’utiliser un serveur XML Socket adapté. Il en existe différent modèles, allant de l’open source aux solutions propriétaires :
Pour illustrer cette prise en main, nous allons utiliser le serveur AquaServer mis en place par Brenden Hall de FigLeaf. Il est difficile aujourd’hui de le trouver sur le web, mais quelques tutoriels le propose encore en téléchargement, notamment sur le site du zéro.
Cahier des charges de la prise en main
Basons nous sur le cas d’étude concret que rencontre la société pour qui ce tutoriel a été écrit. Côté serveur, un simulateur industriel écrit en un langage non web, qui doit communiquer en envoyant des données de manières récurrentes.
Côté client, deux types d’animations Flash qui les réceptionnent. Une première animation qui peut également envoyer des données vers le système et une seconde, complètement passive, qui ne pourra que recevoir l’ensemble des données, provenant soit du simulateur, soit de l’autre animation Flash.
Nous pouvons alors envisager de faire communiquer ce système hétérogène au travers de l’utilisation d’un socket XML. Nous allons reproduire cet environnement au travers de trois animations Flash, une côté serveur et deux côté client.
Distinguons l’unique animation côté serveur comme étant le cœur de l’application et deux animations côté client. La première qui ne peut que recevoir des données (IN) et la seconde qui peut en recevoir mais également en transmettre (IN_OUT).
Afin de pouvoir facilement les distinguer, appelons-les respectivement : serveur.fla, client-in.fla et client in-out.fla.
Lancement du serveur
Avant de mettre le serveur en activité, nous allons nous assurer de la validité du port utilisé. En fonction du serveur utilisé, il est possible d’accéder à un fichier de configuration externe et détaillé (ex : Palabre propose un fichier de configuration relativement complet ‘etc/palabre.conf’).
En ce qui concerne Aqua, tout se passe en ligne de commande. Il suffit d’ouvrir le fichier ‘lance.bat’ avec NotePad++ et de modifier si besoin est, le port 1024 utilisé par défaut. Ensuite un double clic sur ce même fichier lance le serveur et ouvre l’invite de commande.
Trois commandes qui parlent d’elles mêmes sont alors accessibles :
>HELP >LIST >QUIT
Test de connexion au serveur XML Socket
Avant d’aller plus loin dans l’étude complète, nous allons vérifier le bon fonctionnement du serveur et étudier les diverses phases de la communication à mettre en place. Pour une simple et unique animation, Flash fera l’affaire.
Créons un fichier Flash AS2 et enregistrons-le sous connection_socket_01.fla. Ouvrir la fenêtre Actions et saisir le code ci-dessous :
var xmlSocket = new XMLSocket(); xmlSocket.onConnect=function(arg) { if (arg == true) { trace("Connexion réussie") } } xmlSocket.connect("127.0.0.1",1024);
Le code parle de lui-même, il suffit de créer une instance de l’objet XMLSocket et de pointer la fonction de connexion vers l’URL du serveur : Dans notre cas, le serveur local, en précisant le port utilisé au chapitre précédent.
Le gestionnaire d’évènements de connexion permet d’intercepter la connexion et de s’assurer par le paramètre arg reçu, du bon déroulement des opérations. Lancez donc cette animation et la fenêtre de sortie devrait afficher le message « Connexion réussie ».
Échanges de données avec le XML Socket
Une fois la connexion établie, nous allons mettre en place un modèle d’envoi/réception de données au format XML. Un simple écouteur sur la scène permettra d’envoyer des infos sur un clic souris. Enregistrez l’animation sous connection_socket_02.fla et ajouter l’écouteur au code déjà présent :
var ecouteur = new Object() ecouteur.onMouseDown = function(){ trace("coucou") } Mouse.addListener(ecouteur)
Un test rapide nous permet de nous assurer que tout fonctionne correctement. Enregistrons donc l’animation sous connection_socket_03.fla et remplaçons l’instruction trace(« coucou ») par l’envoi d’un message au serveur XMLSocket.
Pour faire simple, nous pouvons à ce stade utiliser une instance XML créant à la volée une instruction XML littérale :
ecouteur.onMouseDown = function(){ var xml_a_envoyer = new XML('<message>Ceci est un texte accentué</message>') xmlSocket.send(xml_a_envoyer) }
Pour intercepter ce message, nous allons définir un écouteur sur l’évènement onXML de la classe XMLSocket.
Pour information, nous avons accès à deux types d’écouteurs : onData et onXML. Le premier s’émet lorsqu’un message a été téléchargé depuis le serveur, le second lorsqu’un message XML est reçu. Bien que les deux soient utilisables dans cette situation, nous utiliserons le second :
xmlSocket.onXML=function(xmlrecu) { trace(xmlrecu) }
Lors du premier test, tout fonctionne correctement, y compris la gestion du caractère accentué, mais si nous cliquons plusieurs fois, le message renvoyé contient à la fois, une chaîne littérale et un ensemble de caractères invisibles, tels que des retours chariots ou des espaces.
Ceci ne nous permet pas d’utiliser correctement les informations reçues. Pour remédier à cela, nous devrions faire appel à la propriété ignoreWhite de l’objet XML en l’initialisant à true (par défaut, elle est initialisée à false), afin de demander au player Flash de ne pas prendre en compte ces caractères, en les traitant comme des nœuds.
Le souci reste que cette instruction se doit d’être utilisée sur la classe XML avant que tout contenu XML n’y soit défini. Il nous faut donc intervenir au niveau du prototype de la classe, afin d’agir par défaut sur l’ensemble des instances XML qui sont créées.
Ajoutons donc une première instruction à notre code et testons à nouveau :
XML.prototype.ignoreWhite = true;
Mise en place d’un composant et de ses classes dérivées
Afin de représenter les actions du simulateur et les modifications de certains paramètres, ou informations transmises, nous allons mettre en place un composant proposant la représentation visuelle d’une donnée et de sa valeur.
Nous ajouterons la possibilité d’agir manuellement sur la valeur affichée. Le composant doit être décliné en deux représentations, celle utilisée par la partie serveur.fla et celle utilisée par les parties client_in.fla et client_in_out.fla.
Nous ne détaillerons pas la mise en place des classes utilisées (step.as et steppeur.as) , où seules les fonctions constructeurs sont renseignées, afin d’initialiser correctement les valeurs par défaut du composant ainsi que la mise en place des écouteurs permettant de modifier manuellement les valeurs affichées.
Dressons uniquement le tableau des variables d’instances et de classes pour définir leur rôle et utilisation :
Variables | Types | Classes | Descriptions |
---|---|---|---|
moins | MovieClip | steppeur | Clip représentant le bouton moins du steppeur |
plus | MovieClip | steppeur | Clip représentant le bouton plus du steppeur |
valeur | TextField | steppeur – step | Textfield représentant le conteneur de la valeur du steppeur |
nom | TextField | steppeur – step | Textfield représentant le conteneur du libellé du composant du steppeur |
titre | String | steppeur – step | Chaîne de caractères représentant le libellé du steppeur |
defaut | Number | steppeur | Valeur du composant du steppeur |
id | String | steppeur – step | Identifiant permettant de pointer vers le composant |
composant | Object | steppeur – step | Objet permettant de recenser l’ensemble des composants steppeur ou step utilisés par les animations. Chaque objet est alors identifié par son id |
En complément de la classe adjointe au symbole du steppeur et du step, nous devrons définir certaines propriétés de composant. Nous reviendrons sur cette étape lors de la réalisation des diverses animations serveur et clients. Commençons par le simulateur côté serveur…
Création de l’animation serveur.fla
Comme nous l’avons vu plus tôt, il n’y a pas de réelle complexité à mettre en place la couche XML Socket… reprenons donc le code comme nous l’avions écrit lors du premier test. Automatisons simplement au passage l’envoi de données, au travers d’un Timer, qui fera cela en toute autonomie toutes les X millisecondes :
XML.prototype.ignoreWhite = true; var timer; var xmlSocket:XMLSocket = new XMLSocket(); xmlSocket.onConnect = function(arg) { timer = setInterval(sendDatas, 1000); }; xmlSocket.onXML = function(argXML) { trace(argXML) }; xmlSocket.connect("127.0.0.1",1024); function sendDatas() { var xml = new XML('<message>coucou</message>'); xmlSocket.send(xml); }
Avant de formater la chaîne XML en fonction de nos besoins, mettons en place les divers composants issus de la classe steppeur.as. Plaçons trois instances sur la scène et personnalisons-les pour refléter divers types de valeurs que pourrait renvoyer un réel simulateur industriel… disons la température, le débit et la pression d’une machine hydraulique.
Personnalisons-les au travers des propriétés de symboles. Pour cela, sélectionnons le symbole dans la bibliothèque et depuis le menu contextuel, optons pour « Définition du composant ». Définissons trois séries de paramètres en fonction du tableau suivant :
Nom | Variable | Valeur | Type |
---|---|---|---|
ID | id | {a,b,c,d,e,f} | List |
Label | titre | Composant | String |
Valeur par défaut… | defaut | 100 | Number |
Ensuite, il suffit de sélectionner tour à tour chacune des instances présentes sur la scène et depuis l’inspecteur de propriétés, dans la rubrique Paramètres de composant, nous pouvons renseigner les valeurs suivantes :
Composant | ID | Label | Valeur par défaut… |
---|---|---|---|
1 | a | Température | 154 |
2 | b | Débit | 5 |
3 | c | Pression | 28 |
La première phase de l’animation est maintenant terminée, et avant d’aller plus loin, il faut nous assurer de formater les données représentées par ces composants vers le Socket. Ainsi, il nous faut maintenant nous tourner vers le schéma que le XML doit adopter et tout en le structurant, le renseigner des données adéquates. Pour cela, nous allons regarder de plus près, et paradoxalement avec du recul, cette communication XML.
Modélisation du fichier XML utilisé
Le fichier XML va devoir transporter et formater l’ensemble de l’information, d’une part pour les composants mis en place à l’étape précédente, mais également à d’autres fins, comme les messages textuels, les instructions de FOAD et les messages remontant vers le simulateur.
Pour l’instant, nous pouvons distinguer au moins quatre familles principales :
Balise / Familles | Description et utilité |
---|---|
message | Des messages textuels de type IRC ou chat en échange entre les diverses animations utilisées (toutes pourront les recevoir, mais seules serveur.fla et client_in_out pourront les transmettre) |
simulateur | Les informations provenant du simulateur (serveur.fla) à destination de chaque (client_in) et (client_in_out) |
foad | Les informations propres aux échanges entre applications clients (de client_in_out vers client_in) |
instruction | Les instructions qui vont remonter de l’animation client_in_out vers le simulateur (serveur) |
Nous pouvons alors envisager un fichier XML contenant 4 nœuds principaux correspondant chacun à l’une des familles vus précédemment. Les attributs de chacun de ces noeuds viendront affiner la nature et le format des contenus transportés.
Le tableau ci-dessous va permettre d’en identifier et d’en qualifier certains :
Balise | Attribut | Description |
---|---|---|
communication | rel | permet d’identifier la nature de l’animation émettrice |
composant | moins | MovieClip écouteur de l’action de décrémentation de la valeur |
composant | plus | MovieClip écouteur de l’action d’incrémentation de la valeur |
composant | nom | TextField contenant le libellé du composant |
composant | valeur | TextField contenant la valeur du composant |
composant | id | Identifiant du composant (dans une variable de classe) |
composant | titre | Libellé du composant |
composant | defaut | Valeur du composant |
Nous pouvons d’ores et déjà dresser un premier profil du fichier XML à mettre en place :
<?xml version="1.0" encoding="utf-8"?> <communication rel=""> <message></message> <simulateur> <composant moins="" plus="" nom="" valeur="" id="" titre="" defaut="" /> <!-- D'autres composants --> </simulateur> <foad></foad> <instruction></instruction> </communication>
Intégration de ce fichier XML dans l’animation serveur.fla
Afin de générer le fichier XML, nous avons deux possibilités : soit utiliser une chaîne de caractères que l’on utilisera comme un XML littéral, soit utiliser le DOM, créer chaque élément en tant que XMLNode et construire ainsi pas à pas, l’arborescence globale.
Nous allons utiliser la seconde piste et mettre en ce sens en place une fonction qui nous permettra de répéter les tâches de manière plus simple et évolutive. De quoi avons nous besoin :
- Construire un nœud
- Lui ajouter un contenu de type Nœud de texte
- Ajouter l’ensemble des attributs de ce nœud
- Et enfin, attacher ce nœud au reste de l’arborescence
A cet effet, créons la fonction xmlise(xml, nœud, val, args) qui attend quatre paramètres, à savoir l’arborescence globale, le nœud, son contenu textuel et un objet représentant l’ensemble des attributs :
function xmlise(xml, noeud, val, args) { var node = xml.createElement(noeud); if (val != null) { var textNode = xml.createTextNode(val); node.appendChild(textNode); } for (var i in args) { node.attributes[i] = args[i]; } return node; }
Son utilisation est on ne peut plus simple… Vérifions-le directement au sein de la fonction sendDatas(). N’oublions pas que cette fonction est invoquée de manière automatique par le Timer. Commençons par l’envoi de la partie <message>, en n’oubliant pas d’ajouter le script ci-dessous et de placer un texte par défaut dans le champs texte du message, avant de tester l’animation :
function sendDatas() { var xml = new XML(); var doc = xmlise(xml, "communication", null, {rel:"serveur"}); doc.appendChild(xmlise(xml, "message", msg_field.text, {})); xml.appendChild(doc); xmlSocket.send(xml); }
Si l’on prend soin de traiter l’information depuis la fonction xmlSocket.onXML, la fenêtre de sortie doit bien renvoyer le message toutes les X millisecondes. Poursuivons en transmettant cette fois-ci les divers paramètres de composant.
S’il est vrai que la balise <message> ne contient qu’une chaîne de caractères qui peut être ajoutée comme un simple TextNode, il en est différemment pour la balise <simulateur>, qui elle, contient d’autres balises de type <composant>. Chacune de ces balises devant refléter les divers composant présents sur la scène.
Nous allons filtrer cette situation et adapter notre fonction à cette particularité. Le plus simple est d’user d’un switch / case pour cela :
if (val != null) { switch (noeud) { case "message" : break; case "simulateur": break; } }
Rappelons-nous que dans notre classe steppeur.as, nous avons une variable de classe, composant, qui est en fait un objet contenant chacune des instances de la classe… utilisons-là pour créer chacune des balises <composant>. Rien de plus simple… invoquons l’ajout du nœud <simulateur> depuis la fonction sendDatas avec les paramètres adéquats :
doc.appendChild(xmlise(xml, "simulateur",steppeur.composant , {}));
et adaptons le code au sein du case « simulateur » de la fonction xmlise() :
case "simulateur": for (var i in val){ node.appendChild(xmlise(xml, "composant", null,val[i])); } break;
Bien qu’ils ne soient pas utiliser pour l’instant, il ne nous reste plus qu’à invoquer la création des deux autres nœuds de premier niveau, afin de finaliser la création du fichier XML.
doc.appendChild(xmlise(xml, "foad", null, {})); doc.appendChild(xmlise(xml, "instruction", null, {}));
Création de l’animation client_in.fla
Passons maintenant à la première animation client, qui va permettre de recevoir et afficher les informations envoyées par le simulateur. La base de la connexion au socket reste identique, seules les instructions d’affichage vont être ajoutées à l’interface. Tout va donc être piloté par la fonction écouteur onXML().
Dans un premier temps, afin de s’assurer de la bonne réception des infos, plaçons un simple trace renvoyant l’argument reçu :
xmlSocket.onXML = function(argXML) { trace(xml) };
Cette fois-ci, pour tester l’animation, nous avons besoin de lancer au préalable l’animation serveur.fla afin de générer l’envoi de données côté simulateur. Une fois lancée, cette animation pourra continuer en tâche de fond, afin de ne pas systématiquement devoir lancer les deux animations à chaque fois.
La fenêtre de sortie doit afficher les informations reçues et en regardant de plus près, il peut y avoir un problème du côté des caractères spéciaux, les caractères accentués en particuliers. Le plus simple, pour ne pas générer ce genre d’erreur, est de doter chacune de nos animations, dès la première ligne de script, d’une instruction de gestion de l’encodage :
System.useCodepage = true;
La récupération des données dans l’argument argXML revient à une simple manipulation de données XML qui vont devoir être réinjectées dans les composants ou parties appropriés de l’animation. Il n’y a rien en AS2 qui nous permette de cibler directement un nœud par son nodeName.
Nous allons devoir parser l’ensemble de l’information pour en extraire le contenu au fur et à mesure. Dans un premier temps, nous allons hard coder l’extraction des données correspondantes à la balise <simulateur>. Il s’agit en fait du deuxième nœud de la racine du document soit : racine.firstChild.childNodes[1].
Une fois ce nœud récupéré, concentrons-nous sur la récupération des diverses données de chaque composant. En parsant les nœuds contenus dans la balise <simulateur>, nous pouvons tour à tour accéder à chaque composant.
Rappelons-nous que l’ensemble de ces données sont, d’une part contenus dans les attributs de la balise et d’autre part, dans les variables d’instance du composant. De même, chaque instance de composant est contenu dans la variable de classe composant de la classe step.as et reste accessible depuis son identifiant : soit step.composant[id].
Il suffit alors de pointer vers une fonction au sein de la classe qui va pour chaque instance appliquer la valeur adhoc :
var simulateur = argXML.firstChild.childNodes[1]; for (var i = 0; i < simulateur.childNodes.length; i++) { var att = simulateur.childNodes[i].attributes; step.composant[att['id']].afficheComposant(att['defaut']); }
Afin de rendre ce code plus évolutif, et de ce fait moins figé par cette version hard codée, mettons en place une boucle qui va parser tour à tour les nœuds racines de l’argument argXML.
A chaque itération, nous pourrons comparer la valeur du nodeName et au travers d’un switch / case affecter le traitement nécessaire. Le code est un peu plus détaillé mais reste très explicite :
var argXML = argXML.firstChild; for (var nd = 0; nd < argXML.childNodes.length; nd++) { var ndTemp = argXML.childNodes[nd]; switch (ndTemp.nodeName) { case "simulateur" : for (var i = 0; i < ndTemp.childNodes.length; i++) { var att = ndTemp.childNodes[i].attributes; step.composant[att['id']].afficheComposant(att['defaut']); } break; } }
La première partie de l’information envoyée est bien retranscrite sur les composants. Prenons maintenant en compte le message textuel. Il suffit de rajouter une condition à la structure switch / case pour récupérer et traiter la balise adéquate, en récupérant le nodeValue de la balise.
Nous allons également externaliser la gestion de cette fonctionnalité sur une fonction externe à la boucle, afin d’anticiper un éventuel accès depuis un autre point du programme.
case "message" : afficheMessage(ndTemp.firstChild.nodeValue) break;
function afficheMessage(arg){ msg_field.text = arg }
Création de l’animation client_in_out.fla
crossdomain.xml
http://help.adobe.com/en_US/AS2LCR/Flash_10.0/help.html?content=00000471.html