服务端数据(自定义结构体),实现本地读取字节流

一、memcpy基本用法

通过malloc、new去创建空间,读取网络字节流数据。

const int MAX_SZIE = 10000;
// 两种创建方式
unsigned char* pBuffer1 = (unsigned char*)malloc(MAX_SIZE);  // free释放并置空
unsigned char* pBuffer2 = new unsigned char[MAX_SIZE];       // delete[] pBuffer2

unsigned char* pSrc = ReadNetWork1();

// 1000的位置开始存放2000个字节的数据。pBuffer的空间必须足够,pSrc必须是2000个大小。
memcpy(pBuffer1 + 1500, pSrc, 2000);

// 500的位置开始存放1000个字节的数据。pBuffer的空间必须足够,pSrc必须是1000个大小。
memcpy(pBuffer1 + 500, pSrc, 1000);

if (pBuffer1)
{
	free(pBuffer1);
	pBuffer1 = nullptr;
}

if (pBuffer2)
{
	delete[] pBuffer2;
	pBuffer2 = nullptr;
}


二、传输结构体解析

  • 假设网络数据的内容为自定义结构体,将其序列化为字节流,通过字节流传输(结构体可以更清晰的组织数据)。
  • 客户端在读取服务端字节流时,需要将其反序列化,将字节流还原为结构体对象。需要特别注意结构体成员内存对齐、整体内存对齐。反序列化有两种方式,一种是定义struct结构体,另一种是不在程序中定义struct结构体,通过配置文件定义成员变量和类型。

1、定义struct结构体

通过定义和服务端一样的struct结构体类型。优点是这样可以很方便的解析数据,不用考虑内存对齐的影响。缺点是如果结构体数据经常改变,那么就需要频繁修改程序。

(1)定义结构数据

实际情况在本地定义结构体时,需要注意子类可能会继承类,并包含虚函数的情况。

class IData
{
	virtual void TransData() = 0;
}

struct command
{
	int a;
	double b;
}

struct status
{
	float c;
	bool d;
}

class Data1 : public IData
{
	command speed;
	status pos;
}

class Data2 : public IData
{
	// ...自己的结构体
}

std::vector<IData*> m_vData;
std::vector<int> m_vStructSize;
(2)根据定义解析网络数据

该示例,将服务端网络数据赋值到Data1和Data2的结构体中。网络数据大小 = (sizeof(Data1) - sizeof(void*)) + (sizeof(Data2) - sizeof(void*))。需要减去头部虚函数指针所占的大小。

// 初始化
IData *p1 = new Data1();
IData *p2 = new Data2();
m_vData.push_back(p1);
m_vData.push_back(p2);
m_vStructSize.push_back(sizeof(Data1));
m_vStructSize.push_back(sizeof(Data2));


unsigned char* pNetData;
int nPos = 0;
int nStructSize = 0;
int nPtr = sizeof(void*);  // 本机64位,指针占8个字节

for (int i = 0; i < m_vData.size(); i++)
{
	// 获取类实际的结构体大小
	nStructSize = m_vStructSize[i];  
	
	// 为什么指针+1?
	// 由于本地定义的每个数据类继承了纯虚函数类,所以该数据类地址前八个字节(本机64位)存放虚函数表指针
	// 后面地址才是存放的真正变量,所以为了给数据类赋值,按理是加8个字节,但是不能直接+8
	// 因为在C++中的概念中,指针++,是指针所指向的类的类型大小++
	// m_vData[i]里面存放的是IData*,而IData是纯虚函数类,正好占一个指针的大小,就是8个字节。
	// 那么该指针+1,正好是8个字节。
	memcpy(m_vData[i] + 1, pNetData + nPos, nStructSize - nPtr);
	
	// nStructSize - nPtr = nStructSize - 8
	nPos += (nStructSize - nPtr);
}
(3)本地处理数据后,发送数据到网络
// ...
// 创建实际空间
unsigned char* pBuffer = new unsigned char[所有数据类的大小 - 每个数据类的虚函数表所占的大小]
for (int i = 0; i < m_vData.size(); i++)
{
	nStructSize = m_vStructSize[i];  
	memcpy(pBuffer + nPos, m_vData[i]+ 1, nStructSize - nPtr);
	nPos += (nStructSize - nPtr);
}


2、不定义struct结构体

通过配置文件,按一定的顺序,定义成员变量以及类型,然后客户端通过解析该配置文件,单独对内存对齐偏移量进行计算,自主对齐,从而解析服务端数据。适合结构体成员众多,频繁修改,只需要修改配置文件,不需要修改程序。

需要注意服务器数据的默认内存对齐数和客户端是否一致,以及字节序是否一致,是大端还是小端。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值