简体   繁体   中英

C# HTML5 Websocket Server

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 client and that tries to send back a HTTP response so the HTML5 WebSocket handshake can be completed.

I believe there is something wrong with my handshake that the server sends to the client. I read the draft (Websocket 76 draft ) which states that at the end of the handshake a response has to be given for the two keys that are given. This response gets calculated by the server.

This is my code:

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

Does anyone have a clue what I'm doing wrong because I'm getting quite desperate.

Thanks in advance

Dennis

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>

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...

请注意“Comet 演示”窗口是我的一个混合物,所以当你运行演示时不要沮丧,只要打开你的浏览器!

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

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

Here is an example that might help you

websocksample

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM