peakzhang 发表于 2008-9-9 19:18:08

支持RunGate的服务器框架实例

作者:尚海忠


当前,一般采用Client/Server架构来开发网络游戏,客户端和服务器一般通过TCP协议进行通信。在设计游戏服务器的过程中,如何保证服务器的安全和最大限度的支持更多的客户端连接是摆在开发者面前一个重要的问题。
为了解决上述两大问题,根据我们的经验,在开发游戏服务器的的过程中,采用如下的服务器架构可有效解决上述两个问题。
图:支持RunGate的游戏服务器架构
在该架构下,Client和RunGate相连,而不是直接和GameServer相连。RunGate主要负责转发客户端和GameServer之间的数据包,GameServer负责处理游戏的所有逻辑。
采用该架构,有如下几个优点:
1、客户端通过RunGate和游戏服务器GameServer相连,GameServer IP对外不可见,这样GameServer更安全,更不易受攻击。一个GameServer对应若干个RunGate,当某个RunGate受攻击或荡机后,其它的RunGate扔然照常运行,和其它RunGate相连的客户端仍然可正常玩游戏。
2、GameServer可支持更多的客户端连接。GameServer把众多的客户端连接分散到多个RunGate中去,而不是独自来承担,从而可支持更多的客户端连接。
3、RunGate可分担一部分安全管理工作,减轻GameServer的压力。例如若某个客户端在一段时间内不发数据包,则把该客户端踢下线的工作可由RunGate来完成。
那么该架构下的GameServer如何来设计呢?在开发游戏的实践中,针对该服务器架构,提出了一个支持RunGate的服务器开发框架,并在《三国豪侠转》服务器中获得了广泛应用,经过了实践的检验。下面对该框架进行详细介绍。
一、一个支持RunGate的服务器开发框架
GameServer有如下两个基本功能:
1、管理和它相连的RunGate以及通过RunGate和GameServer进行通信的客户端;
2、解析客户端通过RunGate转发来的数据包,根据具体协议处理每一个数据包;
在该框架中,主要有四个相关的类CRunGateCtrl、CRunGate、CPlayer、CPacketParser。下面详述这些类的接口及其主要功能。
CRunGateCtrl的功能和接口定义
   类CRunGateCtrl在GameServer中,负责管理和它相连的RunGate。其主要功能有:
 侦听RunGate的TCP连接,当 某个RunGate连接到来时,CRunGateCtrl就创建一个CRunGate对象来管理该RunGate。
 维护一个CRunGate列表,当一个RunGate连接到来后,就创建一个CRunGate对象插入到该列表中;当某个RunGate关闭或断开连接时,就把该对象从列表中删除。
 当GameServer关闭时,释放所有资源。包括关闭侦听端口、把CRunGate从列表中清除等功能。
类CRunGateCtrl的定义如下:
//SOCKET为GameServer侦听RunGate到来时accept返回的套接口描述字
typedef std::map<SOCKET,CRunGate*> CRunGateMap;
class CRunGateCtrl
{
public:
CRunGateCtrl();
~CRunGateCtrl();
//开始侦听RunGate的TCP连接
void StartListen(int nListenPort);
//停止侦听,清空m_RunGateMap,释放所有相关资源
void StopListen();
//获得当前连接到Server上的RunGate数目
int GetRunGateNumbers();
// 函数说明 :当一个新的RunGate连接到Server时,就new一个CRunGate对象来管理该连
// 接,并插入到m_RunGateMap列表中.返回值不为NULL,表示创建新CRunGate
//            :对象成功,并已成功插入到列表中
//   :返回值为NULL, 表示创建CRunGate对象失败
// 参数说明 : hSockGate为accept返回的套接口描述字
CRunGate* AddRunGate(SOCKET hSockGate);
//把hSockGate对应的CRunGate对象从m_RunGateMap列表中删除
//返回值为true表示操作成功,否则表示失败
boolRemoveRunGate(SOCKET hSockGate);
//查找hSockGate对应的CRunGate对象,若在m_RunGateMap列表中存在,//返回指向它的指针,否则返回NULL
CRunGate *FindRunGate(SOCKET hSockGate);
private:
CRunGateMap m_RunGateMap; //GameServer对应的RunGate列表
};
CRunGate的功能和接口定义
类CRunGate在GameServer中负责管理一个具体的RunGate,当GameServer侦听到一个RunGate连接时,就创建一个CRunGate对象,由它负责管理这个具体的RunGate。其主要功能有:
 接收并保存RunGate发来的数据,从数据缓冲区中分解出一个个完整的数据包。
 解析RunGate发来的一个完整数据包
 维护该RunGate上的所有客户端(CPlayer对象)列表,当一个客户端开始和GameServer通信时,RunGate向GameServer发送上线通知,当GameServer收到该上线通知后就创建一个CPlayer对象表示该客户端,并插入到客户端列表中。当RunGate检测到一个客户端断开和自己的连接时,就向GameServer发下线通知。GameServer收到下线消息后,就从客户端列表中删除该CPlayer对象。
