ASP 4.5 : Tout pour être bien dans ses sockets

Intro

Pour commencer petit rappel sur ce que sont les Websockets. C’est un protocole de communication au même titre que HTTP.

Mais le principal intérêt de ce protocole est qu’il est bi directionnel, le client appelle le serveur mais surtout le serveur peut appeler le client.

Il est full duplex, le client peut appeler le serveur pendant que celui-ci l’appelle et réciproquement.

L’autre gros avantage est la faible taille de ces requête, en effet contrairement au protocole HTTP, une fois la communication établie la taille des données en plus du message à faire passer reste minime (2 octets supplémentaires seulement).

Une question que je me suis posée est : est-ce que ce protocole est compatible avec les proxies, firewalls et autres routeurs NAT. La réponse est oui. Ce qu’il faut savoir c’est qu’avant d’établir la connexion entre le client et le serveur il y a un “handshake” entre les deux via HTTP, en gros :

Requête Http : client –> serveur :“salut je ferais bien du websocket moi car je sais faire et je voudrais faire du protocole ws plus tôt”

Réponse Http : serveur –> client : “ça tombe bien moi aussi je sais faire, et ok pour le protocole ws”

La communication est ainsi établie et le protocole ws:// peut être utilisé ou sont équivalent sécurisé le wss:// on appel cela un “upgrade” de connexion.

Les ports utilisés sont alors les même que ceux des protocoles HTTP et HTTPS à savoir 80 et 443, donc pas de reconfiguration des ports.

Maintenant un proxy étant un élément “intelligent”, sans entrer dans en profondeur, le proxy lit le contenu des requêtes qui passent et en fonction de se qu’il lit, peut ou pas accepter la requête. Si bien que si on utilise une connexion https/wss (cryptée donc) la plupart des proxys laisseront passer la connexion ne sachant pas lire son contenu. Ce que je viens de vous dire vous passionne? Un petit tour Ici vous passionnera encore plus.

Et bien entendu, c’est le standard HTML5 qui nous apporte cette nouvelle fonctionnalité.

 

Pour quoi faire ?

Pourquoi utiliser les WebSockets me direz vous? Là où un “parceee quuueee” suffirait à certains, je répondrai aux autres:

  • Faire du client serveur en mode push  (le serveur peut appeler le client), exemple, je suis un match de foot via mon site web préféré. Actuellement le navigateur, via de l’ajax, va appeler le serveur toutes les 30  secondes pour rafraichir le score. S’il y a 10000 utilisateurs et que pendant 90 minutes il n’y a pas de but on aura effectué 180 0000 requêtes pour rien, au lieu de 10 000 (pour afficher la première fois) avec des WebSockets et qui sont en plus moins gourmandes en bande passante.
  • De par sa faible encapsulation de trame (2 octets rajoutés par requêtes en plus du message transporté) et son mode push on peut arriver à faire du client serveur se rapprochant du temps réel où faire un jeu multijoueurs ou une application où il est nécessaire d’avoir des informations en temps réel comme la bourse ou des applications de monitoring de remonté d’alerte temps réel.

 

  

Prérequis

Maintenant vous savez de quoi on parle, un petit tour sur les prérequis avant de rentrer dans le sujet, vous aurez donc besoin :

  • un client compatible (version minimum ):
    • IE9 (dans ses dernières mises à jours)
    • Chrome 13 et plus
    • Firefox 7 et plus
    • Safari 5 et plus
    • Opera 11 et plus
  • IIS8  disponible avec Windows 8 avec la fonctionnalité “web socket” activée :

iis8_websocket_feature

  • Coté framework vous l’aurez compris .NET 4.5
  • Coté IDE un Visual Studio 11 Beta conviendra parfaitement

 

Y a plus qu’à

