C# WebSocket 版本 8+ 服务器通信

标签 c# asp.net html websocket

我一直在开发一个 hello-world 类型的 WebSocket 应用程序,看看是否可以在 VS 2010 中运行它。客户端代码非常简单,并且在 Safari 中运行得很好,因为 Safari 仍然运行在WebSocket 76 协议(protocol)。

另一方面,Google Chrome 运行较新的 Sec-WebSocket-Version 代码,该代码更加复杂。我从 code-plex 项目“借用”了一些代码作为基础,以使其适用于较旧的 76 和 75 协议(protocol):http://wsaspnet.codeplex.com/SourceControl/changeset/view/58121#1561340

然后,我继续升级在那里找到的代码,直到它可以执行新的握手(在下面的代码中)。我最终碰壁并且似乎找不到任何帮助的部分是与 Sec-WebSocket-Version 8+ 套接字的通信。过去你只需在开头附加一个 0x00,在末尾附加一个 0xFF 就可以了!有效!但现在显然还有更多工作要做来解决安全问题。当我通过调试代码发送消息时,我可以看出套接字正在关闭,并且我在几个地方读到,chrome 在收到它不理解的消息时喜欢关闭套接字。

然后我找到了这篇文章:How can I send and receive WebSocket messages on the server side?那家伙和我有同样的问题,但我似乎无法将他的回答者发布的伪代码翻译成 C#。谁能帮帮我吗?

客户端代码:

<div id="ComLog" style="height:600px;">


</div>
<input type="button" onclick="javascript:connectToServer();" value='connect' />

<script type="text/javascript">
    var sock;
    function connectToServer() {
        try {
            sock = new WebSocket("ws://localhost:8181/websock");
            //sock = new WebSocket("ws://192.168.0.100:8181/websock");

            //sock = new WebSocket("ws://websockets.org:8787");

            sock.onopen = sockOpen;
            sock.onerror = sockError;
            sock.onclose = sockClosed;
            sock.onmessage = sockMessage;
        } catch (e) {
            log("error:" + e);
        }
    }

    function sockOpen() {
        log("connected");
    }

    function sockError(error, p2) {
        log("socket error!");
    }

    function sockClosed() {
        log("socket closed");
    }

    function sockMessage(event) {
        log("<b>Server:</b> " + event.data);
    }

    function log(msg) {
        var txtLog = document.getElementById("ComLog");
        txtLog.innerHTML += msg + "</br>";
    }
</script>

服务器代码:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Net;
using System.IO;
using System.Web;
using System.Collections.Specialized;
using System.Text.RegularExpressions;
using System.Threading;
using System.Security.Cryptography;

namespace WebSocks
{
    public class WebSockServer
    {

        /// <summary>
        /// Port number to listen on
        /// </summary>
        private const int PortNumber = 8181;

        /// <summary>
        /// Socket which awaits connections
        /// </summary>
        private static Socket ListenerSocket;

        /// <summary>
        /// Thread in which we await for incomming connections.
        /// </summary>
        static System.Threading.Thread _serverThread;


        static WebSockServer() { }

        /// <summary>
        /// Starts thread with listening socket.
        /// </summary>
        public static void Start()
        {
            System.Threading.ThreadStart ts = new System.Threading.ThreadStart(Listen);
            _serverThread = new System.Threading.Thread(ts);
            _serverThread.Start();
        }

        /// <summary>
        /// Stops listening for connections.
        /// </summary>
        public static void End()
        {
            _serverThread.Abort();
            ListenerSocket.Dispose();
        }

