簡體   English   中英

創建“Hello World”WebSocket 示例

[英]Creating a “Hello World” WebSocket example

我不明白為什么我不能讓下面的代碼工作。 我想用 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 無法發送數據。 每當我放置發送方法時,即使建立了連接,它也不會發送。 我怎樣才能使這項工作?

WebSockets 是依賴 TCP 流連接的協議。 雖然 WebSockets 是基於消息的協議。

如果您想實現自己的協議,那么我建議使用最新且穩定的規范(18/04/12) RFC 6455 該規范包含有關握手和幀的所有必要信息。 以及大部分關於瀏覽器端和服務器端行為場景的描述。 強烈建議在實現代碼期間遵循有關服務器端的建議。

簡而言之,我會像這樣描述使用 WebSockets 的情況:

  1. 創建服務器套接字(System.Net.Sockets)將其綁定到特定端口,並通過異步接受連接保持偵聽。 類似的東西:
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);
  1. 您應該具有將實現握手的接受函數“OnAccept”。 將來,如果系統要每秒處理大量連接,則它必須在另一個線程中。
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);
        }
    }
}
  1. 建立連接后,您必須進行握手 根據規范1.3 Opening Handshake ,建立連接后,您將收到基本的 HTTP 請求和一些信息。 例子:
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

此示例基於協議 13 的版本。請記住,舊版本有一些差異,但大多數最新版本是交叉兼容的。 不同的瀏覽器可能會向您發送一些額外的數據。 例如瀏覽器和操作系統詳細信息、緩存等。

根據提供的握手詳細信息,您必須生成答案行,它們大多相同,但將包含 Accept-Key,即基於提供的 Sec-WebSocket-Key。 在規范 1.3 中清楚地描述了如何生成響應密鑰。 這是我用於 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);
}

握手回答看起來像這樣:

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

但是接受密鑰必須是基於客戶端提供的密鑰和我之前提供的 AcceptKey 方法生成的密鑰。 同樣,確保在接受鍵的最后一個字符后放置兩行新行“\\r\\n\\r\\n”。

  1. 從服務器發送握手應答后,客戶端應觸發“ onopen ”功能,這意味着您可以發送消息。

  2. 消息不是以原始格式發送的,但它們具有Data Framing 並且從客戶端到服務器也基於消息頭中提供的 4 個字節實現數據屏蔽。 盡管從服務器到客戶端,您不需要對數據應用屏蔽。 閱讀第5規范中的數據幀 這是我自己實現的復制粘貼。 它不是現成的代碼,必須進行修改,我發布它只是為了提供使用 WebSocket 框架進行讀/寫的想法和整體邏輯。 轉到此鏈接

  3. 實現成幀后,請確保使用套接字以正確的方式接收數據。 例如為了防止某些消息合並為一個,因為 TCP 仍然是基於流的協議。 這意味着您必須僅讀取特定數量的字節。 消息的長度始終基於標頭,並在標頭本身中提供數據長度詳細信息。 因此,當您從 Socket 接收數據時,首先接收 2 個字節,根據幀規范從標頭中獲取詳細信息,然后如果掩碼提供另外 4 個字節,然后根據數據長度可能為 1、4 或 8 個字節的長度。 在數據之后它自己。 閱讀后,應用去掩碼,您的消息數據就可以使用了。

  4. 您可能想要使用一些數據協議,我建議使用 JSON 由於流量經濟且易於在 JavaScript 客戶端使用。 對於服務器端,您可能需要檢查一些解析器。 有很多,谷歌真的很有幫助。

實現自己的 WebSockets 協議肯定有一些好處和豐富的經驗,以及對協議本身的控制。 但是你必須花一些時間去做,並確保實現是高度可靠的。

同時,您可能會查看谷歌(再次)擁有足夠的現成可用的解決方案。

(代表OP發布答案)

我現在可以發送數據了。 感謝您的回答和@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>

當我運行該代碼時,我能夠從客戶端和服務器發送和接收數據。 唯一的問題是消息到達服務器時會被加密。 以下是程序運行的步驟:

在此處輸入圖片說明

注意來自客戶端的消息是如何加密的。

WebSockets 是使用涉及客戶端和服務器之間握手的協議實現的 我不認為它們的工作方式與普通套接字非常相似。 閱讀協議,並讓您的應用程序進行討論。 或者,使用現有的 WebSocket 庫,或具有WebSocket API 的.Net4.5beta。

我在任何地方都找不到一個簡單的工作示例(截至 1 月 19 日),所以這里是一個更新版本。 我有 chrome 版本 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];

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

客戶端 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>

問題

由於您使用的是 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

工作示例列表:

你可以嘗試這些簡單的例子......

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM