C++游戏服务器框架笔记(一)_封装数据包类
C++游戏服务器框架笔记(二)_封装Socket类
C++游戏服务器框架笔记(三)_封装ByteBuffer类
......
【前要注明】:
游戏服务器与客户端的通信是通过socket网络接口按照既定的协议发送、接受数据包传递数据,那么怎样组织一个数据包是非常重要的环节,这里封装了一个简单的DataPack数据包类,较为方便打包和解析协议数据。
数据包按照以下格式组织:
包头 数据长度 协议数据
数据包的结构是比较简单的 包头比较简陋 也没有校验,后面会再完善优化,下面大概说下DataPack中存储数据的操作:
DataPack有四个核心指针:
m_starptr 指向数据缓冲区在内存中的起始地址
m_endptr 指向数据缓冲区在内存中的结束地址
m_dataptr 指向数据在缓冲区中的数据尾部地址,随数据的大小改变
m_offset 数据偏移指针,方便操作读取数据
DataPack封装构造数据的流程:
构造指定数据指定长的数据包,则动态分配指定长度的内存创建数据缓冲区
并按照上述说明,初始化类核心指针成员。构造完成后的结构如下图所示:
DataPack数据缓冲区的动态扩充:
写入数据的时候如果初始构造的数据包缓冲区的大小不足,则需要动态扩大缓冲区的内存空间,通常的做法是增大一倍空间,不过我这里是只增大写入数据的大小,动态扩大缓冲区内存使用C库函数realloc():
函数声明:void *realloc(void *ptr, size_t size)
函数描述:先判断当前的指针是否有足够的连续空间,如果有,扩大mem_address指向的地址,并且将 mem_address返回,如果空间不够,先按照newsize指定的大小分配空间,将原有数据从头到尾拷贝到新分配的内存区域, 而后 释放原来mem_address所指内存区域(注意:原来指针是自动释放,不需要使用free),同时返回新分配的内存区域 的首地址。即重新分配存储器块的地址。
数返回值:如果重新分配成功则返回指向被分配内存的指针,否则返回空指针NULL。
重新分配缓冲据的内存空间后,需要重新初始化四个核心指针,完成DataPack内存缓冲区的动态扩充工作
下面是DataPack的头文件:
#ifndef _DATAPACK_H_
#define _DATAPACK_H_
#include
#include
#include
#include
//包头长度
#define HEAD_PACK_LEN 4
class DataPack
{
public:
DataPack();
DataPack(void* buffer, int len);
~DataPack();
//反序列化字符串为DataPack
void FromBuffer(void* buffer, int len);
//清空缓冲区
void CleanBufer(void);
//设置数据缓冲区的大小
void SetSize(int size);
//获取数据缓冲区中的数据首地址
const char* GetBuffer(void);
//获取数据缓冲区大小
int GetBufferLength(void);
//设置指针偏移,读取出一个字段数据后需要设置,更新读取位置
void SetOffset(int pos);
//写入unsigned char类型数据,并移动m_offset
void WriteByte(unsigned char c);
//写入short类型数据,并移动m_offset
void WriteShort(short s);
//写入int类型数据,并移动m_offset
void WriteInt(int i);
//写入long类型数据,并移动m_offset
void WriteLong(long l);
//写入float类型数据,并移动m_offset
void WriteFloat(float f);
//写入double类型数据,并移动m_offset
void WriteDouble(double d);
//写入其他DataPack读出的数据到到数据缓冲区
void WriteBuffer(void* buff, int size);
//写入字符串数据,len为长度,默认-1接口内部计算长度
void WriteString(const char* str, int len = -1);
//写入包的长度,即用新的包的长度重新填充包头后面的datalen内存空间
void WritePackLen(int len);
//读出Short类型数据,并移动m_offset
short ReadShort(void);
//读出Int类型数据,并移动m_offset
int ReadInt(void);
//读出unsigned char类型数据,并移动m_offset
unsigned char ReadByte(void);
//读出Float类型数据,并移动m_offset
float ReadFloat(void);
//读出Double类型数据,并移动m_offset
double ReadDouble(void);
//读出Long类型数据,并移动m_offset
long ReadLong(void);
//读出数据缓冲区数据到buffer指针(memcopy), 并移动m_offset
int ReadBuffer(void* buffer);
//读出数据缓冲区数据到buffer指针(memcopy),并加上字符串结束符'\0', 并移动m_offset
int ReadString(char* pstr);
//读出数据缓冲区中字符串,返回字符指针,使用完需要手动释放
char* ReadString(int& out_size);
//获取包数据的长度datalen
int GetPackLen(void);
//获取缓冲区大小
int GetBufferSize(void);
//释放数据缓冲区内存空间
void FreeDack(void);
private:
//检查当前数据缓冲区的空间是否可以添加size大小数据,如果不足则动态扩充(调用SetSize)
void CheckCapacity(int size);
private:
//数据缓冲区起始地址
char* m_starptr;
//数据缓冲区结束地址
char* m_endptr;
//数据缓冲区中数据的结束地址
char* m_dataptr;
//读取数据的偏移指针
char* m_offset;
};
#endif
下面是DataPack的cpp文件:
#include "DataPack.h"
/* 协议头,每个数据包中前两个字节是数据头,
后四个字节是数据长度(包含协议头长度和数据长度)*/
static int DATA_BLOCK = 128;
DataPack::DataPack() {
int size = HEAD_PACK_LEN + DATA_BLOCK;
m_starptr = (char*)malloc(size);
m_endptr = m_starptr + size;
memset(m_starptr, 0, size);
m_dataptr = m_starptr+ HEAD_PACK_LEN;
m_offset = m_starptr + HEAD_PACK_LEN;
}
DataPack::DataPack(void* buffer, int len)
{
m_starptr = (char*)malloc(len);
m_endptr = m_starptr + len;
memcpy(m_starptr, buffer, len);
m_dataptr = m_starptr + HEAD_PACK_LEN + len;
m_offset = m_starptr + HEAD_PACK_LEN;
}
DataPack::~DataPack()
{
self:FreeDack();
}
void DataPack::FreeDack(void)
{
free(m_starptr);
m_endptr = NULL;
m_dataptr = NULL;
m_offset = NULL;
}
void DataPack::FromBuffer(void* buffer, int len)
{
if (this->GetBufferSize() < len)
this->SetSize(len);
memset(m_starptr, 0, this->GetBufferSize());
memcpy(m_starptr, buffer, len);
m_dataptr = m_starptr + len;
m_offset = m_starptr + HEAD_PACK_LEN;
}
void DataPack::CleanBufer(void) {
m_dataptr = m_starptr;
memset(m_starptr, 0, this->GetBufferSize());
m_offset = m_starptr + HEAD_PACK_LEN;
}
void DataPack::SetSize(int size)
{
//不能缩小包内存
if (size <= m_endptr - m_dataptr)
return;
//保存现在的各个地址位置偏移大小
int datalen = m_dataptr - m_starptr;
int offset = m_offset - m_starptr;
//动态扩充数据缓冲区内存并还原各个地址相对m_starptr偏移位置
m_starptr = (char*)realloc(m_starptr, size);
m_endptr = m_starptr + size;
m_dataptr = m_starptr + datalen;
m_offset = m_starptr + offset;
}
const char* DataPack::GetBuffer(void)
{
return m_starptr;
}
int DataPack::GetBufferLength(void)
{
return m_dataptr - m_starptr;
}
void DataPack::SetOffset(int pos)
{
m_offset = m_starptr + pos;
}
void DataPack::WriteByte(unsigned char c)
{
int size = sizeof(unsigned char);
CheckCapacity(size);
memcpy(m_dataptr, &c, size);
m_dataptr += size;
WritePackLen(size);
}
void DataPack::WriteShort(short s)
{
int size = sizeof(short);
CheckCapacity(size);
memcpy(m_dataptr, &s, size);
m_dataptr += size;
WritePackLen(size);
}
void DataPack::WriteInt(int i)
{
int size = sizeof(int);
CheckCapacity(size);
memcpy(m_dataptr, &i, size);
m_dataptr += size;
WritePackLen(size);
}
void DataPack::WriteLong(long l)
{
int size = sizeof(long);
CheckCapacity(size);
memcpy(m_dataptr, &l, size);
m_dataptr += size;
WritePackLen(size);
}
void DataPack::WriteFloat(float f)
{
int size = sizeof(float);
CheckCapacity(size);
memcpy(m_dataptr, &f, size);
m_dataptr += size;
WritePackLen(size);
}
void DataPack::WriteDouble(double d)
{
int size = sizeof(double);
CheckCapacity(size);
memcpy(m_dataptr, &d, size);
m_dataptr += size;
WritePackLen(size);
}
void DataPack::WriteBuffer(void* buff, int size)
{
CheckCapacity(size);
WriteInt(size);
memcpy(m_dataptr, buff, size);
m_dataptr = m_dataptr + size;
WritePackLen(size);
}
void DataPack::WriteString(const char* str, int len)
{
if (len == -1)
len = strlen(str);
WriteInt(len);
CheckCapacity(len);
memcpy(m_dataptr, str, len);
m_dataptr = m_dataptr + len;
WritePackLen(len);
}
void DataPack::WritePackLen(int len)
{
int oldlen = *((int*)(m_starptr)) + len;
memcpy(m_starptr, &oldlen, sizeof(int));
}
void DataPack::CheckCapacity(int size)
{
if (m_endptr - m_dataptr > size) {
return;
}
int add_size = DATA_BLOCK;
while (m_endptr - m_dataptr + add_size < size) {
add_size += DATA_BLOCK;
}
SetSize(GetPackLen() + add_size);
}
short DataPack::ReadShort(void)
{
short data = *((short*)m_offset);
m_offset += sizeof(short);
return data;
}
int DataPack::ReadInt(void)
{
int data = *((int*)m_offset);
m_offset += sizeof(int);
return data;
}
unsigned char DataPack::ReadByte(void)
{
unsigned char data = *((unsigned char*)m_offset);
m_offset += 1;
return data;
}
float DataPack::ReadFloat(void)
{
float data = *((float*)m_offset);
m_offset += sizeof(float);
return data;
}
double DataPack::ReadDouble(void)
{
double data = *((double*)m_offset);
m_offset += sizeof(double);
return data;
}
long DataPack::ReadLong(void)
{
long data = *((long*)m_offset);
m_offset += sizeof(long);
return data;
}
int DataPack::ReadBuffer(void* buffer)
{
int size = ReadInt();
memcpy(buffer, m_offset, size);
m_offset += size;
return size;
}
int DataPack::ReadString(char* pstr)
{
int size = ReadInt();
memcpy(pstr, m_offset, size);
*(pstr + size + 1) = '\0';
m_offset += size;
return size;
}
char* DataPack::ReadString(int& out_size)
{
int size = ReadInt();
char* pstr = (char*)malloc(sizeof(char) * size + 1);
memcpy(pstr, m_offset, size);
*(pstr + size + 1) = '\0';
m_offset += size;
return pstr;
}
int DataPack::GetPackLen(void)
{
return *((int*)m_starptr);
}
int DataPack::GetBufferSize(void)
{
return m_endptr - m_starptr;
}
将协议数据打包成数据包后,调用GetBuffer()和GetBufferLength() 拿到数据和数据长度,然后通过socket接口发送
下面是发送端封装数据包例子(代码片段):
void test_data_pack()
{
//封包
DataPack pack;
pack.SetSize(256);
pack.WriteInt(1997);
pack.WriteInt(1015);
pack.WriteDouble(99.99);
pack.WriteLong(9999);
struct Test {
int num;
float weight;
char data[36];
};
Test t = { 1, 78.8, "So live a life you will remember" };
pack.WriteBuffer(&t, sizeof(t));
pack.WriteString("就活出你的人生 这回忆值得你铭记");
//反序列化,读取数据
DataPack * pack1 = new DataPack();
pack1->FromBuffer((void*)pack.GetBuffer(), pack.GetBufferLength());
cout << pack1->GetPackLen() << endl;
cout << pack1->ReadInt() << endl;
cout << pack1->ReadInt() << endl;
cout << pack1->ReadDouble() << endl;
cout << pack1->ReadLong() << endl;
Test t1;
memset(&t1, 0, sizeof(t1));
cout << pack1->ReadBuffer(&t1) << endl;
cout << t1.num << endl;
cout << t1.weight << endl;
cout << t1.data << endl;
int out_size;
char* pstr = pack1->ReadString(out_size);
cout << pstr << endl;
free(pstr);
}
下面是接收端解析数据包打印数据结果: