簡體   English   中英

C# HTML5 Websocket 服務器

[英]C# HTML5 Websocket Server

我正在嘗試創建一個 C# Websocket 服務器,但我似乎沒有讓它工作。 I now have a server that accepts the TCPClient, receives the HTTP request from the client and that tries to send back a HTTP response so the HTML5 WebSocket handshake can be completed.

我相信服務器發送給客戶端的握手有問題。 我閱讀了草案(Websocket 76 草案),其中指出在握手結束時必須對給出的兩個鍵給出響應。 此響應由服務器計算。

這是我的代碼:

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

The HTML page (which is named Test.html) I'm using for testing is hosted by an apache webserver which runs on my computer I access it by browsing (in Chrome) to http://localhost/Test.html

有沒有人知道我做錯了什么,因為我變得非常絕望。

提前致謝

丹尼斯

這是我根據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>

不知道能不能編譯Objective C,不過這個項目真的很酷...

Blackbox is an embeddable Cocoa HTTP server -- that allows you to associate HTTP resources with Cocoa "responder" objects (kind of like Twisted Web does for Python) rather than files on your filesystem.

使用 Blackbox,您可以快速創建個人文件共享器,編寫通過 HTTP 相互通信的應用程序,並為無頭應用程序輕松創建 web 控制接口。

它基本上是一個漂亮的 package 中的Comet 服務器 我希望我能多說一些,但我仍然想自己弄清楚 sockets ......

請注意“Comet 演示”窗口是我的一個混合物,所以當你運行演示時不要沮喪,只要打開你的瀏覽器!

這是非常簡單的 Websocket 回顯服務器,我使用普通的 Sockets 實現了它。

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

在嘗試在套接字上接收數據之前嘗試發送握手數據

這是一個可能對您有所幫助的示例

網襪樣本

暫無
暫無

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

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