总结我所了解的网络游戏知识4

quanwuling 2008-05-09 03:46:30
3.服务器程序框架 因为技术所限,我所做过的游戏的逻辑都是在一个线程内完成的(数据装载或者网络通信当然是多线程的),也就是只有一个循环体。循环体其实也就做几件事,处理输入、响应时间流逝,输出结果。输入可以是来自网络消息,也或者是数据库查询返回,也或者是命令行指令等等;响应时间流逝也就是每个一定的时间,做些跟时间相关的事情;所谓输出运算结果,可以是发送网络消息,也或者是保存数据,也可能只是简单的往log中输出一段文字等等。游戏中的所有事情基本上都可以归入这几件事。 如下的类是我常用的循环体的简短直观描述。 class Circulator { public: //初始化 virtual int Initialize(); //释放 virtual void Release(); //主循环体 virtual void Run(); //响应网络封包的输入 virtual OnPacket(PACKET* pPacket); //响应时间的流逝 virtual OnUpdate(unsigned int nElapsed); //发送封包的函数,用来输出结果 virtual SendPacket(PACKET* pPacket); } Initialize()和Release()基本上不用说,只在启动和结束的时候执行一次,用来做一些预备和资源释放的工作。 Run()就是循环体的入口,里面基本上是如下一个样子 void Circulator::Run() { while(true) { //从网络层获取封包 NetworkWrapper::GetPacket(); //处理之 OnPacket(); if( time_elapsed >= HeartBeat_Interval ) { //响应时间流逝 OnUpdate(time_elapsed); } } } 接下来说说OnPacket(),这个函数处理所有封包,当然不是做所有的事情,这里仿佛是闸门,闸门后面不可能直接是要灌溉的农田,而是一排整理的渠 道,将出闸的水分流到各个渠道,最终缓缓流入农田。要是闸门后面直接是农田,那么结果就不是灌溉,而是泄洪了。程序也是一样,如果这里直接针对每个网络 消息作处理,这个函数将无比庞大而凌乱,逻辑无法清晰的分开,这对大型的游戏项目来说是灾难性的。 啰嗦了这么多,其实只是想说明OnPacket()只是负责分流工作,将不同的消息交与相关的模块去处理。 其内容大致如下: void Circulator::OnPacket(PACKET* pPacket) { switch(pPacket->GetPacketType()) { case PACKET_TYPE_Login: LoginModule.OnPacket(pPacket); break; case PACKET_TYPE_Map: MapModule.OnPacket(pPacket); break; case PACKET_TYPE_Mail: MailModule.OnPacket(pPacket); break; ... } } 这里用的是简单的switch-case,也有人用Command模式,也或者直接把处理函数绑定在Packet中,各有利弊,不一一详述。 既然上面提到了LoginModule和MapModule以及MailModule等,再来说说另外一个比较重要的东西:逻辑模块。我喜欢把一些相关 的逻辑处理组织在一起做成一个个模块,既高效的完成功能,又尽量相互独立,降低耦合。下面给出一个模块的基类的简单描述来勾勒其工作的方式。 class ModuleBase { public: virtual void OnPacket(PACKET* pPacket); virtual void OnUpdate(unsigned int nElapsed); void RaiseEvent(EVENT* pEvent); void OnEvent(EVENT* pEvent); } 前面两个函数不用细说,一看就知道,就是前面提到的闸门后面的渠道,用来处理分流进来的各种输入。后面一个函数RaiseEvent(),其作用是什么 呢。故名思义,发起事件。我们知道,如果每个模块都能这么简单的包装完全相互独立,那我们干嘛还要用单线程,给每个模块一个线程不是能很大程度上提高处 理能力么。事实是各模块之间不但要公用玩家信息、地图信息、公会信息等等数据,还要相互之间关联,不可能完全独立。但是我也不希望他们之间相互直接引 用,那样会造成头文件包含灾难和逻辑混乱。为此做了EVENT,来提供一种方式让他们可以相互沟通,但不至于耦合。举个例子,玩家登录我们假定是 LoginModule来处理的,但是公会模块也关心玩家上线的事情,可能要为这个玩家准备相关的数据。我们可以直接在LoginModule的 OnLoginOK()里面直接调用GuildModule.OnPlayerLogin(),那如果邮件模块也关心这个事情,那么我们还得加入 MailModule.OnPlayerLogin(),这就是我前面提到的强耦合的方式。用EVENT的方式是在OnLoginOK() 中,RaiseEvent(EVENT_Login),这个函数做什么呢,是把这个事件交给Circulator,让它来处理,Circulator的 处理也很简单,就是根据自己记录的哪些模块关心此事件,就调用它的OnEvent方法来让其有机会响应。这里用到了一个简单的解耦合的方法,也是编程中 常用的方法,解除了各个Module之间的耦合性,各模块只和Circulator之间有耦合,而Circulator是唯一的循环体,和它的耦合是不 可避免的。当然我们可以用同样的注册Packet类型和逻辑模块的方式来降低Circulator和各具体模块之间的耦合,让Circulator只和 ModuleBase之间发生耦合(当然也有个代价问题值得考虑,因为网络封包相当的多,即OnPacket函数的调用相当的频繁,其分流效率一定要 高。很明显switch-case不是一个最高效的方式,但是用来说明服务器程序组织架构很适合:)。 说完了OnPacket,其实OnUpdate也不需要再说了,因为他们的分流方式是一样的,只不过最终的具体模块中他们做的事情不一样而以。如果用抽象的眼光来看,时间流逝也可以看作是一种输入,它和网络封包这类的输入并没有本质的不同,各模块的功能也都是接受一定的输入,计算之,产生输出。其实输 出也可以把他们统一管理起来。如果输出被其他的方式接管,那么模块的功能就可以归纳为一个词--计算服务。各模块所做的事情就像是一架香肠机,在入口丢进去一头猪,在出口整齐的摆出香肠。:)和香肠机有所区别的是,模块提供计算服务的时候需要有些环境数据,比如玩家信息、地图信息等。也可以说功能模块需要数据服务。那正好,我们做一些模块来完成数据服务,不同的数据服务有不同的提供者。比如玩家数据我们可以用一个PlayerMgr来管理并且提供个各功能模块,地图数据我们可以用MapMgr来完成。 对于PlayerMgr可以是一个player list,也可以是一个map,当然只要能完成所需要的功能,用什么方法是不要紧的,面向对象的方法就是封装实现细节。我前面有篇文章提到一个小技巧来高效的组织网游服务器中的数据,文章题目是《小技巧,大作用》。 提到数据服务,还有一种数据服务也要说说,那就是数据存储服务。

...全文
101 1 打赏 收藏 转发到动态 举报
写回复
用AI写文章
1 条回复
切换为时间正序
请发表友善的回复…
发表回复
wincar 2008-05-13
  • 打赏
  • 举报
回复
顶,楼主请继续!

8,303

社区成员

发帖
与我相关
我的任务
社区描述
游戏开发相关内容讨论专区
社区管理员
  • 游戏开发
  • 呆呆敲代码的小Y
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告
暂无公告

试试用AI创作助手写篇文章吧