Создание примера WebSocket «Hello World»

85

Я не понимаю, почему я не могу заставить работать следующий код. Я хочу подключиться с помощью JavaScript к моему консольному приложению сервера. А затем отправьте данные на сервер.

Вот код сервера:

    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));
        }
    }

а вот и 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...");
        };

Когда я запускаю этот код, происходит следующее:

Обратите внимание, что когда я запускаю JavaScript, сервер принимает и успешно устанавливает соединение. Однако JavaScript не может отправлять данные. Всякий раз, когда я размещаю метод отправки, он не отправляется, даже если соединение установлено. Как я могу заставить это работать?

Тоно Нам
источник
9
Этот «вопрос» больше не является вопросом и поэтому не совсем подходит для формата StackOverflow. FWIW, сообщение клиента не зашифровано, оно замаскировано (запутано) с помощью XOR против случайного значения, которое передается как часть кадра. Эта деталь протокола существует, чтобы избежать атак на прокси-серверы, которые могут неправильно понять трафик.
EricLaw
1
спасибо, этот ответ очень полезен :) эй, только одна вещь, это "статическая частная строка guid =" 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 ";" вещь всегда постоянная? Если нет, где я могу получить эти значения?
Charmie
1
я получил следующее: «Сервер не должен маскировать кадры, которые он отправляет клиенту»
Чарми
5
Вероятно, вам следовало оставить исходный вопрос нетронутым. Задача вопросов - представить проблему, а не ее решение. Ответы, как вы могли догадаться, касаются решений.
Кехлан Крумме
1
Почему URL-адрес WebSocket заканчивается на '/ service' (ws: // localhost: 8080 / service)? Почему не просто ws: // localhost: 8080?
andree 04

Ответы:

72

WebSockets - это протокол, основанный на потоковом соединении TCP. Хотя WebSockets - это протокол на основе сообщений.

Если вы хотите реализовать свой собственный протокол, я рекомендую использовать последнюю и стабильную спецификацию (на 18.04.12) RFC 6455 . Эта спецификация содержит всю необходимую информацию о квитировании и кадрировании. А также большая часть описания сценариев поведения как со стороны браузера, так и со стороны сервера. При внедрении кода настоятельно рекомендуется следовать рекомендациям относительно серверной части.

В двух словах я бы описал работу с WebSockets так:

  1. Создайте серверный Socket (System.Net.Sockets), привяжите его к определенному порту и продолжайте прослушивание с асинхронным приемом соединений. Что-то такое:

    Сокет serverSocket = новый сокет (AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.IP);
    serverSocket.Bind (новый IPEndPoint (IPAddress.Any, 8080));
    serverSocket.Listen (128);
    serverSocket.BeginAccept (ноль, 0, OnAccept, ноль);
  2. У вас должна быть принимающая функция «OnAccept», которая реализует рукопожатие. В будущем он должен быть в другом потоке, если система предназначена для обработки огромного количества соединений в секунду.

    private void OnAccept (результат IAsyncResult) {
    пытаться {
        Socket client = null;
        if (serverSocket! = null && serverSocket.IsBound) {
            client = serverSocket.EndAccept (результат);
        }
        if (client! = null) {
            / * Подтверждение связи и управление ClientSocket * /
        }
    } catch (исключение SocketException) {
    
    } наконец-то {
        if (serverSocket! = null && serverSocket.IsBound) {
            serverSocket.BeginAccept (ноль, 0, OnAccept, ноль);
        }
    }
    }
  3. После установления соединения необходимо выполнить рукопожатие . В соответствии со спецификацией 1.3 «Открытие рукопожатия» после установления соединения вы получите базовый HTTP-запрос с некоторой информацией. Пример:

    GET / чат HTTP / 1.1
    Хост: server.example.com
    Обновление: websocket
    Подключение: Обновление
    Sec-WebSocket-ключ: dGhlIHNhbXBsZSBub25jZQ ==
    Происхождение: http://example.com
    Sec-WebSocket-Protocol: чат, суперчат
    Sec-WebSocket-Версия: 13

    Этот пример основан на версии протокола 13. Имейте в виду, что старые версии имеют некоторые отличия, но в основном последние версии перекрестно совместимы. Различные браузеры могут отправлять вам дополнительные данные. Например, сведения о браузере и ОС, кеш и другие.

    На основе предоставленных деталей рукопожатия вам необходимо сгенерировать строки ответа, они в основном одинаковы, но будут содержать Accpet-Key, который основан на предоставленном Sec-WebSocket-Key. В спецификации 1.3 четко описано, как сгенерировать ответный ключ. Вот моя функция, которую я использовал для V13:

    статическая частная строка guid = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
    закрытая строка AcceptKey (ссылка на строковый ключ) {
        строка longKey = ключ + guid;
        SHA1 sha1 = SHA1CryptoServiceProvider.Create ();
        byte [] hashBytes = sha1.ComputeHash (System.Text.Encoding.ASCII.GetBytes (longKey));
        return Convert.ToBase64String (hashBytes);
    }
    

    Ответ рукопожатия выглядит так:

    HTTP / 1.1 101 Протоколы переключения
    Обновление: websocket
    Подключение: Обновление
    Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK + xOo =

    Но ключ принятия должен быть сгенерирован на основе предоставленного ключа от клиента и метода AcceptKey, который я предоставил ранее. Также убедитесь, что после последнего символа клавиши принятия вы поместили две новые строки «\ r \ n \ r \ n».

  4. После отправки ответа с сервера клиент должен активировать функцию « onopen », что означает, что вы можете отправлять сообщения после этого.
  5. Сообщения не отправляются в необработанном формате, но у них есть Data Framing . И от клиента к серверу также реализуйте маскировку данных на основе предоставленных 4 байтов в заголовке сообщения. Хотя от сервера к клиенту вам не нужно применять маскировку данных. Прочтите раздел 5. Фрейминг данных в спецификации. Вот копипаст из моей собственной реализации. Это не готовый к использованию код, и его необходимо модифицировать. Я публикую его, чтобы дать представление и общую логику чтения / записи с использованием фреймов WebSocket. Перейдите по этой ссылке .
  6. После внедрения кадрирования убедитесь, что вы получаете данные правильно с помощью сокетов. Например, чтобы предотвратить объединение некоторых сообщений в одно, поскольку TCP по-прежнему является потоковым протоколом. Это означает, что вы должны читать ТОЛЬКО определенное количество байтов. Длина сообщения всегда зависит от заголовка и предоставленной информации о длине данных в самом заголовке. Поэтому, когда вы получаете данные из Socket, сначала получите 2 байта, получите подробную информацию из заголовка на основе спецификации кадрирования, затем, если маска предоставила еще 4 байта, а затем длину, которая может быть 1, 4 или 8 байтов в зависимости от длины данных. И после данных он сам. После того, как вы его прочитаете, примените демаскировку, и данные вашего сообщения готовы к использованию.
  7. Возможно, вы захотите использовать какой-то протокол данных , я рекомендую использовать JSON из-за экономии трафика и простоты использования на стороне клиента в JavaScript. На стороне сервера вы можете проверить некоторые парсеры. Их много, гугл может быть очень полезным.

Внедрение собственного протокола WebSockets определенно имеет некоторые преимущества и большой опыт, который вы получаете, а также контроль над самим протоколом. Но на это нужно потратить некоторое время и убедиться, что реализация очень надежна.

В то же время вы можете взглянуть на готовые к использованию решения, которых у Google (опять же) достаточно.

мока
источник
Думаю, я застрял на рукопожатии. когда получено новое соединение, я должен отправить клиенту хеш sha1 длинного ключа плюс короткий ключ, верно?
Тоно Нам
Я добавил дополнительную информацию в раздел 3. Он описывает более подробную информацию о рукопожатии со стороны сервера.
moka
1
Также убедитесь, что если в запросе указаны протоколы, используйте то же самое в ответе для строки Sec-WebSocket-Protocol. Но только при условии, что в запросе есть. Также вам не нужна версия для ответа. И в конец добавляем еще одну NewLine. Также отправьте всю строку ответа, закодированную с использованием UTF8: Encoding.UTF8.GetBytes (responseBytes)
moka
Мы близко. Большое спасибо за помощь. Теперь я могу отправить сообщение, но, как мне кажется, сообщение зашифровано. Взгляните на мою правку, над которой я скоро начну работать ...
Тоно Нам
Вам необходимо реализовать кадрирование данных, я считаю, что это будет самый сложный этап в реализации протокола WebSockets. Я добавлю код copy-paste из моей реализации в пост, но обязательно отредактируйте его, потому что в нем есть кое-что, что нужно изменить, но в целом он дает представление и логику работы с фреймом.
moka
9

(Размещен ответ от имени ОП) .

Теперь я могу отправлять данные. Это моя новая версия программы благодаря вашим ответам и коду @Maksims Mihejevs.

Сервер

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>

Когда я запускаю этот код, я могу отправлять и получать данные как от клиента, так и от сервера. Единственная проблема в том, что сообщения поступают на сервер в зашифрованном виде. Вот шаги, как работает программа:

введите описание изображения здесь

Обратите внимание, как зашифровано сообщение от клиента.

халфер
источник
2
Что-то не так с вашим примером, когда я нажимаю Enter, соединение закрывается.
Ричард Агирре
@RichardAguirre: Я разместил это от имени OP (см. Первую строку ответа) - в настоящее время они не будут уведомлены о вашем сообщении. Вы можете попробовать ответить им на вопрос, но, вероятно, им потребуется гораздо больше подробностей.
Halfer
6

WebSockets реализованы с помощью протокола, который включает рукопожатие между клиентом и сервером . Я не думаю, что они работают как обычные розетки. Ознакомьтесь с протоколом и попросите ваше приложение обсудить его. В качестве альтернативы используйте существующую библиотеку WebSocket или .Net4.5beta с API WebSocket .

расточитель
источник
Конечно, они работают как обычные розетки. Сокет находится на более низком уровне, чем протокол приложения, и как таковой не зависит от него. Это означает, что вы можете запускать FTP, SMTP, HTTP, WebSockets и т. Д. На сокете. Разработчик должен убедиться, что он правильно следует протоколу, иначе никто не сможет разговаривать с сервером.
SRM
4

Я нигде не мог найти простой рабочий пример (по состоянию на 19 января), поэтому вот обновленная версия. У меня хром версия 71.0.3578.98.

Сервер C # Websocket:

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

Клиентские html и 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>

Дипак Гаруд
источник
3

Проблема

Поскольку вы используете WebSocket, спонсор правильный. После получения начальных данных от WebSocket вам необходимо отправить сообщение подтверждения с сервера C #, прежде чем будет передаваться какая-либо дополнительная информация.

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

Что-то в этом роде.

Вы можете узнать больше о том, как WebSocket работает на w3 или google.

Ссылки и ресурсы

Вот спецификация протокола: http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol-76#section-1.3

Список рабочих примеров:

Caesay
источник
Также я использую кодировку UTF8. Стоит ли использовать другой, например ASCII?
Тоно Нам
@TonoNam: Насколько я знаю, кодировка UTF8 верна, хотя я не эксперт по HTML5, поэтому я не знаю наверняка.
caesay
Я заставил работать сафари !!! Мне это нужно, чтобы он работал с Google Chrome. Все примеры подключаются, но ни один из них не отправляет данные. Я буду продолжать попытки. Большое спасибо за помощь!
Тоно Нам
Конечно .... Если у меня все еще не получится, я сделаю этот вопрос наградным! Мне действительно любопытно увидеть, как это работает. Спасибо за помощь
Тоно Нам
Ссылка на спецификацию вашего протокола устарела. Safari по-прежнему использует это, но другие браузеры перешли на несовместимый RFC 6455 . Кроме того, хотя вы правы, что первоначальное соединение требует некоторых переговоров, дальнейшие сообщения - нет.
simonc