Pour mettre tout ça en pratique j’ai réalisé un petit chat, programme qui met tout à fait en application l’utilisation des WebSockets.

  • Coté client une simple page html5 et du JavaScript fera l’affaire :
 <script type="text/javascript">
        //Initialize WebSocket
        var socket;
        function connect() {
            var username = $("#usernameTxtb").val();
            var host = "ws://localhost:81/WebSocket/wsHandler.ashx?u=" + username;

            socket = new WebSocket(host);
            //Action on connect
            socket.onopen = function () { socket.send(username + " connected"); }
            //Action on disconnect
            socket.onclose = function () { socket.send(username + " disconnected"); }
            socket.onmessage = function (msg) {
                $("<li/>").html(msg.data).appendTo($("#contentTxtAr"));
            }

        }
        //Send Message to WebSocket Server
        function send(message) {
            socket.send(message);
            $("#messageTxtb").val("");
        }
        $(function () {
            $("#connectBut").click(function () { connect() });
            $("#sendBut").click(function () { send($("#messageTxtb").val()) });

        });
    </script>
 <span>UserName : </span>
<input id="usernameTxtb" type="text" /><input type="button" id="connectBut" value="Connect" /><br />
<input id="messageTxtb" type="text" />
<input type="button" id="sendBut" value="Send" /><br />
<ul id="contentTxtAr"></ul>

Petit tour rapide sur le code :

    • La fonction connect , appelée sur le click du bouton “connectBut” qui sert à se connecter et qui créé un objet WebSocket en lui passant en paramètre l’adresse de la page du serveur (on notera l’utilisation du préfix “ws” et le nom de l’utilisateur en GET). A cette WebSocket on définira 3 actions, sur le “onopen”, “onclose” et “onmessage”. Cette dernière est appelée lors que le serveur envoi un message au client ici on récupère le message qui est au format texte et on ajoute sont contenu dans une balise de type <li>
    • La fonction send qui sera appelée lorsque le client veut envoyer un message

Et c’est tout coté client, assez simple je trouve !

  • Coté serveur on déclare un simple handler (ashx) :
 public class wsHandler : IHttpHandler
   {

       static Dictionary<string, WebSocket> UsersSockets = new Dictionary<string, WebSocket>();
       public void ProcessRequest(HttpContext context)
       {
           if (context.IsWebSocketRequest)
           {
               context.AcceptWebSocketRequest(MyWebSocket);

           }
       }

       //New 4.5 KeyWord async for asynchronous call
       public async Task MyWebSocket(AspNetWebSocketContext context)
       {
           //GetUserName
           var user = context.QueryString["u"];

           WebSocket socket = context.WebSocket;
           //If a user already have this Name
           if (UsersSockets.ContainsKey(user))
           {
               int i = 1;
               //Generate an unique username
               while (UsersSockets.ContainsKey(user))
               {
                   user+=i++;
               }
           }
           UsersSockets.Add(user, socket);

           //Awaiting response until connection closed
           while (true)
           {
               ArraySegment<byte> buffer = new ArraySegment<byte>(new byte[1024]);

               // Asynchronously wait for a message from a client
               WebSocketReceiveResult result = await socket.ReceiveAsync(buffer, CancellationToken.None);

               // If the socket is still open, echo the message back to the client
               //this code is executed only when last instruction is finish (socket.Receive), because of the “await Keyword”
               if (socket.State == WebSocketState.Open)
               {
                   string userMessage = Encoding.UTF8.GetString(buffer.Array);
                   userMessage = user + " " + DateTime.Now.ToString("hh:mm:ss") + ": " + userMessage;
                   buffer = new ArraySegment<byte>(Encoding.UTF8.GetBytes(userMessage));

                   //BroadcastMessage
                   foreach (WebSocket sock in UsersSockets.Values)
                   {
                       await sock.SendAsync(buffer, WebSocketMessageType.Text, true, CancellationToken.None);
                   }
               }
               else
               {
                   if (UsersSockets.ContainsKey(user))
                       UsersSockets.Remove(user);
                   break;
               }
           }
       }


   }
  

Et comme diraient les américains “Et voilà”, pas de configuration du serveur, pas de web.config spécifique!

Conclusion

Voilà un petit exemple assez simple des WebSockets, après reste à voir comment ça se passe “in real life”. Comment ces WebSockets réagiront fasse aux différents types d’architectures, la sécurité, la monté en charges… .

Voilà pour mon premier Post j’espère que vous avez tout lu, et n’hésitez pas à me faire des retours même et surtout négatifs c’est ceux qui font avancer le monde (tant qu’ils sont constructifs bien sûr)!

 

- Romain ZERT -