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

标签 c# c#-4.0 websocket server-side server

我已经在 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");
        }
    }

}

最佳答案

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

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

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

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

  • Client: FIN=1, opcode=0x1, msg="hello"<-- 这是一条消息,长度 <= 125
  • 服务器:(立即处理完整消息)嗨。
  • 客户端:FIN=0opcode=0x1,msg="and a"<--开始多帧消息,长度 > 125
  • 服务器:(正在收听,开始包含文本的新消息)
  • 客户端:FIN=0opcode=0x0,msg="happy new"<--继续多帧消息
  • 服务器:(监听,有效负载连接到之前的消息)
  • 客户端:FIN=1opcode=0x0,msg="year!"<-- 结束多帧消息
  • 服务器:(处理完整消息)祝你新年快乐!

接下来,了解调用 stream.Write() 时发送的内容 - bytes[]、索引、发送的字节长度 ;)


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() 基本上将消息分成 125 个 block 并创建一个要从中提取的队列。根据我们在队列中的位置,使用 FIN 和操作码的正确标志创建 header 。最后,准备好 header 后,用字符串 block 的实际长度 (<= 125) 回填 header 的其余部分。然后把header转成byte数组后写入stream。

此时,您的 header 已正确创建(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代码库。我真的很想学习如何做到这一点并编写我自己的服务器/客户端,并且能够这样做研究该代码库以及一个很好的起点 here用于创建基本的 c# WebSocket 服务器。

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

关于C# - Websocket - 将消息发送回客户端,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/27021665/

相关文章:

javascript - 如何重写 WebSocket send() 方法

c# - 窗口关闭时正在运行的任务会发生什么情况?

c# - 无法将 FSCTL_GET_RETRIEVAL_POINTERS 调用移植到 C#

c# - Ejabberd 模仿

c#-4.0 - 如何使用具有 IEnumerable<T> 类型属性的 UIHint 创建 EditorTemplate

c# - 如何逐步显示 div 标签

asp.net-mvc-3 - 匿名类型成员声明符无效。匿名类型成员必须使用成员赋值、简单名称或成员访问进行声明

websocket - 适用于 Windows 或 Ubuntu 9.04 的 Web 套接字服务器

xcode - Mac OS X 上的 Cocoa Websocket 服务器

c# - 将条件存储为变量