【问题标题】:C# HTML5 Websocket ServerC# HTML5 Websocket 服务器
【发布时间】:2011-08-15 17:02:02
【问题描述】:

我正在尝试创建一个 C# Websocket 服务器,但似乎无法正常工作。 我现在有一个接受 TCPClient 的服务器,接收来自客户端的 HTTP 请求并尝试发回 HTTP 响应,以便可以完成 HTML5 WebSocket 握手。

我认为服务器发送给客户端的握手有问题。 我阅读了草案 (Websocket 76 draft),其中指出在握手结束时必须对给出的两个键给出响应。此响应由服务器计算。

这是我的代码:

static void Main(string[] args)
    {
        int port = 8181;
        IPAddress localAddr = IPAddress.Loopback;

        TcpListener server = new TcpListener(localAddr, port);
        server.Start();

        // Buffer for reading data
        Byte[] receivedBytes = new Byte[256];
        String data = null;

        // Enter the listening loop.
        while (true)
        {
            Console.WriteLine("Waiting for a connection...");

            // Perform a blocking call to accept requests.
            // You could also user server.AcceptSocket() here.
            TcpClient client = server.AcceptTcpClient();
            Console.WriteLine("Connected!\n");

            data = null;

            // Get a stream object for reading and writing
            NetworkStream stream = client.GetStream();

            int i;
            

            // Loop to receive all the data sent by the client.
            while ((i = stream.Read(receivedBytes, 0, receivedBytes.Length)) != 0)
            {
                // Translate data bytes to a ASCII string.
                data = System.Text.Encoding.UTF8.GetString(receivedBytes, 0, i);

                Console.WriteLine("Received:");
                Console.WriteLine(data);
                Byte[] response_token = hashResponse(data);


                string handshake = "HTTP/1.1 101 WebSocket Protocol Handshake\r\n"
                    + "Upgrade: WebSocket\r\n" + "Connection: Upgrade\r\n"
                    + "Sec-WebSocket-Origin: http://localhost\r\n"
                    + "Sec-WebSocket-Location: ws://localhost:8181/websession\r\n"
                    + "\r\n";

                Byte[] writtenBytes = Encoding.UTF8.GetBytes(handshake);

                stream.Write(writtenBytes, 0, writtenBytes.Length);
                stream.Write(response_token, 0, response_token.Length);

                Console.WriteLine("Send:");
                Console.WriteLine(handshake);

                string strHash = Encoding.UTF8.GetString(response_token);
                Console.WriteLine(strHash);
            }                
        }
    }

    static Byte[] hashResponse(string receivedData)
    {
        string strDel = "\r\n";
        char[] delimeter = strDel.ToCharArray();

        string Key1 = null;
        string Key2 = null;
        string hash = null;
        MD5 md5 = MD5.Create();

        string[] lines = receivedData.Split(delimeter);
        Key1 = lines[10].Substring(20);
        Key2 = lines[12].Substring(20);
        hash = lines[16];

        Int64 numbersKey1 = Convert.ToInt64(string.Join(null, Regex.Split(Key1, "[^\\d]")));
        Int64 numbersKey2 = Convert.ToInt64(string.Join(null, Regex.Split(Key2, "[^\\d]")));

        Int64 numberSpaces1 = countSpaces(Key1);
        Int64 numberSpaces2 = countSpaces(Key2);

        int dividedKey1 = (int) (numbersKey1 / numberSpaces1);
        int dividedKey2 = (int) (numbersKey2 / numberSpaces2);

        Byte[] encodedKey1 = Encoding.UTF8.GetBytes(dividedKey1.ToString());
        Byte[] encodedKey2 = Encoding.UTF8.GetBytes(dividedKey2.ToString());
        Byte[] encodedHash = Encoding.UTF8.GetBytes(hash);

        Byte[] combined = Encoding.UTF8.GetBytes(dividedKey1.ToString() + dividedKey2.ToString() + hash);

        Byte[] responseHash = md5.ComputeHash(combined); 
        return responseHash;
    }

    static int countSpaces(string key)
    {
        int counter = 0;
        char[] charArray = key.ToCharArray();

        foreach (char c in charArray)
        {
            if (c.Equals(' '))
                counter++;
        }

        return counter;
    }

我用于测试的 HTML 页面(名为 Test.html)由运行在我的计算机上的 apache 网络服务器托管,我通过浏览(在 Chrome 中)到 http://localhost/Test.html 来访问它

