王骏的博客
编程、网络技术点滴...

公告

逐渐将VC知识库的博客迁移到这里!

随笔分类

随笔档案

相册

最新评论

阅读排行榜

评论排行榜

程序员博客   首页  新随笔  订阅  管理  登录 
 
王骏的博客 阅读(4173) 评论(4)

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

(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出游戏动作进行处理。


评论列表
gaoqing000
re: 传奇游戏服务器源码学习
pUserInfo->Operate(); // 执行玩家游戏动作


这是怎么一回事啊
fastzhao
re: 传奇游戏服务器源码学习
觉得传奇的服务器设计的比较烂,发送竟然用阻塞的!万一发送网络阻塞,那不就卡住了吗!
还有就是个IOCP框架。没什么深奥的东西。
Diviner
re: 传奇游戏服务器源码学习
to:fastzhao
传奇的服务器版本有几个版本,最早人家是用delphi版本写的,后来国内一家公司改用C++实现了一下,但错误比较多。但毕竟实现了。

好像不是堵塞的。
IOCP框架要写得好,也不见得多么容易。
Abi
If my problem was a Death Star, this article is a photon topredo.

发表评论
切换编辑模式