web-dev-qa-db-fra.com

Création d'un exemple WebSocket "Hello World"

Je ne comprends pas pourquoi je ne peux pas faire fonctionner le code suivant. Je souhaite me connecter avec JavaScript à l'application de la console de mon serveur. Et ensuite envoyer des données au serveur.

Voici le code du serveur:

    static void Main(string[] args)
    {            
        TcpListener server = new TcpListener(IPAddress.Parse("127.0.0.1"), 9998);
        server.Start();
        var client = server.AcceptTcpClient();
        var stream = client.GetStream();

        while (true)
        {
            var buffer = new byte[1024]; 
            // wait for data to be received
            var bytesRead = stream.Read(buffer, 0, buffer.Length);                
            var r = System.Text.Encoding.UTF8.GetString(buffer);
            // write received data to the console
            Console.WriteLine(r.Substring(0, bytesRead));
        }
    }

et voici le JavaScript:

        var ws = new WebSocket("ws://localhost:9998/service");
        ws.onopen = function () {
            ws.send("Hello World"); // I WANT TO SEND THIS MESSAGE TO THE SERVER!!!!!!!!
        };

        ws.onmessage = function (evt) {
            var received_msg = evt.data;
            alert("Message is received...");
        };
        ws.onclose = function () {
            // websocket is closed.
            alert("Connection is closed...");
        };

Quand je lance ce code, voici ce qui se passe:

 

Notez que lorsque je lance JavaScript, le serveur accepte et établit une connexion avec succès. JavaScript n'est pas capable d'envoyer des données cependant. Chaque fois que je mets la méthode d'envoi, elle n'enverra pas, même si une connexion est établie. Comment puis-je faire ce travail?

74
Tono Nam

WebSockets est un protocole qui repose sur une connexion TCP en streaming. Bien que WebSockets soit basé sur le protocole Message.

Si vous souhaitez implémenter votre propre protocole, je vous recommande d'utiliser la dernière spécification stable (du 18/04/12) RFC 6455 . Cette spécification contient toutes les informations nécessaires sur la prise de contact et le cadrage. De plus, il est vivement recommandé de suivre les recommandations relatives au côté serveur lors de l’implémentation de votre code.

En quelques mots, je décrirais le travail avec WebSockets comme ceci:

  1. Create server Socket} _ (System.Net.Sockets) le lie à un port spécifique et continue à écouter avec l'acceptation asynchrone des connexions. Quelque chose comme ca:

    Socket serverSocket = new Socket (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP); 
     ServerSocket.Bind (nouvel IPEndPoint (IPAddress.Any, 8080)); 
     ServerSocket.Listen (128); 
     serverSocket.BeginAccept (null, 0, OnAccept, null);
  2. Vous devriez avoir la fonction accepter "OnAccept" qui implémentera la prise de contact. À l’avenir, il doit faire partie d’un autre thread si le système doit gérer un nombre considérable de connexions par seconde.

    onAccept (résultat de IAsyncResult) {
     try {
     Socket client = null; 
     if (serverSocket! = null && serverSocket.IsBound) {
     client = serverSocket.EndAccept (result); 
     } 
     if (client! = null) {
    /* Handshaking et gestion de ClientSocket */
     } 
    } catch (exception SocketException) {
    
    } finally {
     if (serverSocket! = null && serverSocket.IsBound) {
     serverSocket.BeginAccept (null, 0, OnAccept, null); 
     } 
    } 
    }
  3. Une fois la connexion établie, vous devez faire poignée de main. Sur la base de la spécification 1.3 Ouverture de la négociation , une fois la connexion établie, vous recevrez une requête HTTP de base avec certaines informations. Exemple:

    GET/chat HTTP/1.1 
     Hôte: serveur.exemple.com 
     Mise à niveau: websocket 
     Connexion: Mise à niveau 
     Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ == 
     Origine: http: // exemple .com 
     Sec-WebSocket-Protocol: discussion en ligne, superchat 
     Sec-WebSocket-Version: 13

    Cet exemple est basé sur la version du protocole 13. N'oubliez pas que les versions les plus anciennes présentent certaines différences, mais que la plupart des dernières versions sont compatibles entre elles. Différents navigateurs peuvent vous envoyer des données supplémentaires. Par exemple, les détails du navigateur et du système d'exploitation, le cache et autres.

    Sur la base des détails de négociation fournis, vous devez générer des lignes de réponse, elles sont généralement identiques, mais contiendront Accpet-Key, qui est basée sur Sec-WebSocket-Key fournie. La spécification 1.3 décrit clairement comment générer la clé de réponse . Voici la fonction que j'utilise pour V13:

    statique chaîne privée guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"; 
     chaîne privée AcceptKey (clé de chaîne de référence) {
     string longKey = clé + guid; 
     SHA1 sha1 = SHA1CryptoServiceProvider.Create (); 
     byte [] hashBytes = sha1.ComputeHash (System.Text.Encoding.ASCII.GetBytes (longKey)); 
     return Convert.ToBase64String (hashBytes); 
    } 
    

    Poignée de main réponse ressemble à ça:

    HTTP/1.1 101 Protocoles de commutation 
     Mise à niveau: websocket 
     Connexion: Mise à niveau 
     Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK + xOo =

    Mais accepter clé doit être celle générée sur la base de la clé fournie par le client et de la méthode AcceptKey que j'ai fournie auparavant. De même, assurez-vous qu'après le dernier caractère de la clé d'acceptation, vous insériez deux nouvelles lignes "\ r\n\r\n".

  4. Une fois que la réponse de négociation est envoyée depuis le serveur, le client doit activer la fonction "onopen", ce qui signifie que vous pouvez envoyer des messages après.
  5. Les messages ne sont pas envoyés au format brut, mais ils ont Data Framing. Et de client à serveur, implémentez également le masquage des données en fonction de 4 octets fournis dans l’en-tête du message. Bien que de serveur à client, vous n’avez pas besoin d’appliquer le masquage sur les données. Lisez la section 5. Data Framing in specification . Voici un copier-coller de ma propre implémentation. Ce n'est pas du code prêt à l'emploi, et doit être modifié, je le publie juste pour donner une idée et une logique globale de lecture/écriture avec le cadrage WebSocket. Allez à ce lien .
  6. Une fois le cadrage mis en place, assurez-vous de bien recevoir les données à l’aide de sockets. Par exemple, pour éviter que certains messages ne soient fusionnés en un seul, car TCP est toujours un protocole basé sur un flux. Cela signifie que vous devez lire SEULEMENT une quantité spécifique d'octets. La longueur du message est toujours basée sur l'en-tête et les détails de la longueur des données fournies dans l'entête lui-même. Ainsi, lorsque vous recevez des données de Socket, commencez par recevoir 2 octets, obtenez des détails à partir de l'en-tête basé sur la spécification de cadrage, puis si masque fournissait 4 autres octets, puis une longueur pouvant être de 1, 4 ou 8 octets en fonction de la longueur des données. Et après les données, c'est soi-même. Après l'avoir lu, appliquez le démasquage et vos données de message sont prêtes à être utilisées.Vous voudrez peut-être utiliser un protocole de données, il est recommandé d’utiliser JSON en raison d’une économie de trafic importante et d’une utilisation facile en JavaScript côté client. Pour le côté serveur, vous pouvez vérifier certains des analyseurs syntaxiques. Il y en a beaucoup, Google peut être très utile.
  7. L'implémentation de votre propre protocole WebSockets a certainement des avantages et une grande expérience que vous obtenez ainsi que le contrôle du protocole lui-même. Mais vous devez passer du temps à le faire et vous assurer que la mise en œuvre est extrêmement fiable.

En même temps, vous pouvez jeter un coup d'œil aux solutions prêtes à l'emploi que Google (encore) en a assez.

In same time you might have a look in ready to use solutions that google (again) have enough.

58
moka

(Réponse affichée au nom du PO) .

Je suis capable d'envoyer des données maintenant. Ceci est ma nouvelle version du programme grâce à vos réponses et au code de @Maksims Mihejevs. 

Serveur

using System;
using System.Net.Sockets;
using System.Net;
using System.Security.Cryptography;
using System.Threading;

namespace ConsoleApplication1
{
    class Program
    {
        static Socket serverSocket = new Socket(AddressFamily.InterNetwork, 
        SocketType.Stream, ProtocolType.IP);
        static private string guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";

        static void Main(string[] args)
        {            
            serverSocket.Bind(new IPEndPoint(IPAddress.Any, 8080));
            serverSocket.Listen(128);
            serverSocket.BeginAccept(null, 0, OnAccept, null);            
            Console.Read();
        }