类CRunGate的定义如下:
//SOCKET为客户端连上RunGate上时,accept返回的套接口描述字
typedef std::map<SOCKET,CPlayer*>CPlayerMap;
class CRunGate
{
public:
CRunGate(CRunGateCtrl * pRunGateCtrl;);
~CRunGate();
//hSockClient为客户端连接到RunGate上时,RunGate返回的套接口描述字
//当成功时,返回表示该客户端的CPlayer对象,并成功插入到
//m_CPlayerMap列表中;否则返回NULL
CPlayer*AddClient(SOCKET hSockClient, std::string &strUserAccount);
//在m_CPlayerMap中查找hSockClient对应的客户端
//若成功,返回对应的CRunGate对象,否则返回NULL
bool RemoveClient(SOCKET hSockClient);
CPlayer* FindClient(SOCKET hSockClient);
//从数据缓冲区m_strRecvBuf分离出完整的数据包,然后调用解析函数进行//处理
//每当把接收的数据添加到数据缓冲区m_strRecvBuf中时,就立即调用该函//数从中分离出一个个完整的数据包,每当分离出一个完整的数据包后,
//就调用解析函数处理该完整数据包;
voidProcessData();
//解析RunGate发来的一个完整数据包
voidParsePkg(std::string &strPkg);
//处理RunGate发来的命令
voidProcessPkgFromRunGate(SOCKET hSockClient,
WORD wCmdType, constchar *pData,intnLen);
//当解析RunGate转发来的客户端的数据包时,就调用类CPacketParser的
//ProcessPacketFromClient函数进行处理
//向RunGate发送数据包
void SendPkgToRunGate( SOCKET hSockClient,
WORD wCmdType, constchar *pData,intnLen);
private:
CPlayerMap m_CPlayerMap; //RunGate对应的客户端列表
std::string   m_strRecvBuf; //存放RunGate发来的数据
CRunGateCtrl *m_pRunGateCtrl;
};
类CPacketParser 负责解析客户端发来的数据包
其定义如下:
classCPacketParser
{
//pData对应的客户端发来的原始数据包,nLen为该数据包的长度
static void ProcessPkgFromClient(CPlayer *pPlayer, const char *pData,intnLen);
};
CPlayer的功能和接口定义
类CPlayer表示一个具体的客户端,每当一个新的客户端上线时,GameServer就创建一个CPlayer对象表示该客户端。
class CPlayer
{
CPlayer();
CPlayer(const std::string &strUserAccount, SOCKEThSockClient,
CRunGate *pRunGate);
~CPlayer();
//向客户端发送数据
void SendData(const std::string &strPkg);
// 通过CRunGate的SendPkgToRunGate完成数据的发送
public:
std::string   m_strUserAccount; //该客户端对应的账号
SOCKET m_hSockClient;
CRunGate*m_pRunGate; //指向该客户端所属的RunGate
};
二、框架的实现
   在设计该框架的时候,有一个基本的原则,那就是框架和通信底层的具体实现相分离,通信底层的具体实现不要影响框架代码的修改。因为通信体层的具体实现有很多,不同平台的具体实现差别也很大。例如仅在Windwos平台下就有基于Windwows消息机制的、基于事件机制的、也有基于完成端口I/O模型的实现等等。采用相分离的原则,也有利于利用其它一些成熟的开发框架,比如ACE等。这样,当采用一种新的通信技术来实现通信底层时,框架本身就不用做任何修改了(或修改很少)。
   在框架的具体实现过程中,通信体层的实现作为一个基类。类CRunGateCtrl、CRunGate通过继承该基类来完成所有的通信功能。
ACE(ADAPTIVE Communication Environment,自适配网络通信环境)是高度可移植的开放源码主机基础设施中间件工具包,具有高度可移植性并且效率高,经过比较ACE Reactor框架和ACE Proactor 框架后,决定采用 ACE Proactor框架作为框架通信基类的默认实现。主要有以下两个原因:
1 ACE_Reactor在Windows平台下不能支持大量的TCP连接;
2 ACE_Proactor在Windwows平台下采用重叠I/O作为其底层实现机制,可高效的支持大量客户端连接。
3 但也有不足,但ACE_Reactor在Linux下还不能运行。因为Linux现在提供的高效I/O不支持Socket套接口描述字;
   在具体实现时,CRunGateCtrl以ACE_Asynch_Acceptor作为基类,CRunGate以ACE_Service_Handler为基类,借助ACE_Proactor框架就可以实现其全部通信功能。
三、框架的特色
该框架有以下几个特色:
1、 该框架本身是跨平台的。该框架仅使用了各种平台都支持的STL标准模板库(std::string,std::map),不涉及任何的操作系统系统调用;当您把该框架移植到其他平台上时,只需要修改相应的通信底层就可以实现该框架的移植。
2、 该开发框架是高效的。在整个框架中,为了效率起见,整个框架没有使用C++的虚函数机制。
3、 该框架是开放的,通信底层和框架本身是松耦合的,通信底层的实现没有任何限制,可以很容易使用其它框架的优秀成果,比如ACE的ACE_Proactor框架。
四 框架的应用
在实际运用在开发《三国豪侠传online》这款网络游戏中,后台游戏服务器都采用了该框架,实践证明,采用该框架可极大的提高开发效率,节省开发时间,
RunGate技术的广泛运用对服务器的执行效率也有非常显著的提高。实现了合理的负载平衡。

上海盛大网络发展有限公司 研发中心 尚海忠文《程序员》6月杂志网游专题刊登

wishel 发表于 2008-9-9 21:30:10

有些疑问。
没有虚函数能叫框架么?框架的核心就是IOC,一般用Template Method模式实现。
不用虚函数的理由更不成立:“为了为了效率起见”。。。
ACE的各种框架,都用到了虚函数。本文说用到了ACE的框架,然后强调自己没用虚函数:L
虚函数调用的那点性能损失其实基本忽略不计的,真正好的程序是架构清晰,然后在关键的瓶颈部分进行优化。而不是随时随地每一行都过敏甚至极致的考虑性能要求。
另外,有一些方法的命名不够清晰,如voidProcessData();含义很模糊,不能清晰的表达注释中所描述的功能。

以上个人意见,因为比较喜欢,所以多说了点。
页: [1]
查看完整版本: 支持RunGate的服务器框架实例