一、介绍
用于服务器和客户端应用层之间的协议,通过定义的协议,可以将接收的数据转发给相应的模块,相应模块可以将数据按照约定的结构进行解析。
二、协议体
2.1 消息码定义
消息码分为:分类消息码,主消息码,子消息码
2.2 消息码作用
消息码用于消息转发,将消息一层一层传给对应模块。消息码作用介绍:
- 分类消息码,用于网关服转发消息,网关服通过分类消息码,将消息发送给对应的服务器
- 主消息码,将消息转发给对应模块
- 子消息码,一个模块中定义的消息码,用于处理一个具体某个逻辑
在这三种模块中,我们还可以根据实际业务需要添加新的消息码类型。例如,房间插件中的牌桌消息,牌桌消息是与牌桌游戏相关的一类消息,它可以根据不同游戏定义各自的消息类型,为了方便扩展,我们可以将这类消息共用同一个【子消息码】,然后在【子消息码】后面再加一个【游戏消息码】,这样不同游戏可以各自定义自己的游戏消息,游戏消息码为:分类消息码(游戏服),主消息码(房间插件),子消息码(房间数据),某个游戏消息码(具体游戏消息码),数据。
说明:广场服和游戏服,在收到网关服消息,除了登录消息外所有消息都是发送到对应插件中,插件自己处理各种的消息。每个插件都会定义一个自己的消息码,网关服代理通过主消息码将消息发送给有关的插件。
2.3 消息结构
以游戏中浏览邮件为例
1.分类消息和主消息定义
//服务器分类消息
enum CMD_ROOT
{
CMDROOT_GATEWAY_MSG = 0x0, //代理前端消息
CMDROOT_LOGIN_MSG = 0x1, //登录服务器消息
CMDROOT_PLAZA_MSG = 0x2, //广场消息
CMDROOT_GAMESERVER_MSG = 0x3, //游戏服务器消息
CMDROOT_WEB_MSG = 0x4, //WEB服务器消息
CMDROOT_MAX
};
//游戏主消息码定义
enum GS_PLAZA_MSGID
{
// 前面的消息码省略
GS_PLAZA_MSGID_EMAIL = 11, // 邮件 c->s->c
// 后面的消息码省略
GS_PLAZA_MSGID_MAX
};
2. 消息头定义
typedef unsigned char uchar;
template<uchar rootid>
struct GS_Head
{
uchar RootID; //分类消息码
uchar MainID; //主消息码
uchar SubID; //子消息码
GS_Head() :RootID(rootid){}
};
3. 广场服消息头定义
//广场消息头
struct GS_PlazaHead : public GS_Head < CMDROOT_PLAZA_MSG >
{
inline void InitRoot(uchar mainid, uchar subid)
{
RootID = CMDROOT_PLAZA_MSG;
MainID = mainid;
SubID = subid;
}
};
4. 邮件消息码定
// 邮件消息定义
enum GS_PLAZA_EMAILL_MSG
{
PLAZA_EMAIL_VIEW = 0, // 浏览邮件 c->s
// 后面消息省略
PLAZA_EMAIL_MAX
};
5. 邮件消息协议定义
template<uchar subid>
struct GS_EmailHead : public GS_PlazaHead
{
GS_EmailHead() { InitRoot(); }
inline void InitRoot()
{
GS_PlazaHead::InitRoot(GS_PLAZA_MSGID_EMAIL, subid);
}
};
/
//PLAZA_EMAIL_VIEW 客户端请求浏览邮件
struct GS_EmailView : public GS_EmailHead<PLAZA_EMAIL_VIEW>
{
GS_EmailView()
{
InitRoot();
}
};
6. 在发送变长数据的时候,一个字段用于记录变长数据大小,数据可以用一个长度为0的数组记录,内存需要自己申请。
struct GS_XXX : public GS_XXXHead<PLAZA_XXX>
{
int len;
uchar data[0];
GS_XXX()
{
memset(this, 0, sizeof(*this));
InitRoot();
}
};
三、协议组装
为了方便组装发送的数据或解析接收的数据,我们可以对发送数据组装功能进行一层封装。这里提供三种方法
3.1 简单存档接口
存档接口
// 存档接口
struct IArchive
{
//清理
virtual void Clear() = 0;
//释放
virtual void Release() = 0;
// 请求写入缓冲。nLen < 4K
// 这里有个假定:对象必须按顺序写入自身的数据,因为CMapContainer保存对象时
virtual bool Write(const void* buf,int nLen) = 0;
// 请求读出指定数量的数据
virtual bool Read(void* buf,int nLen) = 0;
// 获取当前缓冲指针所指的数据地址,适合直接操作缓冲区
virtual void* GetBuffer() = 0;
//获得头指针的数据地址
virtual void* GetHead() = 0;
//获得某个位置的数据地址
virtual void* GetPos(int nPos) = 0;
// 把当前指针向后移动nOffset字节,返回当前指针
// 如果操作失败,Seek返回-1
virtual sint Seek(sint nOffset = 0) = 0;
// 将指针移动指定位置
virtual sint SeekTo(sint nPtr = 0) = 0;
// 获得当前缓冲指针偏移
virtual sint GetBufferOffset() = 0;
// 读取数据时,获取剩余数据长度,写数据时,返回有效缓冲区长度
virtual sint GetLeftBufferLen() = 0;
//获得缓冲区的长度
virtual sint GetBufferLen() = 0;
// 写入一个字符串
virtual bool WriteString(const stchar* pCh)=0;
// 写入一个BYTE
virtual bool WriteBYTE(const uchar nValue) = 0;
// 写入一个WORD
virtual bool WriteWORD(const ushort nValue) = 0;
// 写入一个DWORD
virtual bool WriteDWORD(const ulong dwValue) = 0;
// 写入一个slong
virtual bool WriteLONG(const slong lValue) = 0;
// 写入一个sint64
virtual bool WriteINT64(const sint64 lValue64) = 0;
//读取一个字符串
virtual stchar* ReadString(stchar* szBuffer, ushort& nMaxLen) = 0;
//读取一个BYTE
virtual uchar ReadBYTE() = 0;
//读取一个WORD
virtual ushort ReadWORD() = 0;
//读取一个DWORD
virtual ulong ReadDWORD() = 0;
//读取一个slong
virtual slong ReadLONG() = 0;
//读取一个sint64
virtual sint64 ReadINT64() = 0;
};
//自动释放存档接口
struct AutoRleaseArchive
{
AutoRleaseArchive(IArchive* pArchive) :m_pArchive(pArchive){}
~AutoRleaseArchive(){ m_pArchive->Release(); }
IArchive* operator->() { return m_pArchive; }
IArchive* m_pArchive;
};
#pragma once
#include "IArchive.h"
//
//框架使用的存档接口
class CDataArchive : public IArchive
{
protected:
// 构造函数, 仅用于被继承
CDataArchive(void);
// 初始化数据
void Init(void* szBindBuffer, sint nMaxSize);
public:
CDataArchive(sint nMaxSize);
~CDataArchive();
public:
//销毁
void Destory();
//重启用
void ResetUse();
//判断是否可以重用
bool CanResetUse(sint& uLen);
//判断能否销毁
bool CanDestory();
//检测
bool Check();
//IArchive
public:
// 写入一个BYTE
bool WriteBYTE(const uchar nValue)final;
// 写入一个WORD
bool WriteWORD(const ushort nValue)final;
// 写入一个DWORD
bool WriteDWORD(const ulong dwValue)final;
// 写入一个slong
bool WriteLONG(const slong lValue)final;
// 写入一个sint64
bool WriteINT64(const sint64 lValue64)final;
// 写入一个字符串
bool WriteString(const stchar* pCh)final;
// 请求写入数据
bool Write(const void *buf, sint nLen)final;
public:
// 请求读出批定数量的数据
bool Read(void *buf, sint nLen)final;
//读取一个字符串
stchar* ReadString(stchar* szBuffer, ushort& nMaxLen)final;
//读取一个BYTE
uchar ReadBYTE()final;
//读取一个WORD
ushort ReadWORD()final;
//读取一个DWORD
ulong ReadDWORD()final;
//读取一个sint64
sint64 ReadINT64()final;
//读取一个LONG
slong ReadLONG()final;
// 获取当前缓冲指针所指的数据地址,适合直接操作缓冲区
void* GetBuffer()final;
//获得头指针的数据地址
void* GetHead()final;
//获得某个位置的数据地址
void* GetPos(sint nPos)final;
// 把当前指针向后移动nOffset字节,返回移动前的指针
// 设置nOffSet即可获得当前缓冲指针偏移
// 如果操作失败,Seek返回-1
sint Seek(sint nOffset)final;
// 同上,把缓冲指针移动到指定位置,返回移动前的指针
sint SeekTo(sint nPtr = 0)final;
// 获得当前缓冲指针偏移
sint GetBufferOffset(void)final;
// 读取数据时,获取剩余数据长度,写数据时,返回有效缓冲区长度
sint GetLeftBufferLen(void)final;
//获得缓冲区的长度
sint GetBufferLen()final;
//清理
void Clear()final;
//释放
void Release()final;
private:
sint m_nOffset;
uchar* m_pBindBuffer;
sint m_nMaxSize;
uchar* m_pNewBuffer;
ushort m_referenceCount;
};
#include "Archive.h"
// 构造函数, 仅用于被继承
CDataArchive::CDataArchive(void) :m_referenceCount(1),m_nOffset(0),m_pBindBuffer(0),m_nMaxSize(0){}
// 初始化数据
void CDataArchive::Init(void* szBindBuffer, sint nMaxSize)
{
if (0 == szBindBuffer || nMaxSize <= 0)
return;
m_nOffset = 0;
m_pBindBuffer = (uchar*)szBindBuffer;
m_nMaxSize = nMaxSize;
}
CDataArchive::CDataArchive(sint nMaxSize) :m_referenceCount(1)
{
#ifdef MEONRYCHECK
m_pNewBuffer = (uchar*)malloc(nMaxSize + sizeof(uchar));
((uchar*)m_pNewBuffer)[nMaxSize] = 0xED;
#else
m_pNewBuffer = (uchar*)malloc(nMaxSize);
#endif
m_nOffset = 0;
m_pBindBuffer = 0;
m_nMaxSize = 0;
Init(m_pNewBuffer, nMaxSize);
}
CDataArchive::~CDataArchive()
{
if (m_pNewBuffer != NULL && Check())
{
free(m_pNewBuffer);
m_pNewBuffer = NULL;
}
}
//销毁
void CDataArchive::Destory()
{
delete this;
}
//重启用
void CDataArchive::ResetUse()
{
if (m_referenceCount != 0)
g_pGlobalServer->GetTrace()->TraceErrorLn(TEXT("CDataArchive::ResetUse:%d"), m_referenceCount);
else
{
m_nOffset = 0;
++m_referenceCount;
}
}
//判断是否可以重用
bool CDataArchive::CanResetUse(sint& uLen)
{
return m_referenceCount == 0 && m_nMaxSize == uLen;
}
//判断能否销毁
bool CDataArchive::CanDestory()
{
return m_referenceCount == 0;
}
//检测
bool CDataArchive::Check()
{
#ifdef MEONRYCHECK
if (m_pNewBuffer != NULL && ((uchar*)m_pNewBuffer)[m_nMaxSize] != 0xED)
{
if (m_nMaxSize >= sizeof(GS_HeadNull))
{
GS_HeadNull* pHead = (GS_HeadNull*)m_pNewBuffer;
g_pGlobalServer->GetTrace()->TraceErrorLn(TEXT("数据打包器内存泄露:%d,%d,%d"), pHead->RootID, pHead->MainID, pHead->SubID);
}
else
g_pGlobalServer->GetTrace()->TraceErrorLn(TEXT("数据打包器内存泄露"));
return false;
}
#endif
return true;
}
// 写入一个BYTE
bool CDataArchive::WriteBYTE(const uchar nValue)
{
return Write(&nValue, sizeof(const uchar));
}
// 写入一个WORD
bool CDataArchive::WriteWORD(const ushort nValue)
{
return Write(&nValue, sizeof(const ushort));
}
// 写入一个DWORD
bool CDataArchive::WriteDWORD(const ulong dwValue)
{
return Write(&dwValue, sizeof(const ulong));
}
// 写入一个slong
bool CDataArchive::WriteLONG(const slong lValue)
{
return Write(&lValue, sizeof(slong));
}
// 写入一个sint64
bool CDataArchive::WriteINT64(const sint64 lValue64)
{
return Write(&lValue64, sizeof(sint64));
}
// 写入一个字符串
bool CDataArchive::WriteString(const stchar* pCh)
{
if (0 == pCh)
return false;
ushort wLen = (ushort)(_tcslen(pCh));
if (!Write(&wLen, sizeof(wLen)))
return false;
if (!Write(pCh, wLen*sizeof(stchar)))
return false;
return true;
}
// 请求写入数据
bool CDataArchive::Write(const void *buf, sint nLen)
{
if (m_nOffset + nLen > m_nMaxSize)
return false;
if (0 == buf)
return false;
if (0 >= nLen)
return false;
memcpy(m_pBindBuffer + m_nOffset, buf, nLen);
m_nOffset += nLen;
return true;
}
// 请求读出批定数量的数据
bool CDataArchive::Read(void *buf, sint nLen)
{
if (0 == buf || 0 >= nLen || m_nOffset + nLen > m_nMaxSize)
return false;
memcpy(buf, m_pBindBuffer + m_nOffset, nLen);
m_nOffset += nLen;
return true;
}
//读取一个字符串
stchar* CDataArchive::ReadString(stchar* szBuffer, ushort& nMaxLen)
{
ushort wLen;
if (!Read(&wLen, sizeof(wLen)))
return NULL;
if (wLen >= nMaxLen)
return NULL;
if (!Read(szBuffer, wLen*sizeof(stchar)))
return NULL;
nMaxLen = wLen;
return szBuffer;
}
//读取一个BYTE
uchar CDataArchive::ReadBYTE()
{
uchar nValue = 0;
Read(&nValue, sizeof(nValue));
return nValue;
}
//读取一个WORD
ushort CDataArchive::ReadWORD()
{
ushort nValue = 0;
Read(&nValue, sizeof(nValue));
return nValue;
}
//读取一个DWORD
ulong CDataArchive::ReadDWORD()
{
ulong nValue = 0;
Read(&nValue, sizeof(nValue));
return nValue;
}
//读取一个sint64
sint64 CDataArchive::ReadINT64()
{
sint64 nValue = 0;
Read(&nValue, sizeof(nValue));
return nValue;
}
//读取一个LONG
slong CDataArchive::ReadLONG()
{
slong nValue = 0;
Read(&nValue, sizeof(nValue));
return nValue;
}
// 获取当前缓冲指针所指的数据地址,适合直接操作缓冲区
void* CDataArchive::GetBuffer()
{
return m_pBindBuffer + m_nOffset;
}
//获得头指针的数据地址
void* CDataArchive::GetHead()
{
return m_pBindBuffer;
}
//获得某个位置的数据地址
void* CDataArchive::GetPos(sint nPos)
{
if (nPos < 0 || nPos >= m_nMaxSize)
return NULL;
return &m_pBindBuffer[nPos];
}
// 把当前指针向后移动nOffset字节,返回移动前的指针
// 设置nOffSet即可获得当前缓冲指针偏移
// 如果操作失败,Seek返回-1
sint CDataArchive::Seek(sint nOffset)
{
if (m_nOffset + nOffset > m_nMaxSize || m_nOffset + nOffset < 0)
return -1;
sint nOld = m_nOffset;
m_nOffset += nOffset;
return nOld;
}
// 同上,把缓冲指针移动到指定位置,返回移动前的指针
sint CDataArchive::SeekTo(sint nPtr)
{
if (nPtr > m_nMaxSize || nPtr < 0)
return -1;
sint nOld = m_nOffset;
m_nOffset = nPtr;
return nOld;
}
// 获得当前缓冲指针偏移
sint CDataArchive::GetBufferOffset(void)
{
return m_nOffset;
}
// 读取数据时,获取剩余数据长度,写数据时,返回有效缓冲区长度
sint CDataArchive::GetLeftBufferLen(void)
{
return m_nMaxSize - m_nOffset;
}
//获得缓冲区的长度
sint CDataArchive::GetBufferLen()
{
return m_nMaxSize;
}
//清理
void CDataArchive::Clear()
{
SeekTo(0);
}
//释放
void CDataArchive::Release()
{
Check();
if (m_referenceCount == 0)
g_pGlobalServer->GetTrace()->TraceErrorLn(TEXT("CDataArchive::Release:%d"), m_referenceCount);
else
--m_referenceCount;
}
存档接口管理
//存档接口管理
class CArchiveMgr
{
public:
//获得单例对象
static CArchiveMgr* GetInstance();
//获得一个存档接口对象
IArchive* NewArchive(sint nMaxSize);
//释放存档接口
void Close();
//销毁对象
void ReleaseArchive(CDataArchive* pArchive);
private:
typedef std::vector<CDataArchive*> ARCHIVEVEC;
typedef std::unordered_map<sint, ARCHIVEVEC> ARCHIVEMAP;
ARCHIVEMAP m_ArchiveMap; //缓存的存档接口
};
//
//获得单例对象
CArchiveMgr* CArchiveMgr::GetInstance()
{
static CArchiveMgr archivemgr;
return &archivemgr;
}
//获得一个存档接口对象
IArchive* CArchiveMgr::NewArchive(sint nMaxSize)
{
ARCHIVEVEC& archivevec = m_ArchiveMap[nMaxSize];
for (ARCHIVEVEC::iterator itr = archivevec.begin(); itr != archivevec.end(); ++itr)
{
if ((*itr)->CanResetUse(nMaxSize))
{
(*itr)->ResetUse();
return *itr;
}
}
CDataArchive* pArchive = new CDataArchive(nMaxSize);
archivevec.push_back(pArchive);
return pArchive;
}
//释放存档接口
void CArchiveMgr::Close()
{
for (ARCHIVEMAP::iterator mapitr = m_ArchiveMap.begin(); mapitr != m_ArchiveMap.end(); ++mapitr)
{
ARCHIVEVEC& archivevec = mapitr->second;
for (ARCHIVEVEC::iterator vecitr = archivevec.begin(); vecitr != archivevec.end(); ++vecitr)
{
ReleaseArchive(*vecitr);
}
archivevec.clear();
}
m_ArchiveMap.clear();
}
//销毁对象
void CArchiveMgr::ReleaseArchive(CDataArchive* pArchive)
{
GS_HeadNull head;
if (pArchive->GetBufferLen() >= sizeof(GS_HeadNull))
head = *((GS_HeadNull*)pArchive->GetHead());
pArchive->Destory();
}
3.2 流结构的读写,支持自动扩展
#pragma once
#include "IArchive.h"
#include <list>
#include <set>
class IStreamBuffer;
struct IStreamObject
{
virtual void serial(IStreamBuffer& buf) = 0;
virtual void unserial(IStreamBuffer& buf) = 0;
};
class IStreamBuffer
{
public:
IStreamBuffer() : m_pBuff(NULL) , m_posRead(0) , m_posWrite(0) , m_validateSize(0), m_bBad(false) {};
virtual ~IStreamBuffer() { _Clear(); }
virtual void Clear() { _Clear(); }
IStreamBuffer& operator=(const IStreamBuffer& r)
{
if (this != &r)
{
Clear();
Write(r.m_pBuff, r.m_validateSize);
m_posRead = r.m_posRead;
m_bBad = r.m_bBad;
}
return *this;
}
char* Data() const { return m_pBuff; }
char* GetReadBuffer() const { return m_pBuff + m_posRead; }
char* GetWriteBuffer() const { return m_pBuff + m_posWrite; }
ulong GetRemainSize() const { return m_validateSize - m_posRead; }
ulong Size() const { return m_validateSize; }
ulong BufferSize() const { return m_buffSize; }
ulong PosRead() const { return m_posRead; }
ulong PosWrite() const { return m_posWrite; }
bool SeekToWrite(ulong posWrite)
{
Resize(posWrite);
if (posWrite < m_buffSize)
{
m_posWrite = posWrite;
m_validateSize = posWrite;
return true;
}
return false;
}
void SkipRead(ulong size)
{
if (m_posRead + size <= m_validateSize) m_posRead += size;
else m_bBad = true;
}
void SkipWrite(ulong size) { Resize(m_posWrite + size); m_posWrite += size; m_validateSize += size; }
virtual void Write(void* src, ulong size)
{
ulong newSize = m_posWrite + size;
Resize(newSize);
if (newSize <= m_buffSize)
{
memcpy(&m_pBuff[m_posWrite], src, size);
m_posWrite += size;
m_validateSize += size;
}
else
{
m_bBad = true;
}
}
virtual void Read(void* dest, ulong size)
{
if (m_posRead + size <= m_validateSize)
{
memcpy(dest, &m_pBuff[m_posRead], size);
m_posRead += size;
}
else
{
m_bBad = true;
}
}
virtual void Put(ulong pos, void* src, ulong size)
{
ulong newSize = pos + size;
Resize(newSize);
if (newSize <= m_buffSize)
{
memcpy(&m_pBuff[pos], src, size);
}
else
{
m_bBad = true;
}
}
template<typename T> void _Write(const T& value) { Write((void*)&value, sizeof(T)); }
template<typename T> void _Read(T& value) { Read((void*)&value, sizeof(T)); }
template<typename T> void _Put(ulong pos, const T& value) { Put(pos, (void*)&value, sizeof(T)); }
IStreamBuffer& operator<<(const std::wstring& wstr)
{
ushort size = (ushort)wstr.size();
_Write(size);
if (size > 0)
{
Write((void*)wstr.data(), size*sizeof(wchar_t));
}
return *this;
}
IStreamBuffer& operator>>(std::wstring& wstr)
{
ushort size = 0;
_Read(size);
wstr.clear();
ulong byteCount = size * sizeof(wchar_t);
if (size > 0 && m_posRead + byteCount <= m_validateSize)
{
wstr.append((const wchar_t*)(&m_pBuff[m_posRead]), size);
SkipRead(byteCount);
}
return *this;
}
IStreamBuffer& operator<<(const std::string& str)
{
ushort size = (ushort)str.size();
_Write(size);
if (size > 0)
{
Write((void*)str.data(), size);
}
return *this;
}
IStreamBuffer& operator>>(std::string& str)
{
ushort size = 0;
_Read(size);
str.clear();
if (size > 0 && m_posRead + size <= m_validateSize)
{
str.append((const char*)m_pBuff[m_posRead], size);
SkipRead(size);
}
return *this;
}
IStreamBuffer& operator<<(const char* str)
{
this->operator<<(std::string(str));
}
IStreamBuffer& operator<<(const wchar_t* str)
{
this->operator<<(std::wstring(str));
}
IStreamBuffer& operator<<(const char& c) { _Write<char>(c); return *this; }
IStreamBuffer& operator>>(char& c) { _Read<char>(c); return *this; }
IStreamBuffer& operator<<(const bool& c) { _Write<bool>(c); return *this; }
IStreamBuffer& operator>>(bool& c) { _Read<bool>(c); return *this; }
IStreamBuffer& operator<<(const sint8& c) { _Write<sint8>(c); return *this; }
IStreamBuffer& operator>>(sint8& c) { _Read<sint8>(c); return *this; }
IStreamBuffer& operator<<(const uint8& c) { _Write<uint8>(c); return *this; }
IStreamBuffer& operator>>(uint8& c) { _Read<uint8>(c); return *this; }
IStreamBuffer& operator<<(const sint16& c) { _Write<sint16>(c); return *this; }
IStreamBuffer& operator>>(sint16& c) { _Read<sint16>(c); return *this; }
IStreamBuffer& operator<<(const uint16& c) { _Write<uint16>(c); return *this; }
IStreamBuffer& operator>>(uint16& c) { _Read<uint16>(c); return *this; }
IStreamBuffer& operator<<(const sint32& c) { _Write<sint32>(c); return *this; }
IStreamBuffer& operator>>(sint32& c) { _Read<sint32>(c); return *this; }
IStreamBuffer& operator<<(const uint32& c) { _Write<uint32>(c); return *this; }
IStreamBuffer& operator>>(uint32& c) { _Read<uint32>(c); return *this; }
IStreamBuffer& operator<<(const slong& c) { _Write<slong>(c); return *this; }
IStreamBuffer& operator>>(slong& c) { _Read<slong>(c); return *this; }
IStreamBuffer& operator<<(const ulong& c) { _Write<ulong>(c); return *this; }
IStreamBuffer& operator>>(ulong& c) { _Read<ulong>(c); return *this; }
IStreamBuffer& operator<<(const sint64& c) { _Write<sint64>(c); return *this; }
IStreamBuffer& operator>>(sint64& c) { _Read<sint64>(c); return *this; }
IStreamBuffer& operator<<(const uint64& c) { _Write<uint64>(c); return *this; }
IStreamBuffer& operator>>(uint64& c) { _Read<uint64>(c); return *this; }
IStreamBuffer& operator<<(const float& c) { _Write<float>(c); return *this; }
IStreamBuffer& operator>>(float& c) { _Read<float>(c); return *this; }
IStreamBuffer& operator<<(IStreamObject& base) { base.serial(*this); return *this; }
IStreamBuffer& operator>>(IStreamObject& base){ base.unserial(*this); return *this; }
template<typename T>
IStreamBuffer& operator<<(std::vector<T>& vec)
{
ulong count = (ulong)vec.size();
Write(&count, sizeof(count));
if (count > 0)
{
for (auto& v : vec) *this << v;
}
return *this;
}
template<typename T>
IStreamBuffer& operator>>(std::vector<T>& vec)
{
vec.clear();
ulong count = 0;
Read(&count, sizeof(count));
if (count > 0)
{
vec.resize(count);
for (ulong i = 0; i < count; ++i) *this >> vec[i];
}
return *this;
}
template<typename T>
IStreamBuffer& operator<<(std::list<T>& list)
{
ulong count = (ulong)list.size();
Write(&count, sizeof(count));
if (count > 0)
{
for (auto& v : list)
{
*this << v;
}
}
return *this;
}
template<typename T>
IStreamBuffer& operator>>(std::list<T>& list)
{
list.clear();
ulong count = 0;
Read(&count, sizeof(count));
if (count > 0)
{
T temp;
for (ulong i = 0; i < count; ++i)
{
*this >> temp;
list.push_back(temp);
}
}
return *this;
}
template<typename T>
IStreamBuffer& operator<<(std::set<T>& set)
{
ulong count = (ulong)set.size();
Write(&count, sizeof(count));
if (count > 0)
{
for (auto& v : set) *this << v;
}
return *this;
}
template<typename T>
IStreamBuffer& operator>>(std::set<T>& set)
{
set.clear();
ulong count = 0;
Read(&count, sizeof(count));
if (count > 0)
{
T temp;
for (ulong i = 0; i < count; ++i)
{
*this >> temp;
set.insert(temp);
}
}
return *this;
}
IStreamBuffer& operator<<(const IStreamBuffer& c)
{
ulong size = c.Size();
_Write(size);
Write(c.m_pBuff, size);
return *this;
}
IStreamBuffer& operator>>(IStreamBuffer& c)
{
ulong size = 0;
_Read(size);
if (m_posRead + size <= m_validateSize)
{
c.Write(&m_pBuff[m_posRead], size);
}
return *this;
}
bool IsBad() { return m_bBad; }
bool IsOk() { return !m_bBad; }
bool Resize(ulong newSize)
{
if (newSize > m_buffSize) { _Resize(newSize); }
return m_buffSize >= newSize;
}
protected:
void _Clear()
{
m_buffSize = 0;
m_posRead = 0;
m_posWrite = 0;
m_validateSize = 0;
m_bBad = false;
m_pBuff = NULL;
}
virtual void _Resize(ulong newSize) = 0;
protected:
ulong m_buffSize; // 缓存大小
ulong m_posRead; // 读pos
ulong m_posWrite; // 写pos
ulong m_validateSize; // 有效数据大小
bool m_bBad; // 状态
char* m_pBuff; // 缓存
};
template<ulong defSize>
class CBuffer : public IStreamBuffer
{
public:
CBuffer()
{
Init();
};
virtual ~CBuffer() { _Clear(); }
virtual void Clear() // reset
{
_Clear();
IStreamBuffer::Clear();
Init();
}
CBuffer(const CBuffer& r)
{
Clear();
IStreamBuffer::operator=(r);
}
CBuffer& operator=(const CBuffer& r)
{
if (this != &r)
{
Clear();
IStreamBuffer::operator=(r);
}
return *this;
}
void Init()
{
m_buffSize = defSize;
m_pBuff = &m_buffInternal[0];
}
protected:
void _Clear()
{
if (m_pBuff && m_pBuff != &m_buffInternal[0])
{
delete[] m_pBuff;、
m_pBuff = NULL;
m_buffSize = 0;
}
}
virtual void _Resize(ulong newSize)
{
if (newSize > m_buffSize)
{
newSize = (newSize > m_buffSize * 2) ? newSize : (m_buffSize * 2);
char* newBuf = new char[newSize];
if (m_pBuff && Size() > 0)
{
memcpy(newBuf, m_pBuff, Size());
}
_Clear();
m_pBuff = newBuf;
m_buffSize = newSize;
}
}
private:
char m_buffInternal[defSize];
};
class CReadBufferHelper : public IStreamBuffer
{
public:
CReadBufferHelper(void* pData, ulong size)
{
m_pBuff = (char*)pData;
m_buffSize = size;
m_validateSize = size;
}
~CReadBufferHelper() {};
virtual void _Resize(ulong newSize) { };
virtual void Write(void* src, ulong size) { /*throw "read only, disable write feature.";*/ };
virtual void Put(ulong pos, void* src, ulong size)
{
//throw "read only, disablBe write feature.";
}
virtual void Clear()
{
m_posRead = 0;
m_posWrite = 0;
}
private:
CReadBufferHelper(const CReadBufferHelper& r) = delete;
CReadBufferHelper& operator=(const CReadBufferHelper& r) = delete;
};
class CWriteBufferHelper : public IStreamBuffer
{
public:
CWriteBufferHelper(IArchive* pData, ulong size)
{
m_pArchive = pData;
m_pBuff = (char*)m_pArchive->GetHead();
m_buffSize = size;
m_bBad = false;
}
CWriteBufferHelper(void* pData, ulong size)
{
m_pArchive = NULL;
m_pBuff = (char*)pData;
m_buffSize = size;
m_bBad = false;
}
~CWriteBufferHelper() { if (m_pArchive) m_pArchive->Release(); }
virtual void _Resize(ulong newSize) { m_bBad = true; };
virtual void Read(void* src, ulong size) { /*throw "write only, disable read feature.";*/ }
virtual void Clear()
{
m_posRead = 0;
m_posWrite = 0;
m_validateSize = 0;
m_bBad = false;
}
private:
CWriteBufferHelper(const CWriteBufferHelper& r) = delete;
CWriteBufferHelper& operator=(const CWriteBufferHelper& r) = delete;
IArchive* m_pArchive;
};
typedef CBuffer<512> XBuffer;
#define BUILD_VECTOR_PACKET(buf, vec)\
{\
ulong count = (ulong)vec.size(); \
buf << count; \
if (count > 0)\
{\
ulong szStruct = sizeof(vec[0]); \
buf << szStruct; \
buf.Write((void*)vec.data(), count * szStruct); \
}\
}
#define BUILD_VECTOR_PACKET_CLASS(buf, vec)\
{\
ulong count = (ulong)vec.size(); \
buf << count; \
for (auto& v : vec) \
{\
buf << v;\
}\
}
#define LOAD_VECTOR_PACKET(buf, vec)\
{\
vec.clear(); \
ulong count = 0; \
buf >> count; \
if (count > 0) \
{\
ulong szStruct = 0; \
buf >> szStruct; \
if (szStruct == sizeof(vec[0])) \
{\
vec.resize(count); \
buf.Read((void*)vec.data(), count * szStruct); \
}\
}\
}
#define LOAD_VECTOR_PACKET_CLASS(buf, vec)\
{\
vec.clear(); \
ulong count = 0; \
buf >> count; \
if (count > 0) \
{\
vec.resize(count); \
for (ulong i = 0; i < count; ++i)\
{\
buf >> vec[i];\
}\
}\
}
#define BUILD_ARRAY_PACKET(buf, arr, count)\
{\
buf << count; \
if (count > 0)\
{\
ulong szStruct = sizeof(arr[0]); \
buf << szStruct; \
buf.Write((void*)&arr[0], count * szStruct); \
}\
}
#define LOAD_ARRAY_PACKET(buf, arr, arrSize)\
{\
ulong count = 0; \
buf >> count; \
if (count > 0 && arrSize == count)\
{\
ulong szStruct = 0; \
buf >> szStruct; \
if (szStruct == sizeof(arr[0]))\
{\
buf.Read((void*)&arr[0], szStruct * count); \
}\
}\
}
3.3 带内存管理的消息接口
内存获取不是每次都是从堆上申请,而是在内存池中获取一个之前开辟好的内存单元,没有就先申请,相同的网络消息数据大小往往都是相同,可以重复利用,避免过多内存碎片。
3.4 优缺点比较
方法一
优点:
- 协议组合方便
- 可以检测内存是否越界
- 可以统计待发送的消息
缺点
- 没有对内存进行复用,容易产生大量内存碎片
- 对一些一些容器数据写入不太方便
方法二
优点:
- 可以通过数据流的形式写入或读取数据
- 内容可以自动扩展
- 一些容器数据的写入和读取很方便
缺点:
- 没有对内存进行复用,容易产生大量内存碎片
- 写入的数据超过当前申请的内存
方法三:
优点:
- 支持内存复用
- 支持流结构
缺点:
- 实现较为复杂
四、扩展延伸
在发送消息的时候,数据从应用层发送给网络层需要一次拷贝,网络层将数据发送给缓冲区又进行了一次拷贝,能不能减少一次拷贝呢?
这需要网络层支持,在网络层中将数据拷贝到缓冲区时,定义好接口,只传入指针和数据大小,在数据发送完之后,我们在将数据进行回收,可以达到减少一次拷贝。结合方法三,在回收的时候可以对数据进行判断,如果是内存池中的数据,将其发到内存中,或者数据块较大,也将它发到内存池中的大数据中,大数据数据大小我们自己定义,一般定义为大于16k。