        public static void Listen()
        {
            //Start listening
            ListenerSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            EndPoint ep = new IPEndPoint(IPAddress.Parse("0.0.0.0"), PortNumber);
            ListenerSocket.Bind(ep);
            ListenerSocket.Listen(5);

            while (true)
            {
                //New client
                using (Socket client = ListenerSocket.Accept())
                {
                    //Receiving clientHandshake
                    string clientHandshake = String.Empty;
                    byte[] buffer = null;
                    int readBytes = 0;
                    do
                    {
                        buffer = new byte[client.Available];
                        readBytes = client.Receive(buffer);
                        clientHandshake += Encoding.UTF8.GetString(buffer);
                    }
                    while (client.Available > 0);

                    //Last eight bytes are body of requets (we should include it in response)
                    byte[] secKey3 = buffer.Skip(readBytes - 8).Take(8).ToArray();

                    //Variables we can extract from clientHandshake
                    string clientOrigin = String.Empty;
                    string secKey1 = String.Empty;
                    string secKey2 = String.Empty;
                    string WebSocketVersion = String.Empty;
                    int WSV = 0;
                    string WebSocketKey = String.Empty;

                    //Extracting values from headers (key:value)
                    string[] clientHandshakeLines = Regex.Split(clientHandshake, Environment.NewLine);
                    foreach (string hline in clientHandshakeLines)
                    {
                        int valueStartIndex = hline.IndexOf(':') + 2;
                        if (valueStartIndex > 0)
                        {
                            if (hline.StartsWith("Origin"))
                            {
                                clientOrigin = hline.Substring(valueStartIndex, hline.Length - valueStartIndex);
                            }
                            else if (hline.StartsWith("Sec-WebSocket-Key2"))
                            {
                                secKey2 = hline.Substring(valueStartIndex, hline.Length - valueStartIndex);
                            }
                            else if (hline.StartsWith("Sec-WebSocket-Key1"))
                            {
                                secKey1 = hline.Substring(valueStartIndex, hline.Length - valueStartIndex);
                            }

                            if (hline.StartsWith("Sec-WebSocket-Version"))
                            {
                                WebSocketVersion = hline.Replace("Sec-WebSocket-Version: ", "");
                                WSV = Convert.ToInt32(WebSocketVersion);
                            }

                            if (hline.StartsWith("Sec-WebSocket-Key"))
                            {
                                WebSocketKey = hline.Replace("Sec-WebSocket-Key: ", "");
                            }
                        }
                    }




                    if (!String.IsNullOrEmpty(WebSocketVersion)) //WebSocketVersion 8 and up handshake check
                    {
                        //New WebSocketVersion number, included after Version 8
                        StringBuilder mResponse = new StringBuilder();
                        mResponse.AppendLine("HTTP/1.1 101 Switching Protocols");
                        mResponse.AppendLine("Upgrade: WebSocket");
                        mResponse.AppendLine("Connection: Upgrade");
                        mResponse.AppendLine(String.Format("Sec-WebSocket-Accept: {0}", ComputeWebSocketHandshakeSecurityHash09(WebSocketKey)) + Environment.NewLine);

                        byte[] HSText = Encoding.UTF8.GetBytes(mResponse.ToString());

                        client.Send(HSText, 0, HSText.Length, 0);
                    }
                    else
                    {
                        //This part is common for all websockets editions (v. 75 & v.76)
                        client.Send(Encoding.UTF8.GetBytes("HTTP/1.1 101 Web Socket Protocol Handshake" + Environment.NewLine));
                        client.Send(Encoding.UTF8.GetBytes("Upgrade: WebSocket" + Environment.NewLine));
                        client.Send(Encoding.UTF8.GetBytes("Connection: Upgrade" + Environment.NewLine));


                        if (String.IsNullOrEmpty(secKey1) && String.IsNullOrEmpty(secKey2))  //75 or less handshake check
                        {                 
                            client.Send(Encoding.UTF8.GetBytes(String.Format("WebSocket-Origin: {0}", clientOrigin) + Environment.NewLine));
                            client.Send(Encoding.UTF8.GetBytes("WebSocket-Location: ws://localhost:8181/websock" + Environment.NewLine));
                            client.Send(Encoding.UTF8.GetBytes(Environment.NewLine));
                        }
                        else //76 handshake check
                        {
                            //Keys present, this means 76 version is used. Writing Sec-* headers 
                            client.Send(Encoding.UTF8.GetBytes(String.Format("Sec-WebSocket-Origin: {0}", clientOrigin) + Environment.NewLine));
                            client.Send(Encoding.UTF8.GetBytes("Sec-WebSocket-Location: ws://localhost:8181/websock" + Environment.NewLine));
                            client.Send(Encoding.UTF8.GetBytes(Environment.NewLine));

                            //Calculating response body
                            byte[] secret = CalculateSecurityBody(secKey1, secKey2, secKey3);
                            client.Send(secret);
                        }
                    }

                    Thread.Sleep(1000);

                    SendMessage("This message will terminate in 5 seconds...", client, WSV);

                    Thread.Sleep(1000);

                    SendMessage("4", client, WSV);

                    Thread.Sleep(1000);

                    SendMessage("3", client, WSV);

                    Thread.Sleep(1000);

                    SendMessage("2", client, WSV);

                    Thread.Sleep(1000);

                    SendMessage("1", client, WSV);

                    Thread.Sleep(1000);

                }
            }
        }