        private static void OnAccept(IAsyncResult result)
        {
            byte[] buffer = new byte[1024];
            try
            {
                Socket client = null;
                string headerResponse = "";
                if (serverSocket != null && serverSocket.IsBound)
                {
                    client = serverSocket.EndAccept(result);
                    var i = client.Receive(buffer);
                    headerResponse = (System.Text.Encoding.UTF8.GetString(buffer)).Substring(0,i);
                    // write received data to the console
                    Console.WriteLine(headerResponse);

                }
                if (client != null)
                {
                    /* Handshaking and managing ClientSocket */

                    var key = headerResponse.Replace("ey:", "`")
                              .Split('`')[1]                     // dGhlIHNhbXBsZSBub25jZQ== \r\n .......
                              .Replace("\r", "").Split('\n')[0]  // dGhlIHNhbXBsZSBub25jZQ==
                              .Trim();

                    // key should now equal dGhlIHNhbXBsZSBub25jZQ==
                    var test1 = AcceptKey(ref key);

                    var newLine = "\r\n";

                    var response = "HTTP/1.1 101 Switching Protocols" + newLine
                         + "Upgrade: websocket" + newLine
                         + "Connection: Upgrade" + newLine
                         + "Sec-WebSocket-Accept: " + test1 + newLine + newLine
                         //+ "Sec-WebSocket-Protocol: chat, superchat" + newLine
                         //+ "Sec-WebSocket-Version: 13" + newLine
                         ;

                    // which one should I use? none of them fires the onopen method
                    client.Send(System.Text.Encoding.UTF8.GetBytes(response));

                    var i = client.Receive(buffer); // wait for client to send a message

                    // once the message is received decode it in different formats
                    Console.WriteLine(Convert.ToBase64String(buffer).Substring(0, i));                    

                    Console.WriteLine("\n\nPress enter to send data to client");
                    Console.Read();

                    var subA = SubArray<byte>(buffer, 0, i);
                    client.Send(subA);
                    Thread.Sleep(10000);//wait for message to be send


                }
            }
            catch (SocketException exception)
            {
                throw exception;
            }
            finally
            {
                if (serverSocket != null && serverSocket.IsBound)
                {
                    serverSocket.BeginAccept(null, 0, OnAccept, null);
                }
            }
        }

        public static T[] SubArray<T>(T[] data, int index, int length)
        {
            T[] result = new T[length];
            Array.Copy(data, index, result, 0, length);
            return result;
        }

        private static string AcceptKey(ref string key)
        {
            string longKey = key + guid;
            byte[] hashBytes = ComputeHash(longKey);
            return Convert.ToBase64String(hashBytes);
        }

        static SHA1 sha1 = SHA1CryptoServiceProvider.Create();
        private static byte[] ComputeHash(string str)
        {
            return sha1.ComputeHash(System.Text.Encoding.ASCII.GetBytes(str));
        }
    }
}

JavaScript:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <script type="text/javascript">
        function connect() {
            var ws = new WebSocket("ws://localhost:8080/service");
            ws.onopen = function () {
                alert("About to send data");
                ws.send("Hello World"); // I WANT TO SEND THIS MESSAGE TO THE SERVER!!!!!!!!
                alert("Message sent!");
            };

            ws.onmessage = function (evt) {
                alert("About to receive data");
                var received_msg = evt.data;
                alert("Message received = "+received_msg);
            };
            ws.onclose = function () {
                // websocket is closed.
                alert("Connection is closed...");
            };
        };


    </script>
</head>
<body style="font-size:xx-large" >
    <div>
    <a href="#" onclick="connect()">Click here to start</a></div>
</body>
</html>

Lorsque je lance ce code, je peux envoyer et recevoir des données du client et du serveur. Le seul problème est que les messages sont cryptés lorsqu'ils arrivent sur le serveur. Voici les étapes de fonctionnement du programme:

enter image description here

Notez comment le message du client est crypté. 

6
halfer

Les WebSockets sont implémentés avec un protocole qui implique une poignée de main entre client et serveur . Je n'imagine pas qu'ils fonctionnent très bien comme des prises normales. Lisez le protocole et faites en sorte que votre application en parle. Vous pouvez également utiliser une bibliothèque WebSocket existante ou .Net4.5beta qui possède un WebSocket API .

