创建一个“ Hello World” WebSocket示例


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无法发送数据。每当我放置send方法时,即使建立连接也不会发送。我该如何工作?


9
这个“问题”似乎不再是一个问题,因此,它实际上并不适合StackOverflow的格式。FWIW,客户端的消息未加密,而是通过与作为帧的一部分传输的随机值进行XOR运算来掩盖(混淆)。存在此协议详细信息是为了避免对代理服务器造成中毒的攻击,这些攻击可能会误解流量。
EricLaw 2012年

1
谢谢,这个答案非常有帮助:)嘿,这是“静态专用字符串guid =“ 258EAFA5-E914-47DA-95CA-C5AB0DC85B11”;“ 事情总是不变的?如果没有,我在哪里可以得到这些值?
Charmie 2013年

1
我得到了这一点:“服务器不得屏蔽它发送给客户端的任何帧”
Charmie

5
您可能应该完整保留原始问题。问题的目的是提出问题,而不是解决方案。您可能已经猜到了答案,是针对解决方案的。
Kehlan Krumme 2013年

1
为什么WebSocket网址以'/ service'结尾(ws:// localhost:8080 / service)?为什么不只是'ws:// localhost:8080'?
andree 2014年

Answers:


72

WebSockets是依赖TCP流连接的协议。尽管WebSockets是基于消息的协议。

如果要实现自己的协议,则建议使用最新且稳定的规范(适用于2012年4月18日)RFC 6455。该规范包含有关握手和成帧的所有必要信息。以及从浏览器和服务器端的行为场景的大多数描述。强烈建议您在实施代码时,遵循有关服务器端的建议。

简而言之,我将描述使用WebSocket的方式:

  1. 创建服务器套接字(System.Net.Sockets)将其绑定到特定端口,并通过异步接受连接来继续侦听。像这样:

    套接字serverSocket =新的Socket(AddressFamily.InterNetwork,SocketType.Stream,ProtocolType.IP);
    serverSocket.Bind(新IPEndPoint(IPAddress.Any,8080));
    serverSocket.Listen(128);
    serverSocket.BeginAccept(null,0,OnAccept,null);
  2. 您应该具有将实现握手的接受函数“ OnAccept”。将来,如果系统要每秒处理大量连接,则它必须处于另一个线程中。

    私人无效OnAccept(IAsyncResult结果){
    尝试{
        套接字客户端= null;
        如果(serverSocket!= null && serverSocket.IsBound){
            客户端= serverSocket.EndAccept(结果);
        }
        if(client!= null){
            / *握手和管理ClientSocket * /
        }
    } catch(SocketException exception){
    
    }最后{
        如果(serverSocket!= null && serverSocket.IsBound){
            serverSocket.BeginAccept(null,0,OnAccept,null);
        }
    }
    }
  3. 建立连接后,您必须进行握手。根据规范1.3打开握手,建立连接后,您将收到基本的HTTP请求以及一些信息。例:

    GET / chat HTTP / 1.1
    主机:server.example.com
    升级:websocket
    连接:升级
    Sec-WebSocket-密钥:dGhlIHNhbXBsZSBub25jZQ ==
    来源:http://example.com
    Sec-WebSocket-Protocol:聊天,超级聊天
    安全WebSocket版本:13

    此示例基于协议13的版本。请记住,旧版本具有一些差异,但大多数最新版本是交叉兼容的。不同的浏览器可能会向您发送一些其他数据。例如,浏览器和操作系统详细信息,缓存等。

    根据提供的握手详细信息,您必须生成应答行,它们基本相同,但将包含基于提供的Sec-WebSocket-Key的Accpet-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));
        返回Convert.ToBase64String(hashBytes);
    }
    

    握手答案如下:

    HTTP / 1.1 101交换协议
    升级:websocket
    连接:升级
    Sec-WebSocket-接受:s3pPLMBiTxaQ9kYGzzhZRbK + xOo =

    但是接受密钥必须是基于客户端提供的密钥和我之前提供的方法AcceptKey生成的密钥。同样,请确保在接受密钥的最后一个字符之后放置两行“ \ r \ n \ r \ n”。

  4. 从服务器发送握手应答后,客户端应触发“ onopen ”功能,这意味着您可以在以后发送消息。
  5. 消息不是以原始格式发送的,但是它们具有Data Framing。从客户端到服务器,以及基于消息头中提供的4个字节的数据实现屏蔽。尽管从服务器到客户端,您无需对数据应用掩码。阅读规范中的第5节。这是我自己的实现中的复制粘贴。它不是现成的代码,必须进行修改,我发布它只是为了提供使用WebSocket框架进行读写的想法和总体逻辑。转到此链接
  6. 实施框架后,请确保使用套接字正确接收数据。例如,为了防止某些消息合并为一条消息,因为TCP仍然是基于流的协议。这意味着您只需要读取特定数量的字节。消息的长度始终基于标头,并且标头本身提供了数据长度详细信息。因此,当您从Socket接收数据时,首先接收2个字节,然后根据Framing规范从标头中获取详细信息,然后如果mask提供了另外4个字节,然后根据数据长度,其长度可能为1、4或8个字节。并经过数据本身。阅读后,应用去屏蔽,您的消息数据即可使用。
  7. 您可能需要使用一些数据协议,由于流量节省并且建议在JavaScript中的客户端上易于使用,因此我建议使用JSON。对于服务器端,您可能需要检查一些解析器。其中有很多,谷歌真的可以提供帮助。

