本文讨论的游戏架构设计中,分为两进程(逻辑服务器进程和数据服务器进程),其中逻辑服务器进程包含多个逻辑网关,单个逻辑网关中含4类线程,发送线程是其中一种并在一个逻辑网关中存在多个,用来处理发送业务。
设计上:
(1)每个玩家有对应的发送线程(N:1,根据发送线程数量哈希取余),发送时需要把数据包提交到该线程的发送添加队列里。 (2)每个玩家有个对应的网关指针 (3)分配发送线程时,会根据网关用户索引(这里的用户索引是会话索引,是会话列表中该会话的下标),获取逻辑网关上的会话列表中的会话。 (4)网关用户索引取余该逻辑网关上的发送线程数,从发送线程列表获取发送线程,并把发送包提交到该发送线程的消息队列。 (5)每个发送线程有个单独的数据包内存池。逻辑线程和每个发送线程交换数据时,每个发送线程有个单独的互斥量。数据包内存池的分配和回收都需要加锁(因为逻辑线程和发送线程之间的数据包内存池管理需要互斥)。
申请发送数据包,需要从逻辑网关的指定发送线程的内存池里获取。
在逻辑网关里分配发送数据包,根据该用户的网关会话索引就可以获取该玩家的会话,再哈希获取发送线程数组中的发送线程。需要设置该包的发送线程、用户索引、验证码。
inline CGateSendPacket& allocSendPacket() { return m_pGate->AllocSendPacket(m_nGateUserIndex); }
CGateSendPacket& ExecSockDataMgr::AllocSendPacket(const int nUserIndex)//nUserIndex 是网关里的用户的索引
{
PRUNGATEUSERSESSION pSession = m_SessionList[nUserIndex];//每个逻辑网关有一个会话列表,每个玩家相对应的会有一个用户索引
assert(pSession != NULL);
PEXECDATASENDTHREAD pSendThread = &m_SendThreads[nUserIndex % m_nSendThreadCount];//哈希发送线程(发送线程的个数是配置的,目前单逻辑网关有2个发送线程,因为游戏服务器发送的数据比接受的数据要多些)
CGateSendPacket *pPacket = pSendThread->pSendPacketPool->allocPacket();
pPacket->setSendInfo(pSendThread, nUserIndex, pSession->nVerifyIdx);//设置发送线程指针
return *pPacket;
}
数据发送线程中的数据缓存结构
typedef struct structExecDataSendThread
{
...
int nThreadIdx; //线程id
CRITICAL_SECTION SendQueueLock;//互斥锁(临界区)
CBList<CGateSendPacket*> *pSendAppendList;//发送添加列表
CBList<CGateSendPacket*> *pSendProcList;//发送处理列表
CGateSendPacketPool *pSendPacketPool;//发送数据包池
}EXECDATASENDTHREAD, *PEXECDATASENDTHREAD;
在提交后会把该数据包提交到该发送线程的数据包添加队列里。
CGateSendPacket &pack = ((CPlayer*)viewList->pEntity)->allocSendPacket();
pack.writeCmd(sysBattle, sStartRun);
pack << data;//写入字节流
pack.flush();
//提交网络数据包到发送队列
inline void flush()
{
if (m_pSendThread)
{
EnterCriticalSection( &m_pSendThread->SendQueueLock );
m_pSendThread->pSendAppendList->add(this);//添加到该线程的发送添加队列
LeaveCriticalSection( &m_pSendThread->SendQueueLock );
m_pSendThread = NULL;//置空避免重复提交
}
}
3、发送数据线程例程
把发送队列的数据拷贝到发送缓冲区。
发送发送缓冲区中的数据。
发送时需要验证发送包的验证码(nVerifyIdx)跟会话的验证码是否是一样的(发送的验证码(主要适用于服务器之间的连接的安全验证,对于客户端的连接可考虑去掉)。
[cpp] view plain copy
void *ExecSockDataMgr::SendDataProcessRoutine(PEXECDATASENDTHREAD pRunThread)
{
TICKCOUNT dwProcStartTick;
ExecSockDataMgr *pRunData;
int nLockSendBufQLockFail = 0;
pRunData = pRunThread->pRunData;
while ( !pRunData->m_boStoping )
{
dwProcStartTick = _getTickCount();
if ( pRunData->CopyWaitSendBuffers( pRunThread, TRUE ) )//交换发送队列和添加队列,拷贝会话的发送队列的数据到会话的发送缓冲区
nLockSendBufQLockFail = 0;
else nLockSendBufQLockFail++;
pRunData->CheckSendSessionBuffers( pRunThread );//发送会话的的发送缓冲区的数据
//循环小于指定时间则休眠一次(16ms),避免频繁io发送(发送可缓存在队列和发送缓存中)
pRunThread->dwProcTick = _getTickCount() - dwProcStartTick;
if ( pRunThread->dwProcTick < 16 )
{
dwProcStartTick = _getTickCount();
moon::OS::osSleep( 1 );
}
}
ExitThread( 0 );
}