Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C# - Websocket - Sending Message Back To Client

I've been working on a C# Web Socket server for roughly 24 hours.

I've currently figured out how to complete a handshake and get the connection initialized.

Also I've figured out how to take the byte[] data and decode it into a it's original string.

But now I'm stuck and looking for help.

I can't seem to figure out how to put together a proper data structure and send it back to the client. If you send the original data you received the WebSocket on the client-side will tell you the data cannot be masked (which is why it needs to be decoded).

So basically, what I'm asking in short is how do I structure response data to send back to the WebSocket client?

I've been using https://www.rfc-editor.org/rfc/rfc6455 as a resource for my research.

Please keep in mind that I'm just using a regular socket for this.

Here is my decode code:

if (dataBuffer.Length > 0)
{
    if (dataBuffer[0] == 129)
    {
        int msg_length = dataBuffer[1] - 128;
        if (msg_length <= 125)
        {
            // Msg ready to decode.
            Log.info("Message Length: " + msg_length);


            Byte[] decoded = new Byte[dataBuffer.Length];
            Byte[] encoded = new Byte[dataBuffer.Length - 6];

            Array.Copy(dataBuffer, 6, encoded, 0, msg_length);

            Byte[] key = new Byte[4] { dataBuffer[2], dataBuffer[3], dataBuffer[4], dataBuffer[5] };
            for (int i = 0; i < encoded.Length; i++)
            {
                decoded[i] = (Byte)(encoded[i] ^ key[i % 4]);
            }

            Log.info("MSG: " + Encoding.UTF8.GetString(decoded));

            byte[] return_msg = new byte[decoded.Length + 8];

            return_msg[0] = 1;
            return_msg[1] = 0;
            return_msg[2] = 0;
            return_msg[3] = 0;
            // OP Code
            return_msg[4] = 0x1;
            return_msg[5] = 0x0;
            return_msg[6] = 0x0;
            return_msg[7] = 0x0;

            Array.Copy(decoded, 0, return_msg, 8, decoded.Length);

            socket.Send(return_msg);
        }
        else if (msg_length == 126)
        {
            // Longer Message
            msg_length = dataBuffer[2] + dataBuffer[3];

            Log.info("Message Length: " + msg_length);

            Byte[] key = new Byte[4] { dataBuffer[4], dataBuffer[5], dataBuffer[6], dataBuffer[7] };

            Byte[] decoded = new Byte[dataBuffer.Length];
            Byte[] encoded = new Byte[dataBuffer.Length - 8];

            Array.Copy(dataBuffer, 8, encoded, 0, msg_length);

            for (int i = 0; i < encoded.Length; i++)
            {
                decoded[i] = (Byte)(encoded[i] ^ key[i % 4]);
            }

            Log.info("MSG: " + Encoding.UTF8.GetString(decoded));
            byte[] return_msg = new byte[decoded.Length + 4];

            return_msg[0] = 129;
            return_msg[1] = 0;
            return_msg[2] = 0;
            return_msg[3] = 0;
                    
            Array.Copy(decoded,0,return_msg,4,decoded.Length);

            socket.Send(return_msg);
        }
        else if (msg_length == 127)
        {
            // Huge Message:
            Log.info("BIG MESSAGE");
        }
    }

}
like image 210
Matthew Auld Avatar asked Feb 11 '23 13:02

Matthew Auld


1 Answers

@vtortola Thank you for posting the links and explanation - I spent a bunch of time studying that and an open source code base (to write my own essentially) and I distilled it down to this for sending a message from the server to the client.

The key, for me, was realizing a couple of things:

First, understand the header. GetHeader() takes care of whether or not its the final frame and whether or not the opcode is set to text of continuation frame. The link @vtortola posted explains it, but I had to really stare at it before I saw the bits:

This post actually explains it decently, but you have to take time and study it - take note of how the FIN and opcode bits match GetHeader()'s job:

  • Client: FIN=1, opcode=0x1, msg="hello" <-- this is a single message, length <= 125
  • Server: (process complete message immediately) Hi.
  • Client: FIN=0, opcode=0x1, msg="and a" <-- begin multi-frame message, length > 125
  • Server: (listening, new message containing text started)
  • Client: FIN=0, opcode=0x0, msg="happy new" <-- continue multi-frame message
  • Server: (listening, payload concatenated to previous message)
  • Client: FIN=1, opcode=0x0, msg="year!" <-- end multi-frame message
  • Server: (process complete message) Happy new year to you too!

Next, understand what you're sending when you call stream.Write() - bytes[], index, LENGTH OF BYTES YOU'RE SENDING ;)


NOTE: My intent was to send JSON formatted strings to and from the web client, so my opcode is set for text (and my example here is based on the assumption you're wanting to send string data), but you can send other types as well.

SendMessageToClient() basically splits the message into 125 chunks and creates a que to be pulled from. Based on where we're at in the que, the header is created with the proper flags for FIN and opcode. Finally, with the header ready, backfill the rest of the header with the actual length of the chunk of string (<= 125). Then write the header to the stream after converting it to a byte array.

At this point, your header is created properly (FIN, rsv1,2,3 set properly, opcode and mask as well as the size of the payload). Now send it.

public void SendMessageToClient(TcpClient client, string msg)
{
    NetworkStream stream = client.GetStream();
    Queue<string> que = new Queue<string>(msg.SplitInGroups(125));
    int len = que.Count;

    while (que.Count > 0)
    {
        var header = GetHeader(
            que.Count > 1 ? false : true,
            que.Count == len ? false : true
        );

        byte[] list = Encoding.UTF8.GetBytes(que.Dequeue());
        header = (header << 7) + list.Length;
        stream.Write(IntToByteArray((ushort)header), 0, 2);
        stream.Write(list, 0, list.Length);
    }            
}


protected int GetHeader(bool finalFrame, bool contFrame)
{
    int header = finalFrame ? 1 : 0;//fin: 0 = more frames, 1 = final frame
    header = (header << 1) + 0;//rsv1
    header = (header << 1) + 0;//rsv2
    header = (header << 1) + 0;//rsv3
    header = (header << 4) + (contFrame ? 0 : 1);//opcode : 0 = continuation frame, 1 = text
    header = (header << 1) + 0;//mask: server -> client = no mask

    return header;
}


protected byte[] IntToByteArray(ushort value)
{
    var ary = BitConverter.GetBytes(value);
    if (BitConverter.IsLittleEndian)
    {
        Array.Reverse(ary);
    }

    return ary;
}

/// ================= [ extension class ]==============>

public static class XLExtensions
{
    public static IEnumerable<string> SplitInGroups(this string original, int size)
    {
        var p = 0;
        var l = original.Length;
        while (l - p > size)
        {
            yield return original.Substring(p, size);
            p += size;
        }
        yield return original.Substring(p);
    }
}

Along with the post above, I studied websocket-sharp's code base. I literally wanted to learn how to do this and write my own server/client and was able to do so studying that code base as well as a great starting point here for creating a basic c# WebSocket server.

Finally, I thank God for the patience to read through all of this ;)

like image 137
neoRiley Avatar answered Feb 15 '23 09:02

neoRiley