我经常收到来自MessageWebSocket连接的消息,这些消息是用zlib压缩的。下面的代码将它们解压缩为一个流,该流可以用JSON.Net的JsonReader读取。
我很肯定还有一些改进要做,但我不能完全理解:
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
}
}
}
}发布于 2018-06-22 12:05:11
我认为您在一个函数中做的太多了,它隐藏了我想要阅读的总体情况,而没有深入了解实现细节。首先,我要做一个概括性的概述:
using (var reader = new StreamReader(e.GetDataStream().AsStreamForRead())
{
SkipOptionalZlibHeader(reader);
var uncompressedStream = ReadAndDecompressStream(reader);
DecodeJson(uncompressedStream);
}通过以下方式:
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。然后,我们可以这样实施:
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,并将其名称更改为更有意义的内容:
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);
}现在,调用方拥有了可丢弃对象的所有权:
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),而要具体。
https://codereview.stackexchange.com/questions/197037
复制相似问题