有没有人知道我做错了什么,因为我变得非常绝望。

提前致谢

丹尼斯

【问题讨论】:

    标签: c# websocket


    【解决方案1】:

    这是我根据 draft-ietf-hybi-thewebsocketprotocol-00 编写的示例服务器,说明了握手阶段:

    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Net;
    using System.Net.Sockets;
    using System.Security.Cryptography;
    using System.Text;
    using System.Text.RegularExpressions;
    
    class Program
    {
        static void Main(string[] args)
        {
            var listener = new TcpListener(IPAddress.Loopback, 8080);
            listener.Start();
            while (true)
            {
                using (var client = listener.AcceptTcpClient())
                using (var stream = client.GetStream())
                {
                    var headers = new Dictionary<string, string>();
                    string line = string.Empty;
                    while ((line = ReadLine(stream)) != string.Empty)
                    {
                        var tokens = line.Split(new char[] { ':' }, 2);
                        if (!string.IsNullOrWhiteSpace(line) && tokens.Length > 1)
                        {
                            headers[tokens[0]] = tokens[1].Trim();
                        }
                    }
    
                    var key = new byte[8];
                    stream.Read(key, 0, key.Length);
    
                    var key1 = headers["Sec-WebSocket-Key1"];
                    var key2 = headers["Sec-WebSocket-Key2"];
    
                    var numbersKey1 = Convert.ToInt64(string.Join(null, Regex.Split(key1, "[^\\d]")));
                    var numbersKey2 = Convert.ToInt64(string.Join(null, Regex.Split(key2, "[^\\d]")));
                    var numberSpaces1 = CountSpaces(key1);
                    var numberSpaces2 = CountSpaces(key2);
    
                    var part1 = (int)(numbersKey1 / numberSpaces1);
                    var part2 = (int)(numbersKey2 / numberSpaces2);
    
                    var result = new List<byte>();
                    result.AddRange(GetBigEndianBytes(part1));
                    result.AddRange(GetBigEndianBytes(part2));
                    result.AddRange(key);
    
                    var response =
                        "HTTP/1.1 101 WebSocket Protocol Handshake" + Environment.NewLine +
                        "Upgrade: WebSocket" + Environment.NewLine +
                        "Connection: Upgrade" + Environment.NewLine +
                        "Sec-WebSocket-Origin: " + headers["Origin"] + Environment.NewLine +
                        "Sec-WebSocket-Location: ws://localhost:8080/websession" + Environment.NewLine + 
                        Environment.NewLine;
    
                    var bufferedResponse = Encoding.UTF8.GetBytes(response);
                    stream.Write(bufferedResponse, 0, bufferedResponse.Length);
                    using (var md5 = MD5.Create())
                    {
                        var handshake = md5.ComputeHash(result.ToArray());
                        stream.Write(handshake, 0, handshake.Length);
                    }
                }
            }
        }
    
        static int CountSpaces(string key)
        {
            return key.Length - key.Replace(" ", string.Empty).Length;
        }
    
        static string ReadLine(Stream stream)
        {
            var sb = new StringBuilder();
            var buffer = new List<byte>();
            while (true)
            {
                buffer.Add((byte)stream.ReadByte());
                var line = Encoding.ASCII.GetString(buffer.ToArray());
                if (line.EndsWith(Environment.NewLine))
                {
                    return line.Substring(0, line.Length - 2);
                }
            }
        }
    
        static byte[] GetBigEndianBytes(int value)
        {
            var bytes = 4;
            var buffer = new byte[bytes];
            int num = bytes - 1;
            for (int i = 0; i < bytes; i++)
            {
                buffer[num - i] = (byte)(value & 0xffL);
                value = value >> 8;
            }
            return buffer;
        }
    }
    

    还有一个示例客户端:

    <!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.onclose = function() {
                alert('connection closed');
            };
        </script>
    </head>
    <body>
    </body>
    </html>
    

    【讨论】:

    • 嗨达林,首先 +1 为一个工作样本,我喜欢。但是,我已经使用 Chrome 16 尝试了您的示例,但它无法正常工作,因为 Chrome 没有像您的服务器期望的那样发送 Sec-WebSockets-Key1 标头,您在此测试中使用了哪个 UA?
    • @EugenioMiró,我使用的是 Chrome,但在我发布此答案时不知道是哪个版本:2011 年 5 月 3 日。我的答案基于 draft-ietf-hybi-thewebsocketprotocol-00。现在有RFC 6455,我认为这是最新的规范,但我不知道 Chrome 16 实现了哪一个。
    • 嗨达林,我发现 Chrome 现在它在标题上发送 13 作为版本,所以我从那里跟进,感谢您的快速回复!
    【解决方案2】:

    这是我使用普通套接字实现的非常简单的 Websocket 回显服务器。

    using System;
    using System.Net;
    using System.Net.Sockets;
    using System.Text;
    using System.Text.RegularExpressions;
    using System.Security.Cryptography;
    
    namespace SimpleWebsocketServer {
        class Program {
            static void Main(string[] args) {
                var listeningSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
                listeningSocket.Bind(new IPEndPoint(IPAddress.Any, port: 80));
                listeningSocket.Listen(0);
    
                while (true) {
                    var clientSocket = listeningSocket.Accept();
    
                    Console.WriteLine("A client connected.");
    
                    var receivedData = new byte[1000000];
                    var receivedDataLength = clientSocket.Receive(receivedData);
    
                    var requestString = Encoding.UTF8.GetString(receivedData, 0, receivedDataLength);
    
                    if (new Regex("^GET").IsMatch(requestString)) {
                        const string eol = "\r\n";
    
                        var receivedWebSocketKey = new Regex("Sec-WebSocket-Key: (.*)").Match(requestString).Groups[1].Value.Trim();
                        var keyHash = SHA1.Create().ComputeHash(Encoding.UTF8.GetBytes(receivedWebSocketKey + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"));
    
                        var response = "HTTP/1.1 101 Switching Protocols" + eol;
                        response += "Connection: Upgrade" + eol;
                        response += "Upgrade: websocket" + eol;
                        response += "Sec-WebSocket-Accept: " + Convert.ToBase64String(keyHash) + eol;
                        response += eol;
    
                        var responseBytes = Encoding.UTF8.GetBytes(response);
    
                        clientSocket.Send(responseBytes);
                    }
    
                    while (true) {
                        receivedData = new byte[1000000];
                        clientSocket.Receive(receivedData);
    
                        if ((receivedData[0] & (byte)Opcode.CloseConnection) == (byte)Opcode.CloseConnection) {
                            // Close connection request.
                            Console.WriteLine("Client disconnected.");
                            clientSocket.Close();
                            break;
                        } else {
                            var receivedPayload = ParsePayloadFromFrame(receivedData);
                            var receivedString = Encoding.UTF8.GetString(receivedPayload);
    
                            Console.WriteLine($"Client: {receivedString}");
    
                            var response = $"ECHO: {receivedString}";
                            var dataToSend = CreateFrameFromString(response);
    
                            Console.WriteLine($"Server: {response}");
    
                            clientSocket.Send(dataToSend);
                        }
                    }
                }
            }
    
            public static byte[] ParsePayloadFromFrame(byte[] incomingFrameBytes) {
                var payloadLength = 0L;
                var totalLength = 0L;
                var keyStartIndex = 0L;
    
                // 125 or less.
                // When it's below 126, second byte is the payload length.
                if ((incomingFrameBytes[1] & 0x7F) < 126) {
                    payloadLength = incomingFrameBytes[1] & 0x7F;
                    keyStartIndex = 2;
                    totalLength = payloadLength + 6;
                }
    
                // 126-65535.
                // When it's 126, the payload length is in the following two bytes
                if ((incomingFrameBytes[1] & 0x7F) == 126) {
                    payloadLength = BitConverter.ToInt16(new[] { incomingFrameBytes[3], incomingFrameBytes[2] }, 0);
                    keyStartIndex = 4;
                    totalLength = payloadLength + 8;
                }
    
                // 65536 +
                // When it's 127, the payload length is in the following 8 bytes.
                if ((incomingFrameBytes[1] & 0x7F) == 127) {
                    payloadLength = BitConverter.ToInt64(new[] { incomingFrameBytes[9], incomingFrameBytes[8], incomingFrameBytes[7], incomingFrameBytes[6], incomingFrameBytes[5], incomingFrameBytes[4], incomingFrameBytes[3], incomingFrameBytes[2] }, 0);
                    keyStartIndex = 10;
                    totalLength = payloadLength + 14;
                }
    
                if (totalLength > incomingFrameBytes.Length) {
                    throw new Exception("The buffer length is smaller than the data length.");
                }
    
                var payloadStartIndex = keyStartIndex + 4;
    
                byte[] key = { incomingFrameBytes[keyStartIndex], incomingFrameBytes[keyStartIndex + 1], incomingFrameBytes[keyStartIndex + 2], incomingFrameBytes[keyStartIndex + 3] };
    
                var payload = new byte[payloadLength];
                Array.Copy(incomingFrameBytes, payloadStartIndex, payload, 0, payloadLength);
                for (int i = 0; i < payload.Length; i++) {
                    payload[i] = (byte)(payload[i] ^ key[i % 4]);
                }
    
                return payload;
            }
    
            public enum Opcode {
                Fragment = 0,
                Text = 1,
                Binary = 2,
                CloseConnection = 8,
                Ping = 9,
                Pong = 10
            }
    
            public static byte[] CreateFrameFromString(string message, Opcode opcode = Opcode.Text) {
                var payload = Encoding.UTF8.GetBytes(message);
    
                byte[] frame;
    
                if (payload.Length < 126) {
                    frame = new byte[1 /*op code*/ + 1 /*payload length*/ + payload.Length /*payload bytes*/];
                    frame[1] = (byte)payload.Length;
                    Array.Copy(payload, 0, frame, 2, payload.Length);
                } else if (payload.Length >= 126 && payload.Length <= 65535) {
                    frame = new byte[1 /*op code*/ + 1 /*payload length option*/ + 2 /*payload length*/ + payload.Length /*payload bytes*/];
                    frame[1] = 126;
                    frame[2] = (byte)((payload.Length >> 8) & 255);
                    frame[3] = (byte)(payload.Length & 255);
                    Array.Copy(payload, 0, frame, 4, payload.Length);
                } else {
                    frame = new byte[1 /*op code*/ + 1 /*payload length option*/ + 8 /*payload length*/ + payload.Length /*payload bytes*/];
                    frame[1] = 127; // <-- Indicates that payload length is in following 8 bytes.
                    frame[2] = (byte)((payload.Length >> 56) & 255);
                    frame[3] = (byte)((payload.Length >> 48) & 255);
                    frame[4] = (byte)((payload.Length >> 40) & 255);
                    frame[5] = (byte)((payload.Length >> 32) & 255);
                    frame[6] = (byte)((payload.Length >> 24) & 255);
                    frame[7] = (byte)((payload.Length >> 16) & 255);
                    frame[8] = (byte)((payload.Length >> 8) & 255);
                    frame[9] = (byte)(payload.Length & 255);
                    Array.Copy(payload, 0, frame, 10, payload.Length);
                }
    
                frame[0] = (byte)((byte)opcode | 0x80 /*FIN bit*/);
    
                return frame;
            }
        }
    }
    

    【讨论】:

    • 我花了几个小时寻找一个简单的 dotnet 命令行 websocket 示例,没有任何库膨胀或比接收、发送和关闭更基本的东西,这是最重要的。跨度>
    【解决方案3】:

    在尝试在套接字上接收数据之前尝试发送握手数据

    这是一个可能对你有帮助的例子

    websocksample

    【讨论】:

    • 在握手之前我并没有真正尝试接收数据,这是客户端发送到我在控制台屏幕上写的服务器的请求。
    • 在发送握手响应“HTTP/1.1 101 WebSocket 协议等”之前,您获得了一个连接,您正在尝试接收数据,我认为您需要在收到连接的那一刻发送这个。在您接收流上的数据之前。
    【解决方案4】:

    不知道是否可以编译Objective C,但是this project is really pretty cool...

    Blackbox 是一个可嵌入的 Cocoa HTTP 服务器——它允许您关联 HTTP 带有 Cocoa“响应者”对象的资源(有点像 Twisted Web 用于 Python)而不是文件系统上的文件。

    使用 Blackbox,您可以快速创建个人文件共享器,编写 通过 HTTP 相互通信的应用程序,并且很容易 为无头应用程序创建 Web 控制界面。

    它基本上是一个很好的包装中的Comet server。我希望我能多说一些,但我仍然在尝试自己找出套接字......

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-10-24
      • 2013-03-01
      • 1970-01-01
      • 2015-10-04
      • 2012-01-05
      • 1970-01-01
      • 2018-04-30
      • 1970-01-01
      相关资源
      最近更新 更多