5
spender

Problème

Depuis que vous utilisez WebSocket, spender est correct. Après avoir reçu les données initiales du WebSocket, vous devez envoyer le message d'établissement de liaison à partir du serveur C # avant que toute information supplémentaire puisse circuler.

HTTP/1.1 101 Web Socket Protocol Handshake
Upgrade: websocket
Connection: Upgrade
WebSocket-Origin: example
WebSocket-Location: something.here
WebSocket-Protocol: 13

Quelque chose dans ce sens.

Vous pouvez faire quelques recherches supplémentaires sur le fonctionnement de WebSocket sur w3 ou google.

Liens et ressources

Voici une spécifcation de protocole: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76#section-1.3

Liste des exemples de travail:

2
caesay

Vous pouvez essayer ces exemples simples ...

0
Rahamath

Je ne pouvais pas trouver un exemple de travail simple n'importe où (à compter du 19 janvier), donc voici une version mise à jour J'ai la version chrome 71.0.3578.98.

Serveur Websocket C #:  

using System;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Security.Cryptography;

namespace WebSocketServer
{
    class Program
    {
    static Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP);
    static private string guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";

    static void Main(string[] args)
    {
        serverSocket.Bind(new IPEndPoint(IPAddress.Any, 8080));
        serverSocket.Listen(1); //just one socket
        serverSocket.BeginAccept(null, 0, OnAccept, null);
        Console.Read();
    }

    private static void OnAccept(IAsyncResult result)
    {
        byte[] buffer = new byte[1024];
        try
        {
            Socket client = null;
            string headerResponse = "";
            if (serverSocket != null && serverSocket.IsBound)
            {
                client = serverSocket.EndAccept(result);
                var i = client.Receive(buffer);
                headerResponse = (System.Text.Encoding.UTF8.GetString(buffer)).Substring(0, i);
                // write received data to the console
                Console.WriteLine(headerResponse);
                Console.WriteLine("=====================");
            }
            if (client != null)
            {
                /* Handshaking and managing ClientSocket */
                var key = headerResponse.Replace("ey:", "`")
                          .Split('`')[1]                     // dGhlIHNhbXBsZSBub25jZQ== \r\n .......
                          .Replace("\r", "").Split('\n')[0]  // dGhlIHNhbXBsZSBub25jZQ==
                          .Trim();

                // key should now equal dGhlIHNhbXBsZSBub25jZQ==
                var test1 = AcceptKey(ref key);

                var newLine = "\r\n";

                var response = "HTTP/1.1 101 Switching Protocols" + newLine
                     + "Upgrade: websocket" + newLine
                     + "Connection: Upgrade" + newLine
                     + "Sec-WebSocket-Accept: " + test1 + newLine + newLine
                     //+ "Sec-WebSocket-Protocol: chat, superchat" + newLine
                     //+ "Sec-WebSocket-Version: 13" + newLine
                     ;

                client.Send(System.Text.Encoding.UTF8.GetBytes(response));
                var i = client.Receive(buffer); // wait for client to send a message
                string browserSent = GetDecodedData(buffer, i);
                Console.WriteLine("BrowserSent: " + browserSent);

                Console.WriteLine("=====================");
                //now send message to client
                client.Send(GetFrameFromString("This is message from server to client."));
                System.Threading.Thread.Sleep(10000);//wait for message to be sent
            }
        }
        catch (SocketException exception)
        {
            throw exception;
        }
        finally
        {
            if (serverSocket != null && serverSocket.IsBound)
            {
                serverSocket.BeginAccept(null, 0, OnAccept, null);
            }
        }
    }

    public static T[] SubArray<T>(T[] data, int index, int length)
    {
        T[] result = new T[length];
        Array.Copy(data, index, result, 0, length);
        return result;
    }

    private static string AcceptKey(ref string key)
    {
        string longKey = key + guid;
        byte[] hashBytes = ComputeHash(longKey);
        return Convert.ToBase64String(hashBytes);
    }

    static SHA1 sha1 = SHA1CryptoServiceProvider.Create();
    private static byte[] ComputeHash(string str)
    {
        return sha1.ComputeHash(System.Text.Encoding.ASCII.GetBytes(str));
    }

    //Needed to decode frame
    public static string GetDecodedData(byte[] buffer, int length)
    {
        byte b = buffer[1];
        int dataLength = 0;
        int totalLength = 0;
        int keyIndex = 0;

        if (b - 128 <= 125)
        {
            dataLength = b - 128;
            keyIndex = 2;
            totalLength = dataLength + 6;
        }

        if (b - 128 == 126)
        {
            dataLength = BitConverter.ToInt16(new byte[] { buffer[3], buffer[2] }, 0);
            keyIndex = 4;
            totalLength = dataLength + 8;
        }

        if (b - 128 == 127)
        {
            dataLength = (int)BitConverter.ToInt64(new byte[] { buffer[9], buffer[8], buffer[7], buffer[6], buffer[5], buffer[4], buffer[3], buffer[2] }, 0);
            keyIndex = 10;
            totalLength = dataLength + 14;
        }

        if (totalLength > length)
            throw new Exception("The buffer length is small than the data length");

        byte[] key = new byte[] { buffer[keyIndex], buffer[keyIndex + 1], buffer[keyIndex + 2], buffer[keyIndex + 3] };

        int dataIndex = keyIndex + 4;
        int count = 0;
        for (int i = dataIndex; i < totalLength; i++)
        {
            buffer[i] = (byte)(buffer[i] ^ key[count % 4]);
            count++;
        }

        return Encoding.ASCII.GetString(buffer, dataIndex, dataLength);
    }

    //function to create  frames to send to client 
    /// <summary>
    /// Enum for opcode types
    /// </summary>
    public enum EOpcodeType
    {
        /* Denotes a continuation code */
        Fragment = 0,

        /* Denotes a text code */
        Text = 1,

        /* Denotes a binary code */
        Binary = 2,

        /* Denotes a closed connection */
        ClosedConnection = 8,

        /* Denotes a ping*/
        Ping = 9,

        /* Denotes a pong */
        Pong = 10
    }

    /// <summary>Gets an encoded websocket frame to send to a client from a string</summary>
    /// <param name="Message">The message to encode into the frame</param>
    /// <param name="Opcode">The opcode of the frame</param>
    /// <returns>Byte array in form of a websocket frame</returns>
    public static byte[] GetFrameFromString(string Message, EOpcodeType Opcode = EOpcodeType.Text)
    {
        byte[] response;
        byte[] bytesRaw = Encoding.Default.GetBytes(Message);
        byte[] frame = new byte[10];

        int indexStartRawData = -1;
        int length = bytesRaw.Length;

        frame[0] = (byte)(128 + (int)Opcode);
        if (length <= 125)
        {
            frame[1] = (byte)length;
            indexStartRawData = 2;
        }
        else if (length >= 126 && length <= 65535)
        {
            frame[1] = (byte)126;
            frame[2] = (byte)((length >> 8) & 255);
            frame[3] = (byte)(length & 255);
            indexStartRawData = 4;
        }
        else
        {
            frame[1] = (byte)127;
            frame[2] = (byte)((length >> 56) & 255);
            frame[3] = (byte)((length >> 48) & 255);
            frame[4] = (byte)((length >> 40) & 255);
            frame[5] = (byte)((length >> 32) & 255);
            frame[6] = (byte)((length >> 24) & 255);
            frame[7] = (byte)((length >> 16) & 255);
            frame[8] = (byte)((length >> 8) & 255);
            frame[9] = (byte)(length & 255);

            indexStartRawData = 10;
        }

        response = new byte[indexStartRawData + length];

        int i, reponseIdx = 0;

        //Add the frame bytes to the reponse
        for (i = 0; i < indexStartRawData; i++)
        {
            response[reponseIdx] = frame[i];
            reponseIdx++;
        }

        //Add the data bytes to the response
        for (i = 0; i < length; i++)
        {
            response[reponseIdx] = bytesRaw[i];
            reponseIdx++;
        }

        return response;
    }
}
}

Client html et javascript:

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <script type="text/javascript">
        var socket = new WebSocket('ws://localhost:8080/websession');
        socket.onopen = function() {
           // alert('handshake successfully established. May send data now...');
		   socket.send("Hi there from browser.");
        };
		socket.onmessage = function (evt) {
                //alert("About to receive data");
                var received_msg = evt.data;
                alert("Message received = "+received_msg);
            };
        socket.onclose = function() {
            alert('connection closed');
        };
    </script>
</head>
<body>
</body>
</html>

0
Deepak Garud