首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >用System.IO.Compression泄气流

用System.IO.Compression泄气流
EN

Code Review用户
提问于 2018-06-22 07:10:12
回答 1查看 2K关注 0票数 5

我经常收到来自MessageWebSocket连接的消息,这些消息是用zlib压缩的。下面的代码将它们解压缩为一个流,该流可以用JSON.Net的JsonReader读取。

我很肯定还有一些改进要做,但我不能完全理解:

代码语言:javascript
复制
private MemoryStream _compressed;
private DeflateStream _decompressor;
private void HandleMessage(object sender, MessageWebSocketMessageReceivedEventArgs e)
{
    using (var datastr = e.GetDataStream()?.AsStreamForRead())
        using (var ms = new MemoryStream())
            {
                datastr.CopyTo(ms);
                ms.Position = 0;
                byte[] input = new byte[ms.Length];
                ms.Read(input, 0, (int)ms.Length);
                int index = 0;
                int length = input.Length;
                using (var output = new MemoryStream())
                {
                    if (input[0] == 0x78)
                    {
                        //Remove the zlib header
                        _compressed.Write(input, index + 2, length - 2);
                        _compressed.SetLength(length - 2);
                    }
                    else
                    {
                        _compressed.Write(input, index, length);
                        _compressed.SetLength(length);
                    }

                    _compressed.Position = 0;
                    _decompressor.CopyTo(output);
                    _compressed.Position = 0;
                    output.Position = 0;

                    using (var reader = new StreamReader(output))
                    using (JsonReader jsreader = new JsonTextReader(reader))
                    {
                        //Deserialize JSON from the jsreader stream
                    }
                }
            }
}
EN

回答 1

Code Review用户

发布于 2018-06-22 12:05:11

我认为您在一个函数中做的太多了,它隐藏了我想要阅读的总体情况,而没有深入了解实现细节。首先,我要做一个概括性的概述:

代码语言:javascript
复制
using (var reader = new StreamReader(e.GetDataStream().AsStreamForRead())
{
    SkipOptionalZlibHeader(reader);
    var uncompressedStream = ReadAndDecompressStream(reader);
    DecodeJson(uncompressedStream);
}

通过以下方式:

代码语言:javascript
复制
const int ExpectedZLibHeaderMarker = 0x78;
const int ExpectedZLibHeaderSize = 2;

static void SkipOptionalZlibHeader(BinaryReader reader)
{
    if (!HasZlibHeader())
        return;

    reader.Seek(CalculateZLibHeaderSize(), SeekOrigin.Current);

    bool HasZlibHeader()
        => reader.PeekChar() == ExpectedZLibHeaderMarker;

    long CalculateZLibHeaderSize()
        => return ExpectedZLibHeaderSize;
}

在这里停一下。在这里,你做了一些巨大的假设:

  • 压缩标志始终是0x78。这不是真的,因为如果消息契约简单地说它是带有zlib头的压缩zlib流,那么他们可以在不破坏契约的情况下更改它。还请注意,没有理由原始压缩流不能以这个神奇的数字开始。您可能想要尝试解码被裁剪的流,如果它失败了,那么尝试解码它,因为它没有头。更好的是:确保它有(或没有)标头,并坚持它,否则您将部署可能失败的代码。
  • 标题大小总是两个字节。根据旗帜,可能会有更多。
  • 流中的其他一切都是数据。不,还有带校验和的页脚。

请注意,我们删除了一个无用的副本,因为SkipOptionalZlibHeader()只是查找标头。作为读者练习:如果底层流不支持查找(检查CanSeek属性),那么您需要读取并丢弃这些字节。

请注意,ReadAndDecompressStream()正在返回一个Stream,您多次重用相同的MemoryStream (如果您在内存占用和/或性能方面有一个可测量的和真正有益的增益,这是一个非常好的事情),这是一个实现细节,我想让它的调用方不知道这一点。而且,没有理由重用DeflateStream。然后,我们可以这样实施:

代码语言:javascript
复制
static Stream ReadAndDecompressStream(BinaryReader reader)
{
    // Go back to beginning for writing
    _output.Position = 0;

    using (var decompressor = new DeflateStream(_output, CompressionMode.Decompress, true))
    {
        reader.CopyTo(decompressor);
    }

    // Go back to beginning for reading
    _output.Position = 0;

    return _output;
}

有件事让我很困扰:我们正在返回一个IDisposable对象(MemoryStream),但是调用者没有它的所有权(然后它就不必释放它)。这是不直观的,它必须被记录下来。还请注意,您的实际代码之所以工作仅仅是因为有实现细节:StreamReader在提供的Stream上调用Dispose(),而您的代码之所以工作仅仅是因为处理MemoryStream没有可见的效果(但它是实现细节)。我们还得为此做点什么。

让我们首先将ReadAndDecompressStream()改为返回StreamReader,并将其名称更改为更有意义的内容:

代码语言:javascript
复制
static StreamReader CreateDecompressedReader(BinaryReader reader)
{
    // Go back to beginning for writing
    _output.Position = 0;

    using (var decompressor = new DeflateStream(_output, CompressionMode.Decompress, true))
    {
        reader.CopyTo(decompressor);
    }

    // Go back to beginning for reading
    _output.Position = 0;

    return new StreamReader(_output, Encoding.UTF8, false, _output.Length, true);
}

现在,调用方拥有了可丢弃对象的所有权:

代码语言:javascript
复制
using (var reader = new StreamReader(e.GetDataStream().AsStreamForRead())
{
    SkipOptionalZlibHeader(reader);
    using (var decompressedReader = CreateDecompressedReader(reader))
        DecodeJson(decompressedReader);
}

注意,我删除了?.,在您的原始代码中,如果e.GetDataStream()?返回null,那么对datastr.CopyTo()的第一次调用就会失败。如果可能发生这种情况,那么妥善处理这一案件。

作为最后一步,您应该添加一些错误检查。使用I/O可能会出错,那么你最好做好准备(特别是如果你把这些假设保持在适当的位置),只是不要添加catch (Exception),而要具体。

票数 7
EN
页面原文内容由Code Review提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://codereview.stackexchange.com/questions/197037

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档