        private static void SendMessage(string Msg, Socket client, int WebSockVersion)
        {
            if (WebSockVersion >= 8)
            {
                //This is the section that doesn't work
                client.Send(Encoding.UTF8.GetBytes(Msg));

            }
            else 
            {
                client.Send(new byte[] { 0x00 });
                client.Send(Encoding.UTF8.GetBytes(Msg));
                client.Send(new byte[] { 0xFF });
            }
        }


        public static byte[] CalculateSecurityBody(string secKey1, string secKey2, byte[] secKey3)
        {
            //Remove all symbols that are not numbers
            string k1 = Regex.Replace(secKey1, "[^0-9]", String.Empty);
            string k2 = Regex.Replace(secKey2, "[^0-9]", String.Empty);

            //Convert received string to 64 bit integer.
            Int64 intK1 = Int64.Parse(k1);
            Int64 intK2 = Int64.Parse(k2);

            //Dividing on number of spaces
            int k1Spaces = secKey1.Count(c => c == ' ');
            int k2Spaces = secKey2.Count(c => c == ' ');
            int k1FinalNum = (int)(intK1 / k1Spaces);
            int k2FinalNum = (int)(intK2 / k2Spaces);

            //Getting byte parts
            byte[] b1 = BitConverter.GetBytes(k1FinalNum).Reverse().ToArray();
            byte[] b2 = BitConverter.GetBytes(k2FinalNum).Reverse().ToArray();
            //byte[] b3 = Encoding.UTF8.GetBytes(secKey3);
            byte[] b3 = secKey3;

            //Concatenating everything into 1 byte array for hashing.
            List<byte> bChallenge = new List<byte>();
            bChallenge.AddRange(b1);
            bChallenge.AddRange(b2);
            bChallenge.AddRange(b3);

            //Hash and return
            byte[] hash = MD5.Create().ComputeHash(bChallenge.ToArray());
            return hash;
        }

        public static String ComputeWebSocketHandshakeSecurityHash09(String secWebSocketKey)         
        {             
            const String MagicKEY = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";             
            String secWebSocketAccept = String.Empty;              
            // 1. Combine the request Sec-WebSocket-Key with magic key.             
            String ret = secWebSocketKey + MagicKEY;              
            // 2. Compute the SHA1 hash             
            SHA1 sha = new SHA1CryptoServiceProvider();             
            byte[] sha1Hash = sha.ComputeHash(Encoding.UTF8.GetBytes(ret));              
            // 3. Base64 encode the hash             
            secWebSocketAccept = Convert.ToBase64String(sha1Hash);              
            return secWebSocketAccept;         
        } 
    }
}

对我所有问题的明显未翻译的“答案”:

bytesFormatted[0] = 129

indexStartRawData = -1 // don't matter what value is here; it will be set now:

if bytesRaw.length <= 125
    bytesFormatted[1] = bytesRaw.length

    indexStartRawData = 2

else if bytesRaw.length >= 126 and bytesRaw.length <= 65535
    bytesFormatted[1] = 126
    bytesFormatted[2] = ( bytesRaw.length >> 8 ) AND 255
    bytesFormatted[3] = ( bytesRaw.length      ) AND 255

    indexStartRawData = 4

