100 votes

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

Je ne comprends pas pourquoi je ne peux pas faire fonctionner le code suivant. Je veux me connecter avec JavaScript à mon application console 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...");
        };

Lorsque j'exécute ce code, voici ce qui se passe :

Notez que lorsque j'exécute le JavaScript, le serveur accepte et établit avec succès une connexion. JavaScript n'est cependant pas en mesure d'envoyer des données. Chaque fois que je place la méthode d'envoi, il n'envoie rien, même si une connexion est établie. Comment faire pour que cela fonctionne ?

10 votes

Cette "question" ne semble plus être une question, et n'est donc pas vraiment adaptée au format de StackOverflow. Pour info, le message du client n'est pas crypté c'est masqué (obscurci) en effectuant un XOR par rapport à une valeur aléatoire qui est transmise dans le cadre de la trame. Ce détail du protocole existe pour éviter les attaques par empoisonnement contre les serveurs proxy qui pourraient mal comprendre le trafic.

2 votes

Merci, cette réponse est très utile :) hey, juste une chose, est-ce que cette chose "static private string guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" ;" est toujours constante ? si non, où puis-je obtenir ces valeurs ?

2 votes

J'ai obtenu ceci : "Un serveur ne doit pas masquer les trames qu'il envoie au client"

81voto

moka Points 6143

WebSockets est un protocole qui repose sur une connexion TCP en continu. Bien que WebSockets soit un protocole basé sur les messages.

Si vous voulez implémenter votre propre protocole, je vous recommande d'utiliser la dernière spécification stable (pour le 18/04/12). RFC 6455 . Cette spécification contient toutes les informations nécessaires concernant le handshake et le framing. Elle décrit également la plupart des scénarios de comportement du côté du navigateur et du côté du serveur. Il est fortement recommandé de suivre les recommandations concernant le côté serveur lors de la mise en œuvre de votre code.

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

  1. Créer un serveur Socket (System.Net.Sockets) le lie à un port spécifique, et continue à écouter en acceptant les connexions de manière asynchrone. Quelque chose comme ça :

    Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP); serverSocket.Bind(new IPEndPoint(IPAddress.Any, 8080)); serverSocket.Listen(128); serverSocket.BeginAccept(null, 0, OnAccept, null);

  2. Vous devriez avoir accepter fonction "OnAccept" qui implémentera le handshake. A l'avenir, elle devra être dans un autre thread si le système est destiné à gérer un nombre important de connexions par seconde.

    private void OnAccept(IAsyncResult result) { try { Socket client = null; if (serverSocket != null && serverSocket.IsBound) { client = serverSocket.EndAccept(result); } if (client != null) { /* Handshaking and managing ClientSocket */ } } catch(SocketException exception) {

    } 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 poignée de main Une fois la connexion établie, vous recevrez une requête HTTP de base avec quelques informations. Exemple :

    GET /chat HTTP/1.1 Host: server.example.com Upgrade: websocket Connection: Upgrade Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== Origin: http://example.com Sec-WebSocket-Protocol: chat, superchat Sec-WebSocket-Version: 13

Cet exemple est basé sur la version du protocole 13. Gardez à l'esprit que les anciennes versions présentent des différences, mais que les dernières versions sont généralement compatibles entre elles. Les 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 la poignée de main fournie, vous devez générer des lignes de réponse, elles sont généralement les mêmes, mais contiennent Accept-Key, qui est basé sur Sec-WebSocket-Key fourni. Dans la spécification 1.3, il est clairement décrit comment générer la clé de réponse. Voici la fonction que j'utilise depuis la V13 :

static private string guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
private string AcceptKey(ref string key) {
        string longKey = key + guid;
        SHA1 sha1 = SHA1CryptoServiceProvider.Create();
        byte\[\] hashBytes = sha1.ComputeHash(System.Text.Encoding.ASCII.GetBytes(longKey));
        return Convert.ToBase64String(hashBytes);
}

La réponse à la poignée de main ressemble à ça :

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

Mais la clé acceptée doit être générée à partir de la clé fournie par le client et de la méthode AcceptKey que j'ai fournie auparavant. De plus, assurez-vous qu'après le dernier caractère de la clé d'acceptation, vous mettez deux nouvelles lignes " \r\n\r\n ".

  1. Après l'envoi de la réponse du serveur, le client doit déclencher " onopen "Cela signifie que vous pouvez envoyer des messages après.

  2. Les messages ne sont pas envoyés en format brut, mais ils ont Cadrage des données . Et du client au serveur, le masquage des données est également mis en œuvre sur la base des 4 octets fournis dans l'en-tête du message. Cependant, du serveur au client, il n'est pas nécessaire d'appliquer le masquage des données. Lire la section 5. Cadrage des données dans les spécifications. Voici un copier-coller de ma propre implémentation. Ce n'est pas un code prêt à l'emploi, et il doit être modifié, je le poste juste pour donner une idée et la logique globale de la lecture/écriture avec l'encadrement WebSocket. Aller à ce lien .

  3. Après l'implémentation du framing, assurez-vous que vous recevez les données de la bonne manière en utilisant les sockets. Par exemple, pour éviter que certains messages soient fusionnés en un seul, car TCP est toujours un protocole basé sur le flux. Cela signifie que vous devez lire UNIQUEMENT 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'en-tête lui-même. Ainsi, lorsque vous recevez des données d'un socket, vous recevez d'abord 2 octets, vous obtenez les détails de l'en-tête basés sur la spécification de cadrage, puis, si le masque est fourni, 4 octets supplémentaires, et enfin la longueur qui peut être de 1, 4 ou 8 octets en fonction de la longueur des données. Et après les données elles-mêmes. Après les avoir lues, appliquez le démasquage et votre message de données est prêt à être utilisé.

  4. Vous pourriez vouloir utiliser des Protocole de données Je recommande d'utiliser JSON en raison de l'économie de trafic et de la facilité d'utilisation du côté client en JavaScript. Pour le côté serveur, vous pouvez vérifier certains parseurs. Il y en a beaucoup, google peut être très utile.

La mise en œuvre de son propre protocole WebSockets présente certainement des avantages et une grande expérience, ainsi que le contrôle du protocole lui-même. Mais vous devez y consacrer du temps et vous assurer que la mise en œuvre est hautement fiable.

Dans le même temps, vous pourriez jeter un coup d'œil aux solutions prêtes à l'emploi que google (encore) a suffisamment.

0 votes

Je pense que je suis bloqué sur le handshaking. Quand une nouvelle connexion est reçue, je dois envoyer au client le hash sha1 de la clé longue plus la clé courte, n'est-ce pas ?

0 votes

J'ai ajouté plus d'informations dans la section 3. Il décrit plus de détails sur le handshake du côté serveur.

1 votes

De même, assurez-vous que si les protocoles sont fournis dans la requête, utilisez les mêmes dans la réponse pour la ligne Sec-WebSocket-Protocol. Mais seulement s'il est fourni dans la requête. De même, vous n'avez pas besoin de Version pour la réponse. Et ajoutez une autre NewLine à la fin. De plus, envoyez la chaîne de réponse entière encodée en UTF8 : Encoding.UTF8.GetBytes(responseBytes)

17voto

halfer Points 8455

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

Je suis capable d'envoyer des données maintenant. Voici 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 j'exécute ce code, je suis en mesure d'envoyer et de recevoir des données à la fois 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 l'exécution du programme :

enter image description here

Notez comment le message du client est crypté.

0 votes

Au moins j'ai eu un problème dans chrome se plaignant du masquage, j'ai du y réfléchir : stackoverflow.com/questions/16932662/ et puis ça a marché, merci.

16voto

Deepak Garud Points 483

Je n'ai pu trouver nulle part un exemple simple et fonctionnel (au 19 janvier), alors voici une version mise à jour. J'ai la version 71.0.3578.98 de Chrome.

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];

        long indexStartRawData = -1;
        long length = (long)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];

        long 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>

