套路化编程:C++与C#之间的zlib(libz)压缩传输

初级代码游戏的专栏介绍与文章目录-CSDN博客

我的github:codetoys

        因为需要压缩,因为一端是C++,所以用了zlib(libz),因为用了zlib,所以C#端也要用兼容zlib格式的方法。

目录

一、技术要点

1.1 如何传输原始长度

1.2 解压时准备的缓冲区不够怎么办

1.3 压缩格式是什么

二、代码

2.1 C++ zlib的包装类

2.2 C# DeflateStream解压缩

2.3 C# 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。

//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自带的,能用尽量用。

		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差不多,除了构造函数少一个参数(因为压缩和解压缩是两个类,不需要用参数来区分)。

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编码的压缩数据(之前有八个字节的原始长度)。

(这里是结束)

  • 23
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值