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

Mat*_*uld 4 c# server-side websocket c#-4.0 server

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

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

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

但现在我被困住了,正在寻求帮助。

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

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

我一直在使用http://tools.ietf.org/html/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");
        }
    }

}
Run Code Online (Sandbox Code Playgroud)

neo*_*ley 5

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

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

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

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

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

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


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

SendMessageToClient()基本上将消息分成 125 个块并创建一个要从中提取的队列。根据我们在队列中的位置,使用适当的 FIN 和操作码标志创建标头。最后,准备好标题后,用字符串块的实际长度(<= 125)回填标题的其余部分。然后在将其转换为字节数组后将标头写入流。

此时,您的标头已正确创建(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);
    }
}
Run Code Online (Sandbox Code Playgroud)

除了上面的帖子,我还研究了websocket-sharp 的代码库。我真的想学习如何做到这一点,写我自己的服务器/客户端,并能做到学习代码库以及作为一个伟大的起点,在这里创建一个基本的C#的WebSocket服务器。

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