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

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

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

(这里是结束)

相关推荐
‘’林花谢了春红‘’42 分钟前
C++ list (链表)容器
c++·链表·list
向宇it2 小时前
【unity小技巧】unity 什么是反射?反射的作用?反射的使用场景?反射的缺点?常用的反射操作?反射常见示例
开发语言·游戏·unity·c#·游戏引擎
九鼎科技-Leo2 小时前
什么是 WPF 中的依赖属性?有什么作用?
windows·c#·.net·wpf
机器视觉知识推荐、就业指导2 小时前
C++设计模式:建造者模式(Builder) 房屋建造案例
c++
Heaphaestus,RC3 小时前
【Unity3D】获取 GameObject 的完整层级结构
unity·c#
baivfhpwxf20233 小时前
C# 5000 转16进制 字节(激光器串口通讯生成指定格式命令)
开发语言·c#
直裾3 小时前
Scala全文单词统计
开发语言·c#·scala
Yang.994 小时前
基于Windows系统用C++做一个点名工具
c++·windows·sql·visual studio code·sqlite3
熬夜学编程的小王4 小时前
【初阶数据结构篇】双向链表的实现(赋源码)
数据结构·c++·链表·双向链表