在 .net 核心中创建“共享 zlib 上下文”

时间:2021-02-07 15:07:17

标签: c# websocket discord compression zlib

我正在尝试为 Discord API 构建一个 WebSocket 客户端作为一个有趣的副项目,但我遇到了一个目前似乎无法解决的问题。

https://discord.com/developers/docs/topics/gateway#encoding-and-compression

在他们关于如何解压缩从他们的 API 返回的输入数据的示例中,他们说:

<块引用>

传输压缩:目前唯一可用的传输 压缩选项是 zlib-stream。您需要运行所有收到的 数据包通过共享的 zlib 上下文,如下面的示例所示。 与网关的每个连接都应使用其自己唯一的 zlib 上下文。

我第一次解压缩来自他们的响应(如一个连接)按预期工作,但第二次给我一个错误,说“未知的压缩方法”。

我假设这是因为我为解压第一个响应而实例化的部分内容需要持续用于此连接的未来响应(只是按照他们的文档所说),但我不确定是什么这实际上意味着在 C# 中我使用的是什么。

static byte[] Decompress(byte[] data)
{
    using var compressedStream = new System.IO.MemoryStream(data);
    using var zipStream = new ZlibStream(compressedStream, CompressionMode.Decompress);
    using var resultStream = new System.IO.MemoryStream();
    zipStream.CopyTo(resultStream);
    return resultStream.ToArray();
}

这是我用于解压的方法,ZlibStream 来自 Ionic.Zlib,但是使用它们的内置方法:“ZlibStream.UncompressString”,它似乎做同样的事情,也会产生同样的错误.

在这种情况下,“通过共享的 zlib 上下文运行所有接收到的数据包”究竟是什么意思? 是否有一些更高阶的压缩上下文需要在给定连接的所有解压缩任务中持续存在?还有什么?

提前致谢,如果我能澄清或添加任何其他细节,请告诉我!

2 个答案:

答案 0 :(得分:2)

正如 Mark Adler 所说,您需要以正确的顺序装入充气袋。这样它就可以引用前面的字节并可以使用它们来填补空白。

我已经设法使用 Ionic.Zlib.ZlibCodec 解决了这个问题,这使您可以更好地控制编解码器(这样它就不会在每次膨胀时都开始新的上下文)。

public class ZlibStreamContext
{
    private ZlibCodec _inflator;

    public ZlibStreamContext(bool expectRFC1950Header = false)
    {
        _inflator = new ZlibCodec();
        _inflator.InitializeInflate(expectRFC1950Header);
    }

    public byte[] InflateByteArray(byte[] deflatedBytes)
    {
        _inflator.InputBuffer = deflatedBytes;
        _inflator.AvailableBytesIn = deflatedBytes.Length;
        // account for a lot of possible size inflation (could be much larger than 4x)
        _inflator.OutputBuffer = new byte[deflatedBytes.Length * 4]; 
        _inflator.AvailableBytesOut = _inflator.OutputBuffer.Length;
        _inflator.NextIn = 0;
        _inflator.NextOut = 0;

        _inflator.Inflate(FlushType.Sync);
        return _inflator.OutputBuffer[0.._inflator.NextOut];
    }
}

我在最后删除了缓冲区中未使用的字节,但如果您只是要使用 System.Text.Encoding.UTF8.GetString(byteArray),则不需要。即使没有正确调整输出数组的大小,它也能工作。

注意:您需要从discord发送给您的第一个数据包中切掉标头。或者您可以将 expectRFC1950Header 设置为 true 并将默认标头 78 9c 添加到每个数据包(尽管我建议使用前者)。

答案 1 :(得分:1)

这意味着对于单个连接,您需要为整个连接保持 zlib 解压缩对象打开,并按顺序向其提供数据包,直到连接完成。对每个数据包的解压对象使用 Write 方法。

在连接完成的同时,zlib 流也应该通知你数据已经完成,因为格式是自终止的。此外,最后还有一个校验值,如果任何数据损坏,就会报告错误。