我的github:codetoys
因为需要压缩,因为一端是C++,所以用了zlib(libz),因为用了zlib,所以C#端也要用兼容zlib格式的方法。
目录
[1.1 如何传输原始长度](#1.1 如何传输原始长度)
[1.2 解压时准备的缓冲区不够怎么办](#1.2 解压时准备的缓冲区不够怎么办)
[1.3 压缩格式是什么](#1.3 压缩格式是什么)
[2.1 C++ zlib的包装类](#2.1 C++ zlib的包装类)
[2.2 C# DeflateStream解压缩](# DeflateStream解压缩)
[2.3 C# SharpZipLib压缩](# SharpZipLib压缩)
一、技术要点
1.1 如何传输原始长度
压缩库不会传递原始长度信息,必须自己想办法传递。我的办法是先传输长度,再传输压缩后的数据。
1.2 解压时准备的缓冲区不够怎么办
zlib的解压缩返回码会指示缓冲区不足,加大之后再次尝试。C#上使用流,没有这个问题。
1.3 压缩格式是什么
zlib的压缩格式是Deflate,C#的DeflateStream的压缩格式现在也是Deflate(.net某个版本以前不是),但是是Raw Deflate,也就是没有两字节的头部格式说明和四字节的尾部校验和。所以呢,zlib的压缩数据去掉前后两个和四个字节传递给C#的DeflateStream解压是没有问题的。
但是如果要传送数据给zlib解压缩,C#的DeflateStream的数据要加上头尾才行,经过了一些尝试之后仍然失败,所以放弃了。改用第三方库SharpZipLib,使用方法和.net自带的DeflateStream几乎相同。
二、代码
2.1 C++ zlib的包装类
这个代码位于我的git库的ctfc项目的src/function/myZIP.h。依赖的头文件都在同一位置,除了zlib.h。
cpp
//myZIP.h 数据压缩
//
// Copyright (c) ct All rights reserved.
// 版权所有 ct 保留所有权利
//
#pragma once
#include "config.h"
#include "function.h"
#include "Buffer.h"
#include "zlib/zlib.h"
namespace ns_my_std
{
//数据压缩,压缩后数据自带解压缩以后长度,8字节,压缩方的字节序(zlib要求自己想办法传输原始长度)
class CMyZip
{
public:
typedef uint64_t T_LEN;
private:
static bool _UnCompress(char const * src, T_LEN srclen, CBuffer & output, long buf_override)
{
T_LEN outsize;//压缩数据记录的解压缩后长度
memmove(&outsize, src, sizeof(T_LEN));
//thelog << "记录的长度 " << CMyTools::ToHex((char*)&outsize, sizeof(T_LEN)) << " : " << outsize <<" 输入长度 "<< srclen << endi;
if (0 == outsize)
{
output.setSize(0);
return true;
}
if (!output.reserve(outsize * buf_override))
{
thelog << "内存不足" << ende;
return false;
}
uLongf len = output.capacity();
//thelog << "预设缓冲区 " << len << endi;
int ret = uncompress((unsigned char *)output.getbuffer(), &len, (unsigned char *)src + sizeof(T_LEN), srclen - sizeof(T_LEN));
if (0 != ret)
{
if (Z_MEM_ERROR == ret)
{
thelog << "解压缩失败 内存不足" << ende;
return false;
}
else if (Z_STREAM_ERROR == ret)
{
thelog << "解压缩失败 level错误" << ende;
return false;
}
else if (Z_BUF_ERROR)
{
thelog << "预设缓冲区不足 " << buf_override << " " << output.capacity() << " " << len << ende;
return _UnCompress(src, srclen, output, buf_override + 1);
}
else
{
thelog << "解压缩失败 " << ret << ende;
return false;
}
}
if (len != outsize)
{
thelog << "解压缩后长度与预期不一致 " << len << " " << outsize << ende;
return false;
}
output.setSize(len);
return true;
}
public:
//默认压缩级别
static bool Compress(char const * src, T_LEN srclen, CBuffer & output)
{
return Compress2(src, srclen, output, Z_DEFAULT_COMPRESSION);
}
//level 0-9 越大压缩率越高耗时越长
static bool Compress2(char const * src, T_LEN srclen, CBuffer & output, int level)
{
if (Z_DEFAULT_COMPRESSION == level);
else if (level < 0)level = 0;
else if (level > 9)level = 9;
else;
if (!output.reserve(compressBound(srclen) + sizeof(T_LEN)))
{
thelog << "内存不足" << ende;
return false;
}
uLongf len = output.capacity() - sizeof(T_LEN);
if (0 != compress2((unsigned char *)output.getbuffer() + sizeof(T_LEN), &len, (unsigned char *)src, srclen, level))
{
thelog << "压缩失败" << ende;
return false;
}
T_LEN tmp = srclen;
memmove(output.getbuffer(), &tmp, sizeof(T_LEN));
output.setSize(sizeof(T_LEN) + len);
return true;
}
static bool UnCompress(char const * src, T_LEN srclen, CBuffer & output)
{
return _UnCompress(src, srclen, output, 2);
}
public:
static int CZip_test(int argc, char ** argv)
{
CEasyFile file;
CBuffer input;
CBuffer output;
CBuffer output2;
if (!file.ReadFile("随便一个文件名", input))
{
thelog << "读文件失败" << ende;
return __LINE__;
}
if (!Compress(input.data(), input.size(), output))return __LINE__;
thelog << input.size() << " " << output.size() << " " << (100 - output.size() * 100 / (input.size() ? input.size() : 1)) << "%" << endi;
if (!UnCompress(output.data(), output.size(), output2))return __LINE__;
thelog << input.size() << " 解压缩后 " << output2.size() << endi;
if (!input.Compare(output2))return __LINE__;
for (long i = 0; i < 10; ++i)
{
if (!Compress2(input.data(), input.size(), output, i))return __LINE__;
thelog << i << " " << input.size() << " " << output.size() << " " << (100 - output.size() * 100 / (input.size() ? input.size() : 1)) << "%" << endi;
}
return 0;
}
};
}
这个代码很简单,自带测试。
除了里面使用了zlib来压缩和解压缩之外,做一点额外工作:
压缩后数据的前八个字节是原始数据长度,发送方的字节序。
按道理应该用网络字节序,这样比较有通用性,不过目前用到的设备字节序都相同,所以还没有做。
注意不同CPU上数据类型的差异,用作传输的部分的长度和字节序都是必须一致的。
2.2 C# DeflateStream解压缩
因为DeflateStream是.net自带的,能用尽量用。
cs
static public string UnCompressMesssage(string text)
{
try
{
byte[] zipdata = Convert.FromBase64String(text);
MemoryStream zipstream = new MemoryStream(zipdata.Skip(10).Take(zipdata.Length - 14).ToArray());//前面8字节原始长度(C++代码额外增加的),DeflateStream格式前面少2个字节,后面少4个字节
DeflateStream deflateStream = new DeflateStream(zipstream, CompressionMode.Decompress);
MemoryStream resultMemoryStream = new MemoryStream();
deflateStream.CopyTo(resultMemoryStream);
text = System.Text.Encoding.UTF8.GetString(resultMemoryStream.ToArray());
deflateStream.Close();
}
catch (Exception ex)
{
text = ex.ToString();//如果出错返回的是异常信息,换成别的吧
}
return text;
}
这个代码针对UTF-8的字符串进行操作,如果数据不是UTF-8,可以使用base64编码后再操作。输入数据是经过了base64编码的压缩数据(咳咳,又损失了一部分压缩效果,因为传输过程还有些别的要求),输出是还原的字符串。
2.3 C# SharpZipLib压缩
代码和使用DeflateStream差不多,除了构造函数少一个参数(因为压缩和解压缩是两个类,不需要用参数来区分)。
cs
using ICSharpCode.SharpZipLib.Zip.Compression.Streams;
static public string CompressMesssage(string text)
{
try
{
MemoryStream uncompressed = new MemoryStream(Encoding.UTF8.GetBytes(text));
MemoryStream resultMemoryStream = new MemoryStream();
DeflaterOutputStream deflateStream = new DeflaterOutputStream(resultMemoryStream);//由于最终无法成功添加头尾信息,所以放弃使用DeflateStream,改为SharpZipLib
uncompressed.CopyTo(deflateStream);
deflateStream.Close();
byte[] tmp = new byte[8 + resultMemoryStream.ToArray().Length];
BitConverter.GetBytes((long)text.Length).CopyTo(tmp, 0);//必须是8位整数
resultMemoryStream.ToArray().CopyTo(tmp, 8);
text = Convert.ToBase64String(tmp);
}
catch (Exception ex)
{
text = ex.ToString();//这个出错处理要改一改
}
return text;
}
输入是字符串,输出是经过base64编码的压缩数据(之前有八个字节的原始长度)。
(这里是结束)