C++ tcp中的可变长度结构体的序列化和反序列化

近日,在项目里,需要对tcp传输的数据进行序列化和反序列化,有很多方法,记录下来
写在前面:使用tcp传输的时候需要注意字节对齐的问题,在以下代码中统一使用单字节对齐

cpp 复制代码
//单字节对齐   写在结构体定义之上
#pragma pack(1)

第一种 QT

如果是用QT写的,结构体拿到值以后,可以使用QDataStream来进行序列化和反序列化,可以参考这个链接

cpp 复制代码
struct MsgBody
{
	int iValue;     // 下面两个vector的size
	double dValue;
	string strValue;
	vector<int> vStrSize;			
	vector<string> vStr;			

	//写数据到类数据成员中
	void write(QByteArray *data)
	{
		QDataStream streamWriter(data, QIODevice::WriteOnly);
		streamWriter.setVersion(QDataStream::Qt_5_14);

		streamWriter << iValue;
		streamWriter << dValue;
        // string 需要转 QString 才能使用 << ,同时如果 string 中有中文,还需要注意当前的编码格式转换
        // 使用 QDataStream 序列化和反序列化不能重载 string
		QString qstr = QString::fromLocal8Bit(strValue.c_str());
		streamWriter << qstr.toLocal8Bit();

		for (int i = 0; i < iValue; i++)
		{
			int iTemp = vStrSize[i];
			streamWriter << iTemp;
		}
		for (int j = 0; j < iValue; j++)
		{
			string strValue = vStr[j];
			QString qstrValue = QString::fromLocal8Bit(strValue.c_str());
			streamWriter << qstrValue.toLocal8Bit();
		}


	}

	//读数据到类数据成员中
	void read(QByteArray &data)
	{
		QDataStream streamReader(&data, QIODevice::ReadOnly);
		streamReader.setVersion(QDataStream::Qt_4_3);

		streamReader >> iValue;
		streamReader >> dValue;

		QByteArray byValue;
		streamReader >> byValue;
		QString qstr = QString::fromLocal8Bit(byValue);
		strValue = string((const char *)qstr.toLocal8Bit());

		for (int i = 0; i < iValue; i++)
		{
			int iTemp;
			streamReader >> iTemp;
			vStrSize.push_back(iTemp);
		}
		for (int j = 0; j < iValue; j++)
		{
			QByteArray qstrValue;
			streamReader >> qstrValue;
			QString str = QString::fromLocal8Bit(qstrValue);
			string strValue = string((const char *)str.toLocal8Bit());
			vStr.push_back(strValue);
		}

	}
};

在代码中write就是把结构体转为二进制,read就是把二进制转为结构体,使用方法

cpp 复制代码
MsgBody body;
// 对 body 的各个变量赋值
QByteArray packet;
body.write(&packet);

body.read(packet);  // packet为二进制

这种方法就是在使用QT写tcp的时候好用
注:

QT如果是string类型中包含有中文的话,需要转编码格式,上述的编码格式转换适用于windows

第二种 强转

强转的方法适用于结构体中没有 可变长度的变量,可变长度就是结构体中包含string, vector类型

cpp 复制代码
struct NET_HEAD
{
public:
	int recvId;   // 接收消息方	
	int sendId;  // 发送消息方	
	int msgType;     // 消息类型
	int dataLen;     // 数据长度  
};

int main()
{
	char *m_Msg_buf = new char[40960];;//消息缓冲区
	char *m_Recv_buf= new char[4096];;//消息缓冲区
	
	NET_HEAD head;
	
	// 结构体转为二进制
	memcpy((void *)m_Msg_buf, (void *)&head, sizeof(NET_HEAD ));
	
	// s_server 是tcp连接
	int recv_len = recv(s_server, m_Recv_buf, 4096, 0);
	memcpy(m_Msg_buf, m_Recv_buf, recv_len);
	// 二进制强转为结构体	
	NET_HEAD* header = (NET_HEAD*)m_Msg_buf;
}

如果使用QT的话更简单了

cpp 复制代码
// 还是上面的结构体,在main函数中写以下代码
NET_HEAD head;
// 为 head 赋值
QByteArray packet;
packet.append((char*)&head, sizeof(NET_HEAD));  // 将 结构体强转为二进制

// 二进制转为结构体
// packet 是接受到的二进制
NET_HEAD * head1 = (NET_HEAD *)packet.data();  

第三种 纯c++

用纯C++来进行序列化和反序列化可变结构体比较有意思,其实也就是memcpy,参考这个

