Aller au contenu principal

3 - Communication et messages

Connexions et protocoles

Pour faire interagir les différents services et types de client, on communique des messages transmis avec les protocoles HTTP et Websocket.

[Schéma archi communication]

Les cas d'utilisation d'HTTP sont limités. L'environnement partagé implique des communications fréquentes et dans les deux sens avec l'API. On ne retrouve donc ce protocole que pour les messages précédents la jonction à un espace public, c'est à dire pour la création et la jonction de salle.

Dès lors qu'on interagit avec un salon, les utilisateurs communiquent via des websockets afin de pouvoir envoyer et recevoir les notifications fréquentes nécessaires pour les synchroniser. Chacun d'eux entretien une unique connexion websocket avec l'API, c'est cette connexion qui l' identifie. Les utilisateurs de l'application Unreal perdent cette connexion lorsqu'ils rejoignent le serveur Unreal associé à leur salon, les messages qui leurs sont destinés sont transmis au serveur qui les valide et les distribue.

Les flux médias ?


Messages principaux

La logique de ce démonstrateur dépend lourdement de messages échangés d'un bout à l'autre de l'architecture. Ci-après une liste non-exhaustive des messages, qui présentent les plus importants pour les use cases principaux.

HTTP

POST /Room/Create :

data : {
'id': <id de l'expéditeur>, //Inutilisé
'friendlyname': <friendlyname>
}

Réponse :

status : 201
data : {
'response': "Room created",
'roomId': <id du salon généré>
}

Requête envoyée d'un client web ou Unreal standalone à l'API pour demander la création d'un nouveau salon. L'API génère un salon et y associe la connexion websocket du demandeur en tant que premier utilisateur. En réponse à la requête, le client reçoit l'id du salon créé, ce qui permet au client web de rejoindre l'url correspondant au salon.

POST /Room/Join :

data : {
'id': <id de l'expéditeur>, //Pas utilisé
'idRoom': <id du salon ciblé>,
'friendlyname': <nom visible>
}

Réponse :

status : 200,
data : {
'response': "Room joined",
'roomId': <id du salon ciblé>
}

Requête envoyée d'un client web ou Unreal standalone à l'API pour demander à rejoindre un salon. Si le salon existe et qu'un serveur Unreal le sert, l'API associe le demandeur au salon. Il est impossible de rejoindre de cette manière un salon en attente d'un serveur Unreal. Si la réponse est positive, le client web rejoint l'url correspondant au salon.

Websocket

Les messages websocket sont des objets stringifiés au format JSON, identifiés selon leur champ "title".

YourId

title: 'yourId',
id: <uuid>

Message envoyé par l'API lors de toute connexion websocket. Inclus l'uuid associé à cette connexion.

JoinRoom

title: 'joinRoom', 
idRoom: <id du salon rejoint> //Obsolète pour les clients, dont on utilise le salon auquel ils sont associés côté API
isUEServer: true //Optionnel

Envoyé par un client à l'API après avoir rejoint un salon avec succès, en réponse l'API broadcast refreshUser. Envoyé par un serveur Unreal à l'API, il contient isUeServer : true, qui indique qu'il souhaite être associé au salon comme game server.

RefreshUser

title: 'refreshUser', 
id: <uuid du client d'origine>
users: <liste des utilisateurs web du salon>

Message envoyé par l'API aux utilisateurs d'un salon lorsqu'un nouvel utilisateur rejoint. Contient la liste des utilisateurs.

UserLeave

title: 'userLeave'
id: <id de l'expéditeur> //Pas utilisé

Message envoyé par le client web à l'API lorsqu'il retourne à l'accueil. Il est retiré du salon et les autres utilisateurs de ce dernier sont notifier du changement avec refreshUser.

StartStream

title: 'startStream'

Message envoyé par le client web à l'API lorsqu'il commence à partager audio et/ou vidéo. Démarre un processus ffmpeg associé à l'utilisateur d'origine. Change statut isStreaming de l'utilisateur à true du point de vue de l'API.

StreamInProgress