else
    bytesFormatted[1] = 127
    bytesFormatted[2] = ( bytesRaw.length >> 56 ) AND 255
    bytesFormatted[3] = ( bytesRaw.length >> 48 ) AND 255
    bytesFormatted[4] = ( bytesRaw.length >> 40 ) AND 255
    bytesFormatted[5] = ( bytesRaw.length >> 32 ) AND 255
    bytesFormatted[6] = ( bytesRaw.length >> 24 ) AND 255
    bytesFormatted[7] = ( bytesRaw.length >> 16 ) AND 255
    bytesFormatted[8] = ( bytesRaw.length >>  8 ) AND 255
    bytesFormatted[9] = ( bytesRaw.length       ) AND 255

    indexStartRawData = 10

// put raw data at the correct index
bytesFormatted.put(bytesRaw, indexStartRawData)


// now send bytesFormatted (e.g. write it to the socket stream)

~~~~~~~~~~~~编辑~~~~~~~~~~~~

当前版本的发送功能:

private static void SendMessage(string Msg, Socket client, int WebSockVersion)
        {
            if (WebSockVersion >= 8)
            {
                bool IsFinal = true;
                int OpCode = 1;
                int? Mask = null;
                int PayloadLength = Encoding.UTF8.GetBytes(Msg).Length;
                byte[] buffer = Encoding.UTF8.GetBytes(Msg);

                int offset = 0;
                buffer[offset++] = (byte)((IsFinal ? 128 : 0) | ((int)OpCode & 15));
                if (PayloadLength > ushort.MaxValue)
                { // write as a 64-bit length
                    buffer[offset++] = (byte)((Mask.HasValue ? 128 : 0) | 127);
                    buffer[offset++] = 0;
                    buffer[offset++] = 0;
                    buffer[offset++] = 0;
                    buffer[offset++] = 0;
                    buffer[offset++] = (byte)(PayloadLength >> 24);
                    buffer[offset++] = (byte)(PayloadLength >> 16);
                    buffer[offset++] = (byte)(PayloadLength >> 8);
                    buffer[offset++] = (byte)(PayloadLength);
                }
                else if (PayloadLength > 125)
                { // write as a 16-bit length
                    buffer[offset++] = (byte)((Mask.HasValue ? 128 : 0) | 126);
                    buffer[offset++] = (byte)(PayloadLength >> 8);
                    buffer[offset++] = (byte)(PayloadLength);
                }
                else
                { // write in the header
                    buffer[offset++] = (byte)((Mask.HasValue ? 128 : 0) | PayloadLength);
                }
                if (Mask.HasValue)
                {
                    int mask = Mask.Value;
                    buffer[offset++] = (byte)(mask >> 24);
                    buffer[offset++] = (byte)(mask >> 16);
                    buffer[offset++] = (byte)(mask >> 8);
                    buffer[offset++] = (byte)(mask);
                }
                //stream.Write(buffer, 0, offset);

                client.Send(buffer, 0, PayloadLength, SocketFlags.None);

            }
            else 
            {
                client.Send(new byte[] { 0x00 });
                client.Send(Encoding.UTF8.GetBytes(Msg));
                client.Send(new byte[] { 0xFF });
            }
        }

最佳答案

说实话,这看起来是对的;它与我的工作代码非常相似(仅处理 32 位长度,因此缺少 >> 32 及以上):

