粘包处理

依据传奇游戏服务器源码总结了一下服务器开发中比较关心的一些问题。

(1)线程之间的共享数据如何同步
CIntLock封装了临界区管理,包含了Lock()和Unlock()两个操作函数,所有
需要同步的类都从CIntLock派生,例如:CWHQueue,CDBManager,CGlobalUserList,CUserInfo,CPlayerObject


(2)数据库是如何管理和连接的
数据库服务器DBSvr采用ODBC进行数据库连接,CConnection,CDatabase,CRecordset,CDBManager等类实现对数据库的管理,这几个类
对ODBC SDK进行了封装。
DBSvr.cpp:数据库连接采用一次连接多次使用的方式,在应用程序初始化函数InitInstance中,
GetDBManager()->Init( InsertLogMsg, szDatabase, "sa", "prg" );
进行数据库的连接。

(3)玩家处理线程ProcessUserHuman主要完成什么工作
GameSvr/ProcessUserHuman.cpp
ProcessUserHuman线程主要负责处理玩家的游戏动作以及数据的发送
pUserInfo->Operate(); // 执行玩家游戏动作
pGateInfo->xSend(); // 发送数据

(4)数据如何发送
在AcceptEx接受连接后,将套接字发送缓冲设置为0,
int zero = 0;
setsockopt(pGateInfo->sock, SOL_SOCKET, SO_SNDBUF, (char *)&zero, sizeof(zero) );

需要发送的数据通过m_pUserInfo->m_pGateInfo->m_xSendBuffQ.PushQ((BYTE *)lpSendBuff);
放入发送队列。由ProcessUserHuman线程中循环调用pGateInfo->xSend();进行发送。

因为发送缓冲已设置为0,那么接下去在pGateInfo->xSend()中调用WSASend将被阻塞,ProcessUserHuman线程中调用所有pGateInfo的xSend(), 数据将被顺序发送出去。

(5)CGateInfo有什么作用,对象是在哪里分配的
CGateInfo用于管理数据收发,包含于CUserInfo对象中。

GameSvr/SockMsg_GateComm.cpp
 AcceptThread中分配CGateInfo对象,并加入到全局列表中
  CGateInfo* pGateInfo = new CGateInfo;

  if (pGateInfo)
  {
   pGateInfo->m_sock = Accept;
   CreateIoCompletionPort((HANDLE)pGateInfo->m_sock, g_hIOCP, (DWORD)pGateInfo, 0);
   if (g_xGateList.AddNewNode(pGateInfo))
   ......

(6)CUserInfo有什么作用,对象在哪里分配
CUserInfo是非常重要的一个类,用于管理玩家信息,包含了
CPlayerObject* m_pxPlayerObject;
CGateInfo* m_pGateInfo;
Operate();
等重要变量与函数。

对于GameSvr:
GameSvr/GateInfo.cpp
 CGateInfo::OpenNewUser中,
 取出g_xUserInfoArr中空闲的CUserInfo对象加入到g_xLoginOutUserInfo列表中,然后对pUserInfo进行赋值
  pUserInfo->Lock();
  pUserInfo->m_sock = lpMsgHeader->nSocket;
  pUserInfo->m_pxPlayerObject = NULL;
  pUserInfo->m_pGateInfo = this;
  pUserInfo->Unlock();
对于LoginSvr:
CGateInfo有个变量CWHList<CUserInfo*> xUserInfoList;
CGateInfo::ReceiveOpenUser中分配CUserInfo对象,xUserInfoList.AddNewNode加入到xUserInfoList列表中

GameSvr与LoginSvr分配CUserInfo对象的方式的不同,其根本原因在于LoginSvr只在登录过程中使用CUserInfo,
不像GameSvr一样需要在整个游戏中时间使用CUserInfo对象。

(7)在何处投递收数据操作
GameSvr/SockMsg_GateComm.cpp
在AcceptThread线程中,接受一个连接后,pGateInfo->Recv();投递异步收数据操作
收缓冲位于:pGateInfo->OverlappedEx[0]中
工作线程ServerWorkerThread中,在收到数据并处理完成后继续投递异步收数据操作

(8)收数据完成后的处理
GameSvr/SockMsg_GateComm.cpp
工作线程ServerWorkerThread中,
if (lpOverlapped->nOvFlag == OVERLAPPED_RECV)
{
....
// 修改缓冲区数据实体的大小
pGateInfo->OverlappedEx[0].bufLen += dwBytesTransferred;
// 循环判断是不是完整的包
while ( pGateInfo->HasCompletionPacket() )
{
 // 解包操作...

(9)判断是否收到了完整的包
CGateInfo::HasCompletionPacket中,缓冲区收到的数据长度>=包头固定长度+包头中指明的数据区长度,就
认为收到了完整的包。

(10)如何进行解包操作
char * CGateInfo::ExtractPacket( char *pPacket )
{
 // 包大小=包头大小+数据区大小
 int packetLen = sizeof( _TMSGHEADER ) + ((_LPTMSGHEADER) &OverlappedEx[0].Buffer)->nLength;

 // 把完整的包复制到pPacket中
 memcpy( pPacket, OverlappedEx[0].Buffer, packetLen );

 // 把完整的包后面的数据移到缓冲区头部(解决粘包的问题)
 memmove( OverlappedEx[0].Buffer, OverlappedEx[0].Buffer + packetLen, DATA_BUFSIZE - packetLen );

 // 修改缓冲区数据大小
 OverlappedEx[0].bufLen -= packetLen;

 return pPacket + packetLen;
}

(11)如何处理玩家游戏数据
在ServerWorkerThread中解包操作完成后,如果数据类型是GM_DATA类型,调用pUserInfo->ProcessUserMessage
处理玩家游戏数据,调用m_pxPlayerObject->AddProcess,在AddProcess中分配PROCESSMSG对象,并将数据复制到
该对象中,然后Push到m_ProcessQ队列中待处理。ProcessUserHuman线程中遍历列表中的所有用户,调用
pUserInfo->Operate(),在CPlayerObject::Operate中从m_ProcessQ中Pop出游戏动作进行处理。

  • 0
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值