title: 'streamInProgress'
buffer: <données binaires> //Opt
id: <id du client d'origine> //Inutilisé
users: [
{
(informations d'un utilisateur du salon dont :)
isStreaming: bool
}
]

Tout message non interprétable par l'API (titre manquant par exemple) est considéré comme un buffer de média audio-vidéo. L'API écrit le contenu du buffer sur le stream correspondant à l'expéditeur. Tous les utilisateurs du salon. Le client récupère la liste des utilisateurs du salon et cherche le stream correspondant aux utilisateurs actuellement en diffusion. Il y a beaucoup d'information redondante, l'avantage est que tout nouvel utilisateur récupère tous les streams dès le premier streamInProgress reçu.

CloseStream

title: 'closeStream'

Message transmis du client à l'API, signale la fin du partage audio/vidéo. Arrête le processus ffmpeg de l'utilisateur appelant, et change son statut isStreaming à false.

StreamClosed

title: 'streamClosed'
user: <id de l'utilisateur fermant son stream>

Message transmis de l'API aux utilisateurs d'un salon lors de la réception d'un message CloseStream. Leur signale d'arrêter de lire les diffusions d'un utilisateur. Pourrait être réuni avec CloseStream.

NewDrawing

title: 'newDrawing'
tabId: <id du tableau blanc ciblé>
drawing: <données du nouveau dessin>

Message envoyé par un client web à l'API qui le transmet directement au serveur UE. Demande la validation d'un dessin local, contient les informations pour le reproduire.

AddDrawings

title: 'addDrawings'
tabx: [
{
tabId: <id d'un tableau blanc ciblé>
drawings: [
{
<données d'un dessin>
}
]
}
]

Message envoyé par le serveur Unreal à l'API pour signaler la validation d'un ou plusieurs nouveaux dessins et passer les informations pour les reproduire. Il est broadcast aux utilisateurs du salon qui les enregistrent localement et les affichent.

GetStatus

title: 'getStatus'
info: (bool)

Message envoyé à un serveur Unreal pour demander toutes les informations à son sujet. Info est faux s'il s'agit d'une requête utilisée pour mettre à jour l'état du salon. En réponse, il envoie GetWBState, GetConnectedClents et ServerInfo, complétés. Ces informations sont broadcastés dans le salon.

GetWBState

title: 'getWBState'
client: <id du client original> //Opt
state: <message addDrawings> //Opt

Message envoyé d'un client à l'API, elle renseigne client et le transmet au serveur Unreal du salon de cet utilisateur. Le serveur lui renvoie getWBState avec la liste des tableaux blancs dans l'environnement virtuel, et les données pour reproduire les dessins, sous la forme d'un message addDrawings. L'API renvoie ensuite ce dernier au client d'origine. Si GetWBState n'a pas de client d'origine, c'est que le message est à l'initiative de l'API ou du serveur Unreal et la réponse doit être broadcast dans le salon.

GetConnectedClients

title: 'getConnectedClients'
info: (bool)
clients: [
{
clientId: <id du client selon Unreal>
clientName: <nom du client selon Unreal>
}
]

Message envoyé par le serveur UE en réponse à GetStatus et lors de sa connexion. Inclus la liste de tous les clients Unreal connectés. Inclus un booléen qui indique si ces données sont purement informatives, sinon cause la destruction du salon si aucun client unreal et aucun utilisateur dans le salon.

ServerInfo

title: 'serverInfo'
port: <port de connexion vers le serveur Unreal>
id: <id associé au serveur> //Inutilisé
idRoom: <id du salon servi par le serveur> //Inutilisé

Message envoyé par le serveur UE en réponse à GetStatus et lors de sa connexion. Inclus les informations permettant d'identifier et de se connecter au serveur.


Use cases

Rejoindre un salon

Partager sa caméra

Partager un dessin (web)

Partager un dessin (unreal)


Difficulté et améliorations potentielles

Initialisation de la websocket.

Actuellement la connexion websocket client-api est démarrée automatiquement dès l'arrivée sur un client (première page web, arrivée dans l'environnement par défaut Unreal). Il serait judicieux de demander une connexion préalable via HTTP.

Définition et interprétation des messages websocket

Nous transmettons des messages d'un bout à l'autre de l'architecture. Chaque message est traité selon son titre. Pour éviter un maximum de manipulation les intermédiaires se contentent de rediriger et compléter les messages. Cela signifie que l'élément le plus strict / limité dicte le format des messages. Dans notre cas, c'est le serveur Unreal, dont le module JSON est très strict.

Pour une utilisation idéale de ses fonctionnalités, il est nécessaire de définir la structure du message au préalable (une FStruct). Les fonctions JSON fournies sont incapables d'interpréter un message de type inconnu ou à la structure mal définie, auxquels cas on peut tout de même parse et récupérer des champs individuels.

Les autres éléments de la solution sont en js et s'accomodent de n'importe quel json, capable de les manipuler comme objet ou string peu importe le contenu. Pour mieux contrôler ce qui peut leur être adressé, nous implémentons un vérificateur dans l'API (élément central de la solution). Il force certains paramètres à partir de schémas simples, comme la taille des id ou la présence d'un titre par exemple.

Si les messages des premières features implémentées dans ce démonstrateur étaient conçus de sorte à limiter leur variété tout en étant applicable à un maximum de use cases, les améliorations qui ont suivi ont nécessité de les multiplier et de les revoir. A force d'ajouts rapides, les messages actuels sont parfois très spécifiques ou, à l'inverse, donnent beaucoup plus d'information que nécessaire. Il faudrait revoir les échanges et les messages pour revenir à quelque chose de plus maintenable et clair.

Authentification des serveurs Unreal

Pour être associé à un salon, un serveur Unreal doit envoyer un message joinRoom avec l'option isUEServer true. N'importe qui peut accéder à l'id d'un salon et donc se faire passer pour son game server en s'y connectant avec cette option avant qu'il soit servi. Pour éviter cette faille, nous pourrions ajouter une clef d'authentification secrète aux paramètres de démarrage du serveur, de la même manière qu'on passe l'id du salon auquel il doit se connecter au lancement.