简体   繁体   English

C# - Websocket - 将消息发送回客户端

[英]C# - Websocket - Sending Message Back To Client

I've been working on a C# Web Socket server for roughly 24 hours.我已经在 C# Web Socket 服务器上工作了大约 24 小时。

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.我还想出了如何获取byte[]数据并将其解码为原始字符串。

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) .如果您发送原始数据,您在客户端收到的 WebSocket 会告诉您数据不能被屏蔽(这就是为什么需要对其进行解码)

So basically, what I'm asking in short is how do I structure response data to send back to the WebSocket client?所以基本上,我要问的是如何构造响应数据以发送回 WebSocket 客户端?

I've been using http://tools.ietf.org/html/rfc6455 as a resource for my research.我一直在使用http://tools.ietf.org/html/rfc6455作为我的研究资源。

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

}

@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. @vtortola 感谢您发布链接和解释——我花了很多时间研究它和一个开源代码库(基本上是我自己写的),然后我将其提炼成这个,以便从服务器向客户端发送消息。

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. GetHeader() 负责它是否是最后一帧以及操作码是否设置为连续帧的文本。 The link @vtortola posted explains it, but I had to really stare at it before I saw the bits: @vtortola 发布的链接对此进行了解释,但在看到这些内容之前,我必须真正盯着它:

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: 这篇文章实际上很好地解释了它,但你必须花时间研究它——注意 FIN 和操作码位如何匹配 GetHeader() 的工作:

  • Client: FIN=1, opcode=0x1, msg="hello" <-- this is a single message, length <= 125 Client: FIN=1, opcode=0x1, msg="hello" <-- 这是一条消息,长度<= 125
  • Server: (process complete message immediately) Hi.服务器:(立即处理完成消息)嗨。
  • Client: FIN=0 , opcode=0x1 , msg="and a" <-- begin multi-frame message, length > 125客户端: FIN=0 , opcode=0x1 , msg="and a" <-- 开始多帧消息,长度 > 125
  • Server: (listening, new message containing text started)服务器:(正在收听,包含文本的新消息已开始)
  • Client: FIN=0 , opcode=0x0 , msg="happy new" <-- continue multi-frame message Client: FIN=0 , opcode=0x0 , msg="happy new" <-- 继续多帧消息
  • Server: (listening, payload concatenated to previous message)服务器:(侦听,有效负载连接到上一条消息)
  • Client: FIN=1 , opcode=0x0 , msg="year!"客户端: 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 ;)接下来,了解您在调用 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.注意:我的意图是将 JSON 格式的字符串发送到 Web 客户端和从 Web 客户端发送,所以我的操作码是为文本设置的(这里的示例基于您想要发送字符串数据的假设),但您可以发送其他类型以及。

SendMessageToClient() basically splits the message into 125 chunks and creates a que to be pulled from. SendMessageToClient()基本上将消息分成 125 个块并创建一个要从中拉出的队列。 Based on where we're at in the que, the header is created with the proper flags for FIN and opcode.根据我们在队列中的位置,使用适当的 FIN 和操作码标志创建标头。 Finally, with the header ready, backfill the rest of the header with the actual length of the chunk of string (<= 125).最后,准备好标题后,用字符串块的实际长度(<= 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).此时,您的标头已正确创建(FIN、rsv1、2、3 设置正确,操作码和掩码以及有效负载的大小)。 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.除了上面的帖子,我还研究了websocket-sharp 的代码库。 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.我真的想学习如何做到这一点,写我自己的服务器/客户端,并能做到学习代码库以及作为一个伟大的起点, 在这里创建一个基本的C#的WebSocket服务器。

Finally, I thank God for the patience to read through all of this ;)最后,我感谢上帝耐心阅读所有这些内容;)

Take a look to this two articles about writing C# WebSocket servers:看看这两篇关于编写 C# WebSocket 服务器的文章:

https://developer.mozilla.org/en-US/docs/WebSockets/Writing_WebSocket_servers https://developer.mozilla.org/en-US/docs/WebSockets/Writing_WebSocket_servers

https://developer.mozilla.org/en-US/docs/WebSockets/Writing_WebSocket_server https://developer.mozilla.org/en-US/docs/WebSockets/Writing_WebSocket_server

It seems like they are both the same article, but they are not!看起来它们都是同一篇文章,但它们不是! In this part of the first link you have the explanation about how to build frames.在第一个链接的这一部分中,您将解释如何构建框架。

UPDATE更新

The first byte contains several information:第一个字节包含几个信息:

  • FIN :Indicates that the frame contains a complete message. FIN :表示该帧包含完整的消息。
  • RSV1: Option 1 RSV1:选项 1
  • RSV2: Option 2 RSV2:选项 2
  • RSV3: Option 3 RSV3:选项 3
  • OptCode: Indicates the type of frame. OptCode:表示帧的类型。

If you want to send a Text message smaller than 125 bytes, lets say your message has 90 bytes, in the first byte you will put the bit 0 to 1(more significant), the next 3 to 0, unless you want to enable options, and the next 4 would be 0001, indicating a Text frame.如果您想发送小于 125 个字节的文本消息,假设您的消息有 90 个字节,在第一个字节中,您将把位 0 设置为 1(更重要),接下来的 3 设置为 0,除非您想启用选项,接下来的 4 将是 0001,表示文本框架。 So your first bye would be 10000001, or 129.所以你的第一次再见将是 10000001 或 129。

Now in the second byte, the first bit indicates if the frame is masked.现在在第二个字节中,第一位指示帧是否被屏蔽。 You don't mask frames from server to client, so you set a 0. The next 7 bits indicates rather the length, or the type of frame length.您不会屏蔽从服务器到客户端的帧,因此您设置了 0。接下来的 7 位表示长度或帧长度的类型。 Because you are sending a small frame, you can indicate any value up to 125 in those 7 bits.因为您正在发送一个小帧,所以您可以在这 7 位中指示最多 125 的任何值。 So because the frame length is 90 bytes, the second byte of the header would be 01011010, or 90.因此,因为帧长度是 90 字节,所以标头的第二个字节将是 01011010,即 90。

So when sending from server to client a Text frame of 90 byes, the first two bytes conforming the header would be 129 and 90. The rest of the message would be 90 bytes of UTF8 encoded bytes.因此,当从服务器向客户端发送 90 字节的文本帧时,符合标头的前两个字节将是 129 和 90。消息的其余部分将是 90 字节的 UTF8 编码字节。

If the frame is longer than 125 bytes, also is the length of the header, check the spec.如果帧长于 125 字节,头的长度也是如此,请检查规范。 If the frame need to be masked (like the frames you get from the client), the first 4 bytes of the body contain the masking key.如果需要屏蔽帧(如您从客户端获取的帧),则正文的前 4 个字节包含屏蔽键。 As you see there are some tidbits to cope with, so I recommend you to read the spec : https://tools.ietf.org/html/rfc6455如您所见,有一些花絮需要处理,因此我建议您阅读规范: https : //tools.ietf.org/html/rfc6455

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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