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

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

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

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

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就是把二进制转为结构体,使用方法

MsgBody body;
// 对 body 的各个变量赋值
QByteArray packet;
body.write(&packet);

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

这种方法就是在使用QT写tcp的时候好用
注:
QT如果是string类型中包含有中文的话,需要转编码格式,上述的编码格式转换适用于windows

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

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的话更简单了

// 还是上面的结构体,在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,参考这个

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类型中有中文的也不用担心编码格式了

20240715
更新
在调试的时候发现问题
我使用的QTQDataStream来序列化和反序列化的(第一种方法),项目在调试的时候,发现使用QDataStream进行序列化了那么就要使用QDataStream进行反序列化。因为QT使用operate<<序列化QString类型的时候,生成的16进制会在前面包含该QString类型的大小。感觉就是QTQDataStream序列化定义了自己的风格。我使用QDataStream序列化,但是我反序列化不是用QDataStream,也能解出来,但是就需要一些操作。最后对于string类型的我就用纯C++来写吧。

使用QDataStreamoperate<<writeBytes两个序列化出来的格式还不一样

  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值