C# HTML5 Websocket Server

后端 未结 4 436
醉话见心
醉话见心 2020-12-28 09:50

I\'m trying to create a C# Websocket server but I just don\'t seem to get it working. I now have a server that accepts the TCPClient, receives the HTTP request from the clie

相关标签:
4条回答
  • 2020-12-28 10:23

    Here is very simple Websocket echo server which I implemented using plain 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;
            }
        }
    }
    
    0 讨论(0)
  • 2020-12-28 10:30

    Here's a sample server I wrote illustrating the handshake phase in accordance to the 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;
        }
    }
    

    And a sample client:

    <!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>
    
    0 讨论(0)
  • 2020-12-28 10:30

    Try sending the handshake data before trying to receive data on the socket

    Here is an example that might help you

    websocksample

    0 讨论(0)
  • 2020-12-28 10:42

    Do't know if you can compile Objective C, but this project is really pretty cool...

    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.

    With Blackbox, you can create personal file sharers in a snap, write applications that communicate with each other over HTTP, and easily create web control interfaces for headless applications.

    It basically is a Comet server in a nice package. I wish I could say more about it, but I'm still kind of trying to figure out sockets myself...

    Please note the "Comet demo" window is a concoction of mine, so don't get frustrated when you run the demo, just open your browser!

    0 讨论(0)
提交回复
热议问题