基于游戏消息数据的二进制序列化实现

9 篇文章 0 订阅
5 篇文章 0 订阅

在游戏开发过程中,经常会涉及到前后端数据的交互。以二进制数据流进行通信是一种常用的方式。然而由于游戏通信的数据复杂多样,结构也相对不一,往往会造成数据的序列化繁琐。现在介绍一种相对简单的方式。

首先描述一下内存结构:

|-------------------------------------------|----------------------------------------------.................

|<-----------------1--------------------->|<------------------------------2------------------>

编号1:标识数据流的长度信息等,也就是我们所说的数据包头(msg head),这里一般是四个字节或八个字节的固定长度。

编号2:真正的有交数据流二进制数据。


基于以上内存互形式,我们来看看相关序列化的代码:

#ifndef _BINARY_WRITE_STREAM_H_
#define _BINARY_WRITE_STREAM_H_

#include <iostream>
#include <vector>
#include <string>
using namespace std;

enum
{
	STREAM_LEN_HEAD_SIZE = 4,
	STREAM_DEF_LEN = 10240,
};

class BinaryWriteStream
{
public:
	BinaryWriteStream()
	{
		//!预留STREAM_LEN_HEAD_SIZE个长度表示此数据流的总长度
		unsigned int len = 0;
		m_data.append((char*)&len, STREAM_LEN_HEAD_SIZE);
	}
	~BinaryWriteStream(void){}
	template<typename T>
	BinaryWriteStream& operator <<(const T& val_);
	template<typename T>
	BinaryWriteStream& operator <<(const std::vector<T>& val_);

	template<>
	BinaryWriteStream& operator <<(const char& val_);
	template<>
	BinaryWriteStream& operator <<(const int& val_);
	template<>
	BinaryWriteStream& operator <<(const unsigned int& val_);
	template<>
	BinaryWriteStream& operator <<(const float& val_);
	template<>
	BinaryWriteStream& operator <<(const double& val_);
	template<>
	BinaryWriteStream& operator <<(const string& val_);

	const string& data()
	{
		return m_data;
	}
private:
	BinaryWriteStream& append(const char* data_, unsigned int size_);
	void flush();
private:
	std::string	m_data;
};

template<typename T>
BinaryWriteStream& BinaryWriteStream::operator <<(const T& val_)
{
	val_.pack(*this);
	return *this;
}

template<typename T>
BinaryWriteStream& BinaryWriteStream::operator <<(const std::vector<T>& val_)
{
	unsigned int size = val_.size();
	*this << size;
	typename std::vector<T>::const_iterator itEnd = val_.end();
	for(typename std::vector<T>::const_iterator it = val_.begin(); it != itEnd; ++it)
	{
		*this << *it;
	}
	return *this;
}

inline BinaryWriteStream& BinaryWriteStream::append(const char* data_, unsigned int size_)
{
	m_data.append(data_, size_);
	flush();
	return *this;
}

inline void BinaryWriteStream::flush()
{
	unsigned int size = m_data.size();
	m_data.replace(m_data.begin(), m_data.begin() + STREAM_LEN_HEAD_SIZE, (char*)&size, STREAM_LEN_HEAD_SIZE);
}

template<>
BinaryWriteStream& BinaryWriteStream::operator <<(const char& val_)
{
	return append((const char*)&val_, sizeof(char));
}

template<>
BinaryWriteStream& BinaryWriteStream::operator <<(const int& val_)
{
	return append((const char*)&val_, sizeof(int));
}

template<>
BinaryWriteStream& BinaryWriteStream::operator <<(const unsigned int& val_)
{
	return append((const char*)&val_, sizeof(unsigned int));
}

template<>
BinaryWriteStream& BinaryWriteStream::operator <<(const float& val_)
{
	return append((const char*)&val_, sizeof(float));
}

template<>
BinaryWriteStream& BinaryWriteStream::operator <<(const double& val_)
{
	return append((const char*)&val_, sizeof(double));
}

