websocket successfully handshaking, but not sending receiving messages correctly (C# Server)

12,015

Solution 1

Messages aren't sent as plain text in recent versions of the spec. See the data framing section for details.

This wiki post is also very helpful.

I've also written a C++ server; the WsProtocol80 class shows how to read/write data.

EDIT: In your sample sending code, the 0x04 byte specifies a 4-byte message. You can set different values and send messages up to 125 bytes in this way. When you change the message length, you'd also have to update the last parameter to l_Stream.Write (which specifies the number of bytes to write). Changing it to lb.Count in all cases would seem better.

If you're still finding bitwise operations confusing and later want to send longer messages or read messages from the client, the link above to a wiki post includes pseudocode which should be helpful.

Solution 2

I had the same problem, i found the solution:

            lb = new List<byte>();
            lb.Add(0x81);
            size = message.Length;//get the message's size
            lb.Add((byte)size); //get the size in bytes
            lb.AddRange(Encoding.UTF8.GetBytes(message));
            stream.Write(lb.ToArray(), 0, size+2); //I do size+2 because we have 2 bytes plus 0x81 and (byte)size

With this solution you can send a larger message but only < 127 characters

note: excuse me but my english is not so good. ^^

Share:
12,015
Christopher Johnson
Author by

Christopher Johnson

Updated on June 05, 2022

Comments

  • Christopher Johnson
    Christopher Johnson almost 2 years

    After fighting all morning, I got the handshake to work but now I'm stuck actually sending and receiving messages. I've been searching for answers to no avail so thought I'd finally step up here and ask :/

    My client so far is very simple:

    function testWebSocket() {
        if (!window.WebSocket) {
            alert('WebSockets are NOT supported by your browser.');
            return;
        }
    
        try {
            var ws = new WebSocket('ws://localhost:8181/websession');
            ws.onopen = function () {
                alert('Handshake successfully established. Ready for data...');
            };
    
            ws.onmessage = function (e) {
                alert('Got WebSockets message: ' + e.data);
            }
    
            ws.onclose = function () {
                alert('Connection closed.');
            };
        }
        catch (e) {
            alert(e);
        }
    }
    

    yes, I've done a lot of code borrowing for this...I'm just trying to get a proof of concept working with a simple "chat application"

    my server is composed mostly of two classes, the SocketServer.cs and the SocketClient.cs

    they follow:

    SocketServer.cs

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Net.Sockets;
    using System.Net;
    using System.IO;
    
    namespace WebSocketServer.Entities
    {
        class SocketServer
        {
            public static Form1 parentForm;
            TcpListener socketServer;
            public static List<SocketClient> ClientList = new List<SocketClient>();
    
    
            public SocketServer(Form1 pForm)
            {
    
                parentForm = pForm;
                parentForm.ApplyText("Socket Class Initiated\r\n");
                socketServer = new TcpListener(IPAddress.Any, 8181);
                // tell the console that it's started
                parentForm.ApplyText("Socket Server Started\r\n");
    
                // create continuous loops to listen for new connections
                // start the listener
                socketServer.Start();
                while (true)
                {
                    // check for any incoming pending connections
                    // create new socket client for new connection
                    TcpClient socketConnection = socketServer.AcceptTcpClient();
                    DateTime now = DateTime.Now;
                    //write message to console to indicate new connection
                    parentForm.ApplyText("New Client Connected - " + now.ToString("MM/dd/yyyy h:mm:ss tt") + "\r\n");
                    // create new client object for this connection
                    SocketClient socketClient = new SocketClient(socketConnection, parentForm);
                }
            }
    
            public static void CloseClient(SocketClient whichClient)
            {
                ClientList.Remove(whichClient);
                whichClient.Client.Close();
                // dispose of the client object
                whichClient.Dispose();
                whichClient = null;
                parentForm.ApplyText("Client Disconnected\r\n");
            }
    
    
    
            public static void SendTextToClient(SocketClient sc, string text)
            {
                StreamWriter writer = new StreamWriter(sc.Client.GetStream());
                // check if client is still connected, then send the text string
                try
                {
                    if (sc.Client.Connected)
                    {
                        writer.WriteLine(text);
                        writer.Flush();
                        writer = null;
                    }
                }
                catch
                {
                    CloseClient(sc);
                }
    
            }
    
    
            public static void SendBroadcast(string text)
            {
                StreamWriter writer;
                // loop through the array and send text to all clients
                foreach (SocketClient client in ClientList)
                {
                    if (client.Client.Connected)
                    {
                        try
                        {
                            writer = new StreamWriter(client.Client.GetStream());
                            writer.WriteLine(text);
                            writer.Flush();
                            writer = null;
    
                        }
                        catch
                        {
                            CloseClient(client);
                        }
                    }
                }
            }
        }
    }
    

    SocketClient.cs

    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.Net.Sockets;
    using System.IO;
    using System.Threading;
    using System.Security.Cryptography;
    
    namespace WebSocketServer.Entities
    {
        class SocketClient
        {
            public TcpClient Client;
            StreamReader reader;
            StreamWriter writer;
            Form1 parentForm;
    
    
            public SocketClient(TcpClient client, Form1 pForm)
            {
                parentForm = pForm;
                Client = client;
                Thread clientThread = new Thread(new ThreadStart(StartClient));
                clientThread.Start();
            }
    
    
            private void StartClient()
            {
                SocketServer.ClientList.Add(this);
                // create a reader for this client
                reader = new StreamReader(Client.GetStream());
                // create a writer for this client
                writer = new StreamWriter(Client.GetStream());
    
                var headers = new Dictionary<string, string>();
    
                string line = "";
                while ((line = reader.ReadLine()) != string.Empty)
                {
                    if (!string.IsNullOrEmpty(line))
                    {
                        var tokens = line.Split(new char[] { ':' }, 2);
                        if (!string.IsNullOrWhiteSpace(line) && tokens.Length > 1)
                        {
                            headers[tokens[0]] = tokens[1].Trim();
                        }
                    }
                }
    
    
                String secWebSocketAccept = ComputeWebSocketHandshakeSecurityHash09(headers["Sec-WebSocket-Key"]);
    
                // send handshake to this client only
                writer.WriteLine("HTTP/1.1 101 Web Socket Protocol Handshake");
                writer.WriteLine("Upgrade: WebSocket");
                writer.WriteLine("Connection: Upgrade");
                writer.WriteLine("WebSocket-Origin: http://localhost:63422/");
                writer.WriteLine("WebSocket-Location: ws://localhost:8181/websession");
                writer.WriteLine("Sec-WebSocket-Accept: " + secWebSocketAccept);
                writer.WriteLine("");
                writer.Flush();
    
                SocketServer.SendBroadcast("New Client Connected");
    
                Thread clientRun = new Thread(new ThreadStart(RunClient));
                clientRun.Start();
            }
    
            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;
             }
    
    
            private void RunClient()
            {
                try
                {
                    string line = "";
                    while (true)
                    {
                        line = reader.ReadLine();
                        if (!string.IsNullOrEmpty(line))
                        {
                            parentForm.ApplyText(line + "\r\n");
                            SocketServer.SendBroadcast(line);
                        }
                    }
                }
                catch
                {
                    parentForm.ApplyText("Client Disconnected\r\n");
                    SocketServer.CloseClient(this);
                }
            }
    
            public void Dispose()
            {
                System.GC.SuppressFinalize(this);
            }
        }
    
    }
    

    I can connect with multiple instances in Chrome and my server shows all of the clients connecting and I'm seeing the alert that the handshake was successful. But when I try sending text from the client (code not shown above, but it's a pretty straightforward ws.send(text) kinda thing), it comes across to the server as garbled text. When I try and do a writer.WriteLine("whatever") from the server to the client, the onmessage event never fires. I've done a LOT of looking around after I finally got the handshake sorted out, and can't find any good examples of how to resolve this.

    should I not be using a StreamWriter? Am I missing something else in my handshake (a protocol maybe).

    TIA for looking and helping.

    Edit:

    The below code works but I don't know how to modify it to allow for dynamic sized text lengths. I'm ok with just being able to send text that is 127 or less at this point, but I can't seem to get a handle on how to even get past 4.

    public static void SendBroadcast(string text)
    {
        StreamWriter writer;
        // loop through the array and send text to all clients
        foreach (SocketClient client in ClientList)
        {
            if (client.Client.Connected)
            {
                try
                {
                    NetworkStream l_Stream = client.Client.GetStream();  
                    List<byte> lb = new List<byte>();
                    lb.Add(0x81);
                    lb.Add(0x04);
                    lb.AddRange(Encoding.UTF8.GetBytes("test"));
                    l_Stream.Write(lb.ToArray(), 0, 6);
                }
                catch
                {
                    CloseClient(client);
                }
            }
        }
    }
    

    I've tried modifying lb.Add(0x04) to lb.Add(0x07) and sending "testing" no dice. I'm also confused as to what the l_Stream.Write() parameters are. I know it's the byte array, offset, and size but size of what?

  • Christopher Johnson
    Christopher Johnson over 12 years
    wow that is very greek to me. I ended up being able to send down "test" to the client by switching to a NetworkStream from a StreamWriter and writing out a byte array from a sample I found but it limited the text being sent to only 4 chars (I assume one of the byte values I added to the array, caused that limitation but I don't know much about bytes et al). Thanks for your C++ example but I've never even looked at anything C++ before so it wasn't very helpful. I'd like to be able to use StreamWriter instead of NetworkStream, and I'm trying to educate myself better on bytes, but struggling.
  • Christopher Johnson
    Christopher Johnson over 12 years
    I updated my OP with an edit of what I have working to send "test" down to the client. Any chance you could provide some insight as to my questions in that edit?
  • Christopher Johnson
    Christopher Johnson over 12 years
    thanks that was a big help. I have one other question about generating the hex value to add to my byte array, but that's out of the scope of this question.