在 .net 核心中创建 "shared zlib context"

Creating a "shared zlib context" in .net core

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

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

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

Transport Compression: Currently the only available transport compression option is zlib-stream. You will need to run all received packets through a shared zlib context, as seen in the example below. Every connection to the gateway should use its own unique zlib context.

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

我假设这是因为我正在实例化以解压缩第一个响应的某些部分需要为该连接的未来响应持续存在(只是按照他们的文档所说),但我不是确定在 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 上下文接收到的数据包”究竟意味着什么? 是否有一些高阶压缩上下文需要在给定连接的所有解压缩任务中保留?还有别的吗?

提前致谢,如果我可以澄清或添加任何其他详细信息,请告诉我!

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

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

正如 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 发送给您的第一个数据包中删除 header。或者您可以将 expectRFC1950Header 设置为 true 并将默认的 header 78 9c 添加到每个数据包(尽管我推荐前者)。