本文的持久存储数据库是以自定义的文件数据库为基础的。但原理上适用于各种持久型数据库的缓存加速。
设计上:
(1)角色描述数据列表有三个:角色id为键的,角色名为键的,账号id为键的。
(2)在服务器启动时把角色描述数据文件读取到角色描述数据列表。
(3)添加角色时添加角色描述数据结构到角色描述数据列表。
(4)查询角色时先检查角色描述数据列表中的该角色描述数据。
(5)修改角色名字时需要修改角色名为键的角色描述数据列表。
1、角色描述数据结构
角色描述结构
角色描述结构存储在CharDesc数据库中,存储所有角色的查询以及关系信息。
存储的数据的目的在于以给小的内存开销以及更快的效率实现对角色是否存在的
判断、获取与特定账号对应的角色列表、获取特定角色职业、所属账号、名称、
等级、性别、所属国家、状态以及更新日期等常用的关系型数据。
typedef struct CharDesc
{
const INT64
nCharId;
//角色ID,加const以防止被误修改
const INT64
nUserId;
//账号ID,加const以防止被误修改
const CHAR
sCharName[48];
//角色名称,加const以防止被误修改
const CHAR
sAccount[64];
//角色所属账号字符串,加const以防止被误修改
WORD
wState;
//角色状态1,类型为CharDescState中的枚举值集合
WORD
wLevel;
//等级
BYTE
btGender;
//性别
BYTE
btJob;
//职业
BYTE
btReserve0[2];
//----reserve
INT
nCreateTime;
//创建时间(MiniDateTime)
INT
nUpdateTime;
//上次更新时间(MiniDateTime)
INT
nLoginIP;
//上次登陆IP
WORD
wServerId;
//所属服务器ID
BYTE
btReserve1[42];
//保持结构大小为192字节
}CHARDESC, *PCHARDESC;
保留一些字段作为拓展使用和字节对齐。
2、装载数据到角色描述列表
从自定义文件数据库装载角色描述数据到角色描述列表。
有3个不同索引的不同角色描述数据列表。
BOOL CLocalDB::LoadAllCharDesc()
{
INT_PTR i;
INT64 nDataId, nDataSize;
INT_PTR nCharCount = (INT_PTR)m_CharDescDB.getDataCount(); //有效角色描述数据索引数量
CharDesc *pCharDesc = m_CharDescAllocator.allocObjects(nCharCount);//内存池分配角色描述内存(会自动回收)
//保留列表内存空间并暂时关闭排序功能,在所有数据被读取并添加到列表后再排序可以提升效率
m_CharIdSortList.setSorted(FALSE);
m_CharIdSortList.reserve(nCharCount);//基于角色ID排序的角色描述列表
m_CharNameSortList.setSorted(FALSE);
m_CharNameSortList.reserve(nCharCount);//基于角色名称排序的角色描述列表
m_UserIdSortList.setSorted(FALSE);
m_UserIdSortList.reserve(nCharCount);//基于账号ID排序的角色描述列表
for (i=0; i<nCharCount; ++i) //遍历角色描述数据(第一个数据、第二个数据...)
{
nDataId = m_CharDescDB.getId(i); //获取(角色描述)数据记录唯一ID值
nDataSize = m_CharDescDB.get(nDataId, pCharDesc, sizeof(*pCharDesc));//读取角色描述数据文件到内存
if (nDataSize < (INT64)sizeof(*pCharDesc))
{
logError("角色描述数据库中ID为%lld的数据异常,读取字节为%lld", nDataId, nDataSize);
return FALSE;
}
//添加角色描述信息到列表
m_CharIdSortList[i].pCharDesc = pCharDesc;
m_CharNameSortList[i].pCharDesc = pCharDesc;
m_UserIdSortList[i].pCharDesc = pCharDesc;
pCharDesc++;
}
//读取完成后重新对列表开启排序
m_CharIdSortList.trunc(nCharCount);
m_CharIdSortList.setSorted(TRUE);
m_CharNameSortList.trunc(nCharCount);
m_CharNameSortList.setSorted(TRUE);
m_UserIdSortList.trunc(nCharCount);
m_UserIdSortList.setSorted(TRUE);
return TRUE;
}
3、角色缓存使用
(1)角色数据查询
获取角色数据
从角色描述列表检查角色存在及其状态的正常性
从更新队列里面取角色数据,防止出现快速重登录导致的回档现象
从磁盘文件读取角色数据
VOID CDBDataClient::CatchLoadCharData(CDataPacketReader &inPacket)
{
INT nUserId;
INT64 nCharId;
DWORD dwDataSize;
inPacket >> nUserId;
inPacket >> nCharId;
CDataPacket &pack = AllocProtoPacket(DBType::dsLoadCharData);
//先检查角色描述列表的是否有该角色,并且检查该角色的状态
CharDesc *pCharDesc = m_pLocalDB->GetCharDesc(nCharId);//从角色描述列表获取角色描述数据
if (!pCharDesc || pCharDesc->nUserId != nUserId
|| (pCharDesc->wState & (CHARSTATE_DELETED | CHARSTATE_DISABLED))//检查角色是否被删除或者被禁止
|| pCharDesc->wServerId != m_nServerId )
{
//Error: 非法的角色数据加载请求
pack << (int)1;
pack << nCharId;
}
else//检查角色存在及其状态的正常性
{
dwDataSize = 0;
pack << (int)0;//SUCCESS
pack << nCharId;
pack << dwDataSize;
//优先从更新队列里面取角色数据,防止出现快速重登录导致的回档现象
dwDataSize = m_pLocalDB->GetCharDataInUpdateList(nCharId, pack);
if (dwDataSize != 0)
{
pack.adjustOffset(-(INT64)(dwDataSize + sizeof(dwDataSize)));
pack << dwDataSize;
pack.adjustOffset(dwDataSize);
}
else
{
//从磁盘文件读取
dwDataSize = (DWORD)m_pLocalDB->GetCharData(nCharId, NULL, 0);
pack.adjustOffset(-(INT64)(sizeof(dwDataSize)));
pack << dwDataSize;
//如果有角色数据则直接返回角色数据,否则将返回角色描述
if (dwDataSize > 0)
{
pack.reserve(pack.getLength() + dwDataSize);
pack.adjustOffset(m_pLocalDB->GetCharData(nCharId, pack.getOffsetPtr(), dwDataSize));
}
else//返回角色描述数据
{
WriteCharDesc(pack, pCharDesc);
}
}
}
FlushProtoPacket(pack);
}
从角色描述列表获取角色描述数据
GameDBType::CharDesc* CLocalDB::GetCharDesc(INT64 nCharId) const
{
INT_PTR nIdx = m_CharIdSortList.search(nCharId);
if (nIdx < 0)
return NULL;
return m_CharIdSortList[nIdx].pCharDesc;
}
(2)创建角色数据
处理创建角色消息
创建角色
VOID CDBDataClient::CatchCreateChar(CDataPacketReader &inPacket)
{
SERVERUSERHANDLE UserHandle;
INT nUserId;
CHAR sAccount[64], sCharName[64];
BYTE btGender = 0, btJob = 0;
CharDesc *pDesc;
sAccount[0] = sCharName[0] = 0;
sAccount[ArrayCount(sAccount)-1] = 0;
sCharName[ArrayCount(sCharName)-1] = 0;
inPacket >> UserHandle;
inPacket >> nUserId;
inPacket.readString(sAccount, ArrayCount(sAccount) - 1);
inPacket.readString(sCharName, ArrayCount(sCharName) - 1);
inPacket >> btJob;
inPacket >> btGender;
CDataPacket &pack = AllocProtoPacket(DBType::dsCreateChar);
pack << UserHandle;
//检查数据是否有效
if (!sAccount || !sAccount[0] || !sCharName || !sCharName[0]
|| btGender > 1 || (strlen(sCharName) > 24))
{
//Error:无效的请求数据
pack << (int)-1;
pack << nUserId;
}
else if (!m_pDataServer->GetDBEngine()->getEnableCreateCharacter())
{
//Error:本服务器禁止创建角色
pack << (int)-2;
pack << nUserId;
}
else
{
//将角色名称中的字母全部最小化并检查名称是否合法
CDBEngine *pEngine = m_pDataServer->GetDBEngine();
pEngine->LowerCaseNameStr(sCharName);
if (!pEngine->CheckNameStr(sCharName))//检查角色名字的合法性
{
//Error:角色名非法
pack << (int)-3;
pack << nUserId;
}
else
{
//判断是否能创建更多的角色,最多允许3个角色
if (m_pLocalDB->GetUserAvaliableCharCount(nUserId, m_nServerId) > 2) //已经有3个角色了
{
//Error:无法创建更多的角色
pack << (int)-4;
pack << nUserId;
}
else
{
//角色名称是否已被使用
if (m_pLocalDB->GetCharDesc(sCharName))//从角色名字为键的角色描述列表检查
{
//Error:角色名称已被使用
pack << (int)-5;
pack << nUserId;
}
else
{
//向LocalDB添加角色
pDesc = m_pLocalDB->AddChar(m_nServerId, nUserId, sAccount, sCharName, btJob, btGender);
if (!pDesc)
{
//Error:服务器内部错误(LocalDB数据库错误)
pack << (int)-6;
pack << nUserId;
}
else
{
m_pSQLDB->PostAddNewChar(pDesc);
//返回新创建的角色描述
pack << (int)0;
pack << nUserId;
WriteCharDesc(pack, pDesc);//角色描述数据写到消息包
}
}
}
}
}
FlushProtoPacket(pack);
}
添加角色
GameDBType::CharDesc* CLocalDB::AddChar(INT nServerId, INT64 nUserId, LPCSTR sAccount,
LPCSTR sCharName, INT nJob, INT nGender)
{
CharDesc *pCharDesc = GetCharDesc(sCharName);//检查名字,如果有改名字的角色存在,则添加失败
if (pCharDesc)
return NULL;
//构造角色描述数据
pCharDesc = m_CharDescAllocator.allocObjects(1);//分配角色描述数据结构
ZeroMemory(pCharDesc, sizeof(*pCharDesc));
*((PINT64)&pCharDesc->nCharId) = GameDBType::makeCharId((WORD)nServerId).llid;//生成角色guid
*((PINT64)&pCharDesc->nUserId) = nUserId;//角色id
strncpy((LPSTR)pCharDesc->sCharName, sCharName, ArrayCount(pCharDesc->sCharName)-1); //角色名
((LPSTR)pCharDesc->sCharName)[ArrayCount(pCharDesc->sCharName)-1] = 0;
strncpy((LPSTR)pCharDesc->sAccount, sAccount, ArrayCount(pCharDesc->sAccount)-1);//账号
((LPSTR)pCharDesc->sAccount)[ArrayCount(pCharDesc->sAccount)-1] = 0;
pCharDesc->btJob = nJob;//职业
pCharDesc->btGender = nGender;//性别
pCharDesc->nCreateTime = CMiniDateTime::now(); //创建时间
*((PWORD)&pCharDesc->wServerId) = nServerId;//服务器id
//将新的角色描述数据投递到角色描述更新列表中(交由本地文件线程处理,写到本地文件数据库)
PostUpdateCharDesc(pCharDesc);
//向各个角色排序列表中添加角色描述数据
CharIdSortedDesc idDesc;
CharNameSortedDesc nameDesc;
UserIdSortedDesc uidDesc;
idDesc.pCharDesc = pCharDesc;
nameDesc.pCharDesc = pCharDesc;
uidDesc.pCharDesc = pCharDesc;
//添加到角色描述列表
m_CharIdSortList.add(idDesc);
m_CharNameSortList.add(nameDesc);
m_UserIdSortList.add(uidDesc);
return pCharDesc;
}