实施自己的WebSockets协议肯定会带来一些好处和丰富的经验,并且可以自己控制协议。但是您必须花一些时间来做,并确保实现高度可靠。

同时,您可能已经准备好使用谷歌(再次)拥有足够的解决方案。


我想我一直在握手。当收到新连接时,我必须将长键和短键的sha1哈希发送给客户端,对吗?
Tono Nam

我在第3节中添加了更多信息。它描述了服务器端握手方面的更多细节。
moka 2012年

1
同样,请确保如果提供了请求中的协议,则对Sec-WebSocket-Protocol行使用相同的响应。但仅在有要求时提供。同样,您不需要版本进行响应。并在末尾添加另一个NewLine。以及发送使用UTF8编码的整个响应字符串:Encoding.UTF8.GetBytes(responseBytes)
moka 2012年

我们接近了。非常感谢帮忙。我现在可以发送邮件了,但是我相信邮件是加密的。看一下我的编辑,我将很快开始工作……
Tono Nam

您必须实现数据框架,这将是WebSockets协议实现中最复杂的部分。我将在实现中添加复制粘贴代码,但要确保对其进行了编辑,因为它有一些更改,但是总体上提供了使用框架的想法和逻辑。
moka'4

9

(代表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>

运行该代码时,我能够从客户端和服务器发送和接收数据。唯一的问题是,消息到达服务器后会被加密。以下是程序运行方式的步骤:

在此处输入图片说明

请注意如何加密来自客户端的消息。


2
您的示例出了点问题,当我按Enter时,连接将关闭。
理查德·阿奎尔

@RichardAguirre:我代表OP发布了此信息(请参见答案的第一行)-目前不会通知您您的消息。您可以尝试在问题下对其进行ping操作,但是它们可能需要更多的细节。
半身'17

6

WebSockets通过涉及客户端和服务器之间握手的协议实现。我不认为它们像普通插座一样工作。阅读该协议,并让您的应用程序使用它。或者,使用现有的WebSocket库或具有WebSocket API的.Net4.5beta 。


当然,它们的工作原理与普通插座非常相似。套接字位于比应用程序协议低的级别,因此不依赖于它。这意味着您可以在套接字上运行FTP,SMTP,HTTP,WebSocket等。实施者应确保自己正确遵循协议,否则任何人都无法与服务器进行对话。
SRM

4

我在任何地方(截至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];

        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

工作示例列表:


我也使用UTF8编码。我应该使用其他字符(例如ASCII)吗?
Tono Nam

@TonoNam:据我所知,UTF8编码是正确的-尽管我不是HTML5专家,所以我不确定。
caesay 2012年

我使它与野生动物园一起工作!!!我需要它来使其与Google Chrome兼容。所有示例都已连接,但没有一个成功发送数据。我会继续努力。非常感谢您的帮助!
Tono Nam

当然.....如果我仍然无法解决问题,我将把这个问题变成悬而未决的问题!我真的很好奇看到这个工作。感谢您的帮助
Tono Nam

您的协议规范链接已过期。Safari仍使用此功能,但其他浏览器已移至不兼容的RFC 6455。同样,虽然您正确地认为初始连接需要进行一些协商,但其他消息则不需要。
simonc 2012年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.