1 votes

Vous devez utiliser long au lieu de int pour la longueur des données brutes dans GetFrameFromString . Sinon, votre décalage de bits pour les messages plus grands que 65535 peut donner une valeur différente de la longueur réelle. Puisque int peut aller jusqu'à 2,147,483,647 y long a 9,223,372,036,854,775,807 qui est la limite de trame de la RFC6455.

0 votes

@funie200 Merci de mettre à jour la réponse

6voto

spender Points 51307

Les WebSockets sont mis en œuvre avec un protocole qui implique poignée de main entre le client et le serveur . J'imagine qu'ils ne fonctionnent pas vraiment comme des sockets normaux. Renseignez-vous sur le protocole, et faites en sorte que votre application le parle. Sinon, utilisez une bibliothèque WebSocket existante, ou .Net4.5beta qui dispose d'une fonction API WebSocket .

1 votes

Bien sûr, ils fonctionnent comme des prises de courant normales. Le socket se situe à un niveau inférieur au protocole de l'application et, en tant que tel, n'en dépend pas. Cela signifie que vous pouvez exécuter FTP, SMTP, HTTP, WebSockets, etc... sur une socket. C'est à l'implémenteur de s'assurer qu'elle suit correctement le protocole ou personne ne pourra parler au serveur.

3voto

caesay Points 4760

Numéro

Puisque vous utilisez WebSocket, Spender a raison. Après avoir reçu les données initiales de la WebSocket, vous devez envoyer le message de poignée de main du serveur C# avant que toute autre information 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 de ce genre.

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

Liens et ressources

Voici une spécification du protocole : https://datatracker.ietf.org/doc/html/draft-hixie-thewebsocketprotocol-76#section-1.3

Liste d'exemples de travail :

0 votes

J'utilise également l'encodage UTF8. Devrais-je en utiliser un autre, comme l'ASCII ?

0 votes

@TonoNam : Pour autant que je sache, l'encodage UTF8 est correct - bien que je ne sois pas un expert en HTML5, donc je n'en suis pas sûr.

0 votes

Je l'ai fait fonctionner avec safari ! !! J'ai besoin de le faire fonctionner avec google chrome cependant. Tous les exemples se connectent mais aucun d'entre eux ne réussit à envoyer des données. Je vais continuer à essayer. Merci beaucoup pour votre aide !

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X