cpp 复制代码
struct ComplexData {
	int id;
	int len;
	string name;
	int iStrVecLen;
	vector<int> viStr;
	vector<string> vStr;
	int valuesLen;
	vector<double> values;
	char* serialize() const {
		int offset = 0;
		char* buffer = new char[sizeof(ComplexData)];
		memcpy(buffer + offset, &this->id, sizeof(int));
		offset += sizeof(int);

		memcpy(buffer + offset, &this->len, sizeof(int));
		offset += sizeof(int);

		memcpy(buffer + offset, (this->name).data(), this->len);
		offset += this->len;
		offset += 1;   // 加1 是为了防止string类型后面有 \0 

		memcpy(buffer + offset, &this->iStrVecLen, sizeof(int));
		offset += sizeof(int);

		for (int i = 0; i < this->iStrVecLen; i++)
		{
			int iValue = this->viStr[i];
			memcpy(buffer + offset, &iValue, sizeof(int));
			offset += sizeof(int);
		}

		for (int i = 0; i < this->iStrVecLen; i++)
		{
			string str = this->vStr[i];
			int sizeStr = this->viStr[i];
			memcpy(buffer + offset, str.data(), sizeStr);
			offset += sizeStr;
			offset += 1;
		}

		memcpy(buffer + offset, &this->valuesLen, sizeof(int));
		offset += sizeof(int);

		for (int i = 0; i < this->valuesLen; i++)
		{
			memcpy(buffer + offset, &(this->values[i]), sizeof(double));
			offset += sizeof(double);
		}

		return buffer;
	}
	// 反序列化函数
	void deserialize(char* buffer) {
		int offset = 0;
		memcpy(&this->id, buffer + offset, sizeof(int));
		offset += sizeof(int);

		memcpy(&this->len, buffer + offset, sizeof(int));
		offset += sizeof(int);

		this->name = std::string(buffer + offset, this->len);
		offset += this->len;
		offset += 1;

		memcpy(&this->iStrVecLen, buffer + offset, sizeof(int));
		offset += sizeof(int);

		for (int i = 0; i < this->iStrVecLen; i++)
		{
			int iValue;
			memcpy(&iValue, buffer + offset, sizeof(int));
			offset += sizeof(int);
			this->viStr.push_back(iValue);
		}

		for (int i = 0; i < this->iStrVecLen; i++)
		{
			int iValue = this->viStr[i];
			string str = std::string(buffer + offset, iValue);
			offset += iValue;
			offset += 1;
			this->vStr.push_back(str);
		}


		memcpy(&this->valuesLen, buffer + offset, sizeof(int));
		offset += sizeof(int);

		for (int i = 0; i < this->valuesLen; i++)
		{
			double dValue;
			memcpy(&dValue, buffer + offset, sizeof(double));
			offset += sizeof(double);
			this->values.push_back(dValue);
		}

	}
};

int main()
{
	string name = "Join Doe";
	vector<double> vec;
	vec.push_back(1.1);
	vec.push_back(2.2);
	vec.push_back(3.3);

	string str1 = "teards sfdf";
	string str2 = "sdsHJJMHj";
	string str3 = "测试代码";
	string str4 = "123qweer长度";

	vector<string> vecStr;
	vecStr.push_back(str1);
	vecStr.push_back(str2);
	vecStr.push_back(str3);
	vecStr.push_back(str4);

	vector<int> vecIStr;
	vecIStr.push_back(str1.size());
	vecIStr.push_back(str2.size());
	vecIStr.push_back(str3.size());
	vecIStr.push_back(str4.size());

	ComplexData oriData;
	oriData.id = 1;
	oriData.len = name.size();
	oriData.name = name;
	oriData.iStrVecLen = vecStr.size();
	oriData.viStr = vecIStr;
	oriData.vStr = vecStr;
	oriData.valuesLen = vec.size();
	oriData.values = vec;
	
	// 序列化并发送
	char* buffer = oriData.serialize();
	
	// 接收并反序列化
	ComplexData receivedObj;
	receivedObj.deserialize(buffer);

	cout << "recvData.id: " << receivedObj.id << endl;
	cout << " recvData.name: " << receivedObj.name << endl;
	for (int i = 0; i < receivedObj.values.size(); i++)
	{
		cout << receivedObj.values[i] << endl;
	}

	for (int i = 0; i < receivedObj.vStr.size(); i++)
	{
		cout << receivedObj.vStr[i] << endl;
	}

	return 0;
}

纯C++序列化和反序列化对于string类型中有中文的也不用担心编码格式了

相关推荐
Ajiang28247353042 小时前
对于C++中stack和queue的认识以及priority_queue的模拟实现
开发语言·c++
梓仁沐白4 小时前
ubuntu+windows双系统切换后蓝牙设备无法连接
windows·ubuntu
速盾cdn4 小时前
速盾:CDN是否支持屏蔽IP?
网络·网络协议·tcp/ip
yaoxin5211234 小时前
第二十七章 TCP 客户端 服务器通信 - 连接管理
服务器·网络·tcp/ip
内核程序员kevin4 小时前
TCP Listen 队列详解与优化指南
linux·网络·tcp/ip
‘’林花谢了春红‘’6 小时前
C++ list (链表)容器
c++·链表·list
九鼎科技-Leo8 小时前
什么是 WPF 中的依赖属性?有什么作用?
windows·c#·.net·wpf
机器视觉知识推荐、就业指导8 小时前
C++设计模式:建造者模式(Builder) 房屋建造案例
c++
Yang.9910 小时前
基于Windows系统用C++做一个点名工具
c++·windows·sql·visual studio code·sqlite3