int offset = 0;
buffer[offset++] = (byte)((IsFinal ? 128 : 0) | ((int)OpCode & 15));
if (PayloadLength > ushort.MaxValue)
{ // write as a 64-bit length
    buffer[offset++] = (byte)((Mask.HasValue ? 128 : 0) | 127);
    buffer[offset++] = 0;
    buffer[offset++] = 0;
    buffer[offset++] = 0;
    buffer[offset++] = 0;
    buffer[offset++] = (byte)(PayloadLength >> 24);
    buffer[offset++] = (byte)(PayloadLength >> 16);
    buffer[offset++] = (byte)(PayloadLength >> 8);
    buffer[offset++] = (byte)(PayloadLength);
}
else if (PayloadLength > 125)
{ // write as a 16-bit length
    buffer[offset++] = (byte)((Mask.HasValue ? 128 : 0) | 126);
    buffer[offset++] = (byte)(PayloadLength >> 8);
    buffer[offset++] = (byte)(PayloadLength);
}
else
{ // write in the header
    buffer[offset++] = (byte)((Mask.HasValue ? 128 : 0) | PayloadLength);
}
if (Mask.HasValue)
{
    int mask = Mask.Value;
    buffer[offset++] = (byte)(mask >> 24);
    buffer[offset++] = (byte)(mask >> 16);
    buffer[offset++] = (byte)(mask >> 8);
    buffer[offset++] = (byte)(mask);
}
stream.Write(buffer, 0, offset);

之后,它只写入有效负载(如果有)。由于您是从服务器写入客户端,因此您实际上不需要担心出站掩码,因此它始终只是 127。但是,如果您要接受传入消息,那么您确实非常需要担心掩码。

注意:Windows 8 上的 .NET 4.5 在 HttpListener 和 ASP.NET 中内置了 WebSocket 支持;在那之前,另一种方法可能是查看 SuperWebSocket。


重新编辑:您已经删除了自己的数据!尝试一下:

bool IsFinal = true;
int OpCode = 1;
int? Mask = null;
byte[] payload = Encoding.UTF8.GetBytes(Msg);
int PayloadLength = payload.Length;
byte[] buffer = new byte[64]; // for working out the header

int offset = 0;
buffer[offset++] = (byte)((IsFinal ? 128 : 0) | ((int)OpCode & 15));
if (PayloadLength > ushort.MaxValue)
{ // write as a 64-bit length
    buffer[offset++] = (byte)((Mask.HasValue ? 128 : 0) | 127);
    buffer[offset++] = 0;
    buffer[offset++] = 0;
    buffer[offset++] = 0;
    buffer[offset++] = 0;
    buffer[offset++] = (byte)(PayloadLength >> 24);
    buffer[offset++] = (byte)(PayloadLength >> 16);
    buffer[offset++] = (byte)(PayloadLength >> 8);
    buffer[offset++] = (byte)(PayloadLength);
}
else if (PayloadLength > 125)
{ // write as a 16-bit length
    buffer[offset++] = (byte)((Mask.HasValue ? 128 : 0) | 126);
    buffer[offset++] = (byte)(PayloadLength >> 8);
    buffer[offset++] = (byte)(PayloadLength);
}
else
{ // write in the header
    buffer[offset++] = (byte)((Mask.HasValue ? 128 : 0) | PayloadLength);
}
if (Mask.HasValue)
{
    int mask = Mask.Value;
    buffer[offset++] = (byte)(mask >> 24);
    buffer[offset++] = (byte)(mask >> 16);
    buffer[offset++] = (byte)(mask >> 8);
    buffer[offset++] = (byte)(mask);
}
// you might want to manually combine these into 1 packet
client.Send(buffer, 0, offset, SocketFlags.None);
client.Send(payload, 0, payload.Length, SocketFlags.None);

关于C# WebSocket 版本 8+ 服务器通信,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/9624427/

相关文章:

javascript - jQuery 每个函数在同一元素中输出多个选择器

c# - XNA 窗口调整大小调用 LoadContent

c# - Xamarin.Forms。 XAML Label IsVisible 条件未按预期进行评估

asp.net - 从一段文本中去除任何超链接和文本

c# - session 变量不存储值

html - 简单的 CSS 下拉菜单在 IE6 或 7 中不起作用

C# 使用 certenroll.dll 在没有 CA 的情况下生成非自签名客户端 CX509Certificate 请求

c# - DNN 7.3.4 中的/signalr/hubs 404(未找到)

javascript - 使用 ASP.NET 的 OnFocus OnBlur

javascript - 使用鼠标中键按钮提交表单