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

Posted

技术标签:

【中文标题】C# - Websocket - 将消息发送回客户端【英文标题】:C# - Websocket - Sending Message Back To Client 【发布时间】:2015-01-17 06:08:19 【问题描述】:

我已经在 C# Web Socket 服务器上工作了大约 24 小时。

我目前已经弄清楚如何完成握手并初始化连接。

我还想出了如何获取byte[] 数据并将其解码为原始字符串。

但现在我陷入困境并寻求帮助。

我似乎无法弄清楚如何组合正确的数据结构并将其发送回客户端。 如果您发送原始数据,您在客户端收到的 WebSocket 会告诉您数据不能被屏蔽(这就是它需要解码的原因)

所以基本上,我要问的是如何构造响应数据以发送回 WebSocket 客户端?

我一直使用https://www.rfc-editor.org/rfc/rfc6455 作为我的研究资源。

请记住,我只是为此使用常规套接字。

这是我的解码代码:

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


【问题讨论】:

您学习 Socket 是为了好玩还是为了完成工作?如果是后者,你检查过 TcpClient 吗?它为你做了很多工作。 我认为手动操作是一种更好的学习体验。 【参考方案1】:

@vtortola 感谢您发布链接和解释 - 我花了很多时间研究它和一个开源代码库(基本上是我自己编写的),我将它提炼成这个,以便从服务器发送消息到客户。

对我来说,关键是要意识到几件事:

首先,了解标题。 GetHeader() 负责它是否是最后一帧以及操作码是否设置为连续帧的文本。 @vtortola 发布的链接对此进行了解释,但在我看到这些位之前,我必须真正盯着它:

This 的帖子实际上解释得很好,但你必须花时间研究它——注意 FIN 和操作码位如何匹配 GetHeader() 的工作:

客户端:FIN=1, opcode=0x1, msg="hello" 服务器:(立即处理完成消息)嗨。 客户端:FIN=0opcode=0x1,msg="and a" 125 服务器:(正在侦听,包含文本的新消息已启动) 客户端:FIN=0opcode=0x0,msg="happy new" 服务器:(侦听,负载连接到上一条消息) 客户端:FIN=1opcode=0x0,msg="year!" 服务器:(处理完成消息)也祝你新年快乐!

接下来,了解您在调用 stream.Write() 时发送的内容 - bytes[], index, LENGTH OF Bytes You're SENDING ;)


注意:我的意图是向 Web 客户端发送 JSON 格式的字符串和从 Web 客户端发送 JSON 格式的字符串,因此我的操作码设置为文本(我的示例基于 假设您要发送字符串数据),但您可以 也发送其他类型。

SendMessageToClient() 基本上将消息分成 125 个块并创建一个要从中提取的队列。根据我们在队列中的位置,使用适当的 FIN 和操作码标志创建标头。最后,准备好标题后,用字符串块的实际长度(

此时,您的标头已正确创建(FIN、rsv1、2、3 设置正确,操作码和掩码以及有效负载的大小)。现在发送它。

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

除了上面的帖子,我还研究了websocket-sharp's 代码库。我真的很想学习如何做到这一点并编写自己的服务器/客户端,并且能够学习该代码库以及创建基本 c# WebSocket 服务器的一个很好的起点here。

最后,我感谢上帝耐心地阅读所有这些;)

【讨论】:

我真的不认为我自己能解决这个问题。我抓住了你的代码并成功了。 该代码非常完美,做得很好,感谢您节省了大量时间!!【参考方案2】:

看看这两篇关于编写 C# WebSocket 服务器的文章:

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

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

似乎它们都是同一篇文章,但它们不是!在第一个链接的this part 中有关于如何构建框架的说明。

更新

第一个字节包含几个信息:

FIN :表示该帧包含完整的消息。 RSV1:选项 1 RSV2:选项 2 RSV3:选项 3 OptCode:表示帧的类型。

如果您想发送小于 125 个字节的文本消息,假设您的消息有 90 个字节,在第一个字节中,您会将位 0 ​​设置为 1(更重要),接下来的 3 设置为 0,除非您想要启用选项,接下来的 4 将是 0001,表示文本框。所以你的第一个再见将是 10000001 或 129。

现在在第二个字节中,第一位指示帧是否被屏蔽。您不屏蔽从服务器到客户端的帧,因此设置为 0。接下来的 7 位表示长度或帧长度的类型。因为您正在发送一个小帧,所以您可以在这 7 位中指示最多 125 的任何值。因此,由于帧长度为 90 字节,因此标头的第二个字节将为 01011010,即 90。

因此,当从服务器向客户端发送 90 个字节的文本帧时,符合标头的前两个字节将是 129 和 90。消息的其余部分将是 90 个字节的 UTF8 编码字节。

如果帧长于 125 字节,也是标头的长度,请检查规范。如果需要对帧进行屏蔽(例如从客户端获取的帧),则正文的前 4 个字节包含屏蔽键。如您所见,有一些花絮需要处理,所以我建议您阅读规范:https://www.rfc-editor.org/rfc/rfc6455

【讨论】:

好的,我已经看过几十次了,但我不太明白如何在byte[] 中构建框架。我知道第一个字节是 1,因为它是一个文本数据包,但在那之后我该怎么办? 不是真的,检查我的更新。它解释了如何构建小文本框架的标题。 很烦人,你不只是包含一个代码示例。当然,我现在有时间进行位移位横冲直撞,如果您问我,这应该包含在 C# 中。这是一个糟糕的答案。如果我决定编写此代码,我会将其发布为不同的答案,但我很可能会选择与 websockets 不同的解决方案。 “这就是 WebSockets 的魔力。然而,从这些所谓的数据“帧”中提取信息并不是那么神奇的体验。” => 确实

以上是关于C# - Websocket - 将消息发送回客户端的主要内容,如果未能解决你的问题,请参考以下文章

无法解码来自 Websocket 的消息

c# webapi websocket 服务端消息发送

Websockets:从NodeJS websocket服务器到带有WebSocketSharp的C#客户端的多个响应

Spring:向 websocket 客户端发送消息

异步客户端/服务器通信 C#

如何使用java服务器将消息发送到特定的websocket连接