template<>
BinaryWriteStream& BinaryWriteStream::operator <<(const string& val_)
{
	unsigned int size = val_.size();
	append((const char*)&size, sizeof(unsigned int));
	return append(val_.c_str(), size);
}

#endif



由代码可见:

1》在构造函数中我预留了一个指定长度,也就是上面所述的编号1。

2》我定义了一个标准模版函数,里面调用了类型的pack函数,也就是说:所有自定义的类型要进行序列化,都必须有一个pack函数来定义自身的序列化方式。

那么对常用基本数据类型,这里我进行了一个特化,直接进行内存数据拷贝。

3》每次数据进行序列化后,数据流长度变化,我都会调用flush函数更新预留区的值。


接下来,我们一起来看自定义类型的定义形式,如上述,必须有一个pack的函数,这里我们举个例子如下:

 struct test
{
   int a;
   BinaryWriteStream& pack(BinaryWriteStream& stream) const
   {
	return stream << a;
   }
};
显然上面的类是可以进行序列化的。所以,为了方便起见,我定义了一些宏,部分代码如下:

#ifndef _STRUCT_TEMPLATE_H_
#define _STRUCT_TEMPLATE_H_

class BinaryWriteStream;
class BinaryReadStream;

#define DEFINE_STRUCT_1(STRUCT_NAME, T1, V1) \
struct STRUCT_NAME	\
{ \
	T1 V1; \
	BinaryWriteStream& pack(BinaryWriteStream& stream) const \
	{ \
		return stream << V1; \
	} \
	BinaryReadStream& unpack(BinaryReadStream& stream) \
	{ \
		return stream >> V1; \
	} \
};

#define DEFINE_STRUCT_2(STRUCT_NAME, T1, V1, T2, V2) \
struct STRUCT_NAME	\
{ \
	T1 V1; \
	T2 V2; \
	BinaryWriteStream& pack(BinaryWriteStream& stream) const \
	{ \
		return stream << V1 << V2; \
	} \
	BinaryReadStream& unpack(BinaryReadStream& stream) \
	{ \
		return stream >> V1 >> V2; \
	} \
};


#define DEFINE_MSG_1(MSG_NAME, MSGID_NAME, T1, V1) \
struct MSG_NAME	\
{ \
	int msgid; \
	T1 V1; \
	MSG_NAME(){msgid = MSGID_NAME;} \
	BinaryWriteStream& pack(BinaryWriteStream& stream) const \
	{ \
		return stream << msgid << V1; \
	} \
	BinaryReadStream& unpack(BinaryReadStream& stream) \
	{ \
		return stream >> msgid >> V1; \
	} \
};

#define DEFINE_MSG_2(MSG_NAME, MSGID_NAME, T1, V1, T2, V2) \
struct MSG_NAME	\
{ \
	int msgid; \
	T1 V1; \
	T2 V2; \
	MSG_NAME(){msgid = MSGID_NAME;} \
	BinaryWriteStream& pack(BinaryWriteStream& stream) const \
	{ \
		return stream << msgid << V1 << V2; \
	} \
	BinaryReadStream& unpack(BinaryReadStream& stream) \
	{ \
		return stream >> msgid >> V1 >> V2; \
	} \
};

#endif
 所以上面的示例test类可以直接表示成:DEFINE_STRUCT_1(test, int, a);


同样的思想,反序列化的代码如下:

#ifndef _BINARY_READ_STREAM_H_
#define _BINARY_READ_STREAM_H_

#include <iostream>
#include <vector>
#include <string>
using namespace std;

class BinaryReadStream
{
public:
	BinaryReadStream(const char* data_, unsigned int len_)
		m_data(data_),m_len(len_),m_cur(data_)
	{
		m_cur += STREAM_LEN_HEAD_SIZE;
	}
	~BinaryReadStream(void){}
	template<typename T>
	BinaryReadStream& operator >>(T& val_);
	template<typename T>
	BinaryReadStream& operator >>(std::vector<T>& val_);

	template<>
	BinaryReadStream& operator >>(char& val_);
	template<>
	BinaryReadStream& operator >>(int& val_);
	template<>
	BinaryReadStream& operator >>(unsigned int& val_);
	template<>
	BinaryReadStream& operator >>(float& val_);
	template<>
	BinaryReadStream& operator >>(double& val_);
	template<>
	BinaryReadStream& operator >>(string& val_);
private:
	unsigned int remainLen()
	{
		return m_len - (m_cur - m_data);
	}

	BinaryReadStream& readData(char* data_, unsigned int len_)
	{
		if(remainLen() < len_)
		{
			throw exception("size no enough");
		}
		::memcpy(data_, m_cur, len_);
		m_cur += len_;
		return *this;
	}
private:
	const char* const	m_data;
	const unsigned int	m_len;
	const char*			m_cur;
};

template<typename T>
inline BinaryReadStream& BinaryReadStream::operator >>(T& val_)
{
	val_.unpack(*this);
	return *this;
}

template<typename T>
inline BinaryReadStream& BinaryReadStream::operator >>(std::vector<T>& val_)
{
	unsigned int len = 0;
	readData((char*)&len, sizeof(unsigned int));
	for(unsigned int i = 0; i < len; ++i)
	{
		T temp;
		*this >> temp;
		val_.push_back(temp);
	}
	return *this;
}

template<>
inline BinaryReadStream& BinaryReadStream::operator >>(char& val_)
{
	return readData((char*)&val_, sizeof(char));
}

template<>
inline BinaryReadStream& BinaryReadStream::operator >>(int& val_)
{
	return readData((char*)&val_, sizeof(int));
}

template<>
inline BinaryReadStream& BinaryReadStream::operator >>(unsigned int& val_)
{
	return readData((char*)&val_, sizeof(unsigned int));
}

template<>
inline BinaryReadStream& BinaryReadStream::operator >>(float& val_)
{
	return readData((char*)&val_, sizeof(float));
}

template<>
inline BinaryReadStream& BinaryReadStream::operator >>(double& val_)
{
	return readData((char*)&val_, sizeof(double));
}

template<>
inline BinaryReadStream& BinaryReadStream::operator >>(string& val_)
{
	unsigned int len;
	readData((char*)&len, sizeof(unsigned int));
	return readData((char*)&val_, len);
}

#endif



测试代码如下:

// stream.cpp : 定义控制台应用程序的入口点。
//

#include "stdafx.h"

#include <iostream>
using namespace std;
#include <vector>

#include "BinaryWriteStream.h"
#include "BinaryReadStream.h"
#include "structTemplate.h"

DEFINE_STRUCT_2(test_struct, int, a, std::vector<int>, b);
DEFINE_MSG_2(test_msg2, 2, test_struct, st, int, a);
DEFINE_MSG_1(test_msg, 2, test_struct, st);
#ifdef WIN32_SYSTEM
int _tmain(int argc, _TCHAR* argv[])
#else
int main(int argc, char* argv[])
#endif
{
	int a = 0;
	int b = 3;
	int c = 4;
	std::vector<int> d;
	d.push_back(a);
	d.push_back(b);
	d.push_back(c);

	test_struct st;
	st.a = b;
	st.b = d;

	test_msg ms;
	ms.st = st;
	BinaryWriteStream stream;
	stream << ms;

	try
	{
		const string& data = stream.data();
		BinaryReadStream readStream(data.c_str(), data.size());
		test_msg ds;
		readStream >> ds;
	}
	catch (exception& e)
	{
		cout << "exception:" << e.what() << endl;
	}

	
	getchar();
	return 0;
}

以上只介绍了核心思想,不足和完善的地方日后有时间在整理。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值