介于要设计游戏服务器和客户端逻辑,现在需要普及一下我在代码中用到的一些数据结构,这一篇讲一下字节流--- ByteStream。
字节流就是自己实现的一个数据存储区,可对基础数据和其他特定数据进行序列化和反序列化。
用与玩家数据或者其他服务器数据的落地保存。
也可用于客户端和服务器,、服务器和服务器之间的通信。
里面也添加了对字节顺序的处理逻辑。
如果是通信的数据包可以启动字节顺序转换。
需要注意的一点是我对输入的每一个元素只拷贝元素内容,没有存储其类型,所以在字节流输出的时候没有办法做类型校验,这样对于结构经常变化的数据集合不是很友好,所以如果在通信层面用的话,对于不是经常变化结构的数据集合使用这种方法好于protobuf, 对于经常变化的数据集合可以使用protobuf, 但是我的服务器实现里面是模块化封装,底层的数据结构不会发生改变,所有会用ByteStream, 而 protobuf 会给上层业务层面使用。
贴一下代码:
1:字节顺序代码:
// 字节顺序转换
#ifndef EndianConverter_h__
#define EndianConverter_h__
#include
namespace SCore
{
// 未知
#define ENDAIN_UNKNOW 0
// 大端 (网络字节顺序)
#define ENDIAN_BIG 1
// 小端 (本地字节顺序)
#define ENDIAN_LITTLE 2
// 每一个包含头文件的cpp 都会生成 endian_struct ,目前只有一个文件包含
static union endian_struct
{
char buf[sizeof(int)];
int i;
} g_endian_t = { {(char)ENDIAN_LITTLE , (char)3, (char)5, (char)ENDIAN_BIG } };
// 本机字节顺序
#define LOCAL_ENDIAN (g_endian_t.i & 0xff)
// 字节顺序反转
template void EndianReverse(char* value)
{
if (value == (char*)0 || N <= 1)
return;
for (int i = 0; i < N / 2; ++i)
{
std::swap(value[i], value[N - i - 1]);
}
}
// 字节顺序转换
template void EndianConverter(T* value)
{
EndianReverse((char*)value);
}
// 网络字节顺序转换成本机字节顺序
template void NetEndianToLocal(T* value)
{
if (ENDIAN_BIG == LOCAL_ENDIAN)
return;
EndianReverse((char*)value);
}
// 本机字节顺序转换成网络字节顺序
template void LocalEndianToNet(T* value)
{
if (ENDIAN_BIG == LOCAL_ENDIAN)
return;
EndianReverse((char*)value);
}
// 顺序转换
template
void ENDIAN_SWAP(char* ptr)
{
for (int i = 0; i < N / 2; ++i)
{
std::swap(ptr[i], ptr[N - i - 1]);
}
}
}
2:字节流代码:
// 字节流
#ifndef ByteStream_h__
#define ByteStream_h__
#include
#include
#include
#ifndef _WIN32
#include
#endif
#include "Net/EndianConverter.h"
namespace SCore
{
#define INIT_BYTE_SIZE 128
class ByteStream
{
typedef std::vector ByteData;
typedef char* ByteStreamChar;
typedef wchar_t* ByteStreamWChar;
public:
ByteStream(size_t initSize = INIT_BYTE_SIZE, bool bEndian = false)
{
m_data.reserve(initSize);
m_nMaxSize = initSize;
m_nUseSize = 0;
m_nReadOffset = 0;
m_bEndian = bEndian;
*this << m_bEndian;
}
ByteStream(const ByteStream& cpy) :
m_nMaxSize(cpy.m_nMaxSize),
m_nReadOffset(cpy.m_nReadOffset),
m_nUseSize(cpy.m_nUseSize)
{
m_data.reserve(m_nMaxSize);
memcpy(m_data.data(), cpy.m_data.data(), m_nUseSize);
}
ByteStream(const char* cs, size_t len)
{
m_nMaxSize = 0;
m_nUseSize = 0;
m_nReadOffset = 0;
LoadData(cs, len);
}
// 获取流数据
const char* GetData()
{
return m_data.data();
}
// 获取流数据大小
size_t GetDataLen()
{
return m_nUseSize;
}
// 加载数据
void LoadData(const char* ptr, size_t len)
{
m_nUseSize = 0;
Append(ptr, len);
ResetReadOffset();
}
// 重置读偏移
void ResetReadOffset()
{
m_nReadOffset = 0;
*this >> m_bEndian;
}
// 清理
void Clear()
{
m_nUseSize = 0;
}
public:
// 输入
template
ByteStream& operator<<(const T& t)
{
if (m_bEndian)
{
T tmp = t;
LocalEndianToNet(&tmp);
Append((const char*)&tmp, sizeof(t));
return *this;
}
Append((const char*)&t, sizeof(t));
return *this;
}
// 特例重载
ByteStream& operator<<(const char* t)
{
*this << std::string(t);
return *this;
}
ByteStream& operator<<(const wchar_t* t)
{
*this << std::wstring(t);
return *this;
}
// 特例化模板
template<>
ByteStream& operator<<(const ByteStreamChar& t)
{
*this << std::string(t);
return *this;
}
template<>
ByteStream& operator<<(const ByteStreamWChar& t)
{
*this << std::wstring(t);
return *this;
}
template<>
ByteStream& operator<<(const std::string& t)
{
Append(t.c_str(), t.length() + 1);
return *this;
}
template<>
ByteStream& operator<<(const std::wstring& t)
{
Append((const char*)t.c_str(), (t.length() + 1) * sizeof(wchar_t));
return *this;
}
// 输出
template
ByteStream& operator>>(T& t)
{
if (m_nUseSize - m_nReadOffset < sizeof(t))
{
assert(0 && "read byte stream faild...");// ERROR
return *this;
}
memcpy(&t, m_data.data() + m_nReadOffset, sizeof(t));
m_nReadOffset += sizeof(t);
if (m_bEndian)
{
NetEndianToLocal(&t);
}
return *this;
}
// 特例化模板
template<>
ByteStream& operator>>(std::string& t)
{
size_t len = strlen(m_data.data() + m_nReadOffset);
if (len >= m_nUseSize - m_nReadOffset)
{
assert(0 && "read byte stream faild...");// ERROR
return *this;
}
t = (m_data.data() + m_nReadOffset);
m_nReadOffset += (len + 1);
return *this;
}
template<>
ByteStream& operator>>(std::wstring& t)
{
size_t len = (strlen(m_data.data() + m_nReadOffset) + 1) * sizeof(wchar_t);
if (len >= m_nUseSize - m_nReadOffset)
{
assert(0 && "read byte stream faild...");// ERROR
return *this;
}
t = (wchar_t*)(m_data.data() + m_nReadOffset);
m_nReadOffset += len;
return *this;
}
private:
// 追加写
void Append(const char* ptr, size_t len)
{
assert(ptr && len > 0);
if (!ptr || len == 0)
{
return;
}
if (m_nMaxSize - m_nUseSize < len)
{
// 扩充
size_t newSize = m_nMaxSize + (m_nMaxSize >> 1);
if (newSize < len + m_nUseSize)
{
newSize = len + m_nUseSize;
}
m_data.reserve(newSize);
m_nMaxSize = newSize;
}
memcpy(m_data.data() + m_nUseSize, ptr, len);
m_nUseSize += len;
}
private:
ByteData m_data;
size_t m_nMaxSize;
size_t m_nUseSize;
size_t m_nReadOffset; // 读取偏移
bool m_bEndian;
};
}
#endif // ByteStream_h__
// 简单使用示例:
// 通信包使用ByteStream:
void SendMsg()
{
// 定义零时变量,初始化buffer大小128, 开启字节顺序转换
ByteStream bs(128, true);
// 要发送的数据
time_t now = g_ServerCore.GetNow();
int id = 1;
std::string name("xxxxxxx");
long long data = 2;
// 输入数据
bs << now;
bs << id << name << data;
// 发包
// void send(int msg_type, const char* msg, size_t msg_len);
#define msg_type_test 2 // 定义消息类型,这里都是演示
pSession->Send(msg_type_test , bs.GetData(), bs.GetDataLen());
}
// 收包:
void OnRecvMsg(MsgPacket* msg)
{
assert(msg);
// msg->msg : 上面发送的bs.GetData()
// msg->len : 上面发送的bs.GetDataLen()
ByteStream bs(msg->msg, msg->len);
// 数据输出
time_t now;
int id = 1;
std::string name;
long long data;
bs >> now >> id >> name >> data;
// TODO
}
// 用于数据存储是一样的使用方式,只不过不需要字节顺序转换
ByteStream bs; // 这样定义就可以,
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)