在开发过程中经常需要修改游戏服务器的各种配置参数,以方便在冒烟环境,开发环境和预发布环境中进行调试。我们以前使用ini文件进行服务器配置,所以经常需要关掉服务器,修改ini文件,然后再重新把服务器拉起来。最近有点时间,就开始考虑做个服务器启动小工具,能方便服务器在不同环境下的部署。

 

开始想用QT来写这个小工具,但发现建立一个QT工程其实有点小麻烦,后来干脆使用PythonQT来做。一个100多行的小脚本就能满足需求,以后交给其他人扩展功能也很方便。

 

主要是想做两个功能

1:可以方便的对服务器在不同环境下进行部署

2:可以在服务器启动时设置系统时间,因为经常需要测试一些时间性的业务。比如元旦节的活动。以前都是通过改操作系统时间来测的,这样每台机器就只能启动一个服务器了,如果想测两个不同时间的业务,就要多找一台机器。

 

                                                    image

 

下面是代码,可以很简单的修改成自己需要的工具


上面的小工具在启动服务器时是阻塞式的,一旦服务器启动,小工具就会处于假死阶段,直到关闭服务器。

可以使用Thread库,建立一个线程来启动服务器即可。

 

最后使用py2exe ,把上述脚本编译成EXE后就可以在团队内部发布了。热烈的笑脸

posted @ 2012-01-03 18:54 BadKeeper 阅读(31) 评论(0)  编辑

其实在一个MMORPG的项目中,经常会遇到这种问题,服务器作为主控方,掌握了绝大部分数据的修改权利,通常对于不同类型的数据包,需要专门定义协议,然后在数据改变的时候同步给客户端(在《武林至尊》中,我们把这种数据主要分为资产和FOV数据两种)。在客户端的逻辑模块,根据服务器发过来的消息包修改本地数据。而客户端的其他模块(UI,渲染)需要定期扫描客户端逻辑模块的数据变化(也可以由逻辑模块主动通知),然后再将部分数据转换为自己模块内部的本地数据(脚本数据,渲染数据)。

                                                               Image

服务器端掌管着数千个客户端需要得到的数据集,如果在上图中,把客户端本身也看出一个控制器,我们可以想象这样一种抽象结构,主要的数据都集中在服务器端,上千个客户端就像控制器一样联接在数据中心上,通过网络来存取数据。在客户端本地,它自己又是一个比较小的数据中心,它的每个功能模块连接在数据中心上,通过函数调用来存取数据。

在一个C/S结构的项目中,数据的变化应该主要包含几种方式:

  1. 数据在A端被修改,B端需要知道这种变化
  2. 数据在A端被修改,B端不需要知道这种变化
  3. 数据在A端和B端同时被修改(不是严格意义上的同时)。双方互不关心变化
  4. 数据在A端和B端同时被修改(不是严格意义上的同时)。以其中一端为主,另外一端需要通过各种机制进行数据纠正。
  5. 数据在A端,B端定期或事件型触发对数据的请求。

无论是哪一种情况,数据都可以归纳为3种类型。

  1. 数据有原稿和拷贝,原稿的拥有者可以修改和读取最终的数据结果,拷贝方的拥有者只能读取结果,无法修改结果。
  2. 数据有两份,分别由拥有者读取和修改,双方之间在一定时间内互不影响。
  3. 数据只有一份,拥有者都可以进行修改和读取。
其中2,3情况在客户端设计时经常会遇到,1情况则在服务器设计时大量出现。

让我们来考虑一种新的设计模式 - 包含了网络端的数据集中式。

首先要定义数据层的定位和功能

数据层能够定义数据,并且定义数据的类型,是原稿还是拷贝。对所有数据,数据层都应该提供数据在最小粒度上的置脏功能,但对于数据的划分粒度,则应该根据业务逻辑来决定。例如背包数据,可以认为所有数据是一个完整整体(最终会导致每次背包的数据变化是全量更新),也可以认为一个背包格的数据是一个整体(增量更新)。粒度的划分难以建立统一的标准,通常要考虑到数据量的大小和数据本身的各种特性。

      数据层应该为数据的最小粒度单元提供读写功能,当然也应该为最小单元之上的整合单元提供一些辅助型的读写功能,否则最终代码的实现上会非常繁琐。

      数据脏标志的写功能,不应该暴露到数据层之外,避免在上层应用上无意间调用。脏标准的读取功能是否暴露可以根据实际需求来决定。如果认为所有数据的变化都应该由数据层主动通知给其他模块的话,就无需暴露。如果有由外围模块扫描数据的情况,则需要暴露。

     如果要保留脏标准的读取功能,则应该暴露数据变化的次数。之所以要这样要求,是因为无法保证只有一个模块会关心一个对应的数据,例如A B模块同时关心一个数据,那么数据只要一脏,A B就无法停止更新了,A在读取数据时不能将数据改为不脏,B也同样不能这样做。

     对于数据层来说,最重要的功能应该是如何管理数据的原稿和拷贝。考虑到原稿和拷贝通常在不同的网络终端上,数据层应该提供从原稿方将数据变化同步到拷贝方的功能。这种同步和解析的执行应该是自动和透明的,无需上层关心。例如可以将不同的数据设置不同的标志,数据层定期扫描原稿方数据的变化标准,如果发现有变化的数据,就将这些数据统一压到协议中,然后一次性同步给拷贝方。

     细化上述设计的话,主要有两点需要扩展。

首先数据的同步应该分为不同的频道,每个频道有流量控制和频率控制,以免数据的变化过快时导致模型的崩溃。每个数据在定义时应该指定所属的频道,一些控制型的数据可以注册到高频率的频道中(减小网络延时导致的体验感问题),一些数据量大的资产数据,可以注册到低频率的频道中。

其次数据的同步流应该一种自解释功能,或者说应该提供一个通用协议,可以把任意数量的,不同类型的数据按任意顺序组织起来,一次型同步给数据的拷贝方。类似于一个文件流的概念,先有一个文件头,后面是文件的具体内容。这样可以让数据层在每次扫描时,只对一个拷贝方放送少量的网络包,避免出现大量小包,导致TCP/IP头占用大量流量的问题。

 

--未完待续

posted @ 2011-02-04 10:56 BadKeeper 阅读(107) 评论(0)  编辑

个人还是偏爱在设计初第一步使用模块层次图来描述一个工程的基本结构。因为层次图可以在较浅的层面上对整个工程进行全面考虑,比较容易在一开始就确定细化设计的重点,并且通常层次图也可以对整体工程的子工程划分起到启发作用。但模块层次图无法具体体现出单层中各个模块之间的交互和依赖关系,也不能提供程序代码的层次划分关系,不能单独使用层次图对开发进行指导。

工程层次结构如图

image(2)

图中的分割线表示多平台支持的分界线,通常分割线所在层次越高,开发难度就越高,分割线可以选择在网络协议层之上,或者在业务逻辑层之上不再支持跨平台实现。

具体情况要根据需要多平台支持何种业务来定。例如客户端在Windows平台上,服务器在Linux平台上,可以选择业务逻辑层之上不再支持

多平台实现,如果客户端在Windows平台上,逻辑服务器在Windows平台上,后端服务器在Linux平台上,也可以选择在网络协议层之上就不再支持

多平台实现。

跨平台支持层:提供跨平台的类型定义和函数定义,例如对int64,int32等的定义,以及对sprintf等函数的定义。

                          需要注意的是,由于该层存在的主要目的是使上层开发中,无需关心所面向的平台,因此在其之上的

                         各层开发中,需要调用某些平台的特有API时,应该将该API定义在《跨平台支持层》中,并且对该API

                         提供其他平台实现方案。该法则要保持到上图的分割线处。

实用工具层:提供内存监管,日志,时钟,Profile等,无需在这层提供多线程工具等平台差异较大的功能。

网络协议层:提供所有网络协议的定义,压缩,存储,生成,解析等功能

业务逻辑层:提供所有业务逻辑,并提供对网络协议的存取接口。

PS:其实网络协议层和业务逻辑层之间的关系比较难以确定。首先网络协议层在定义协议时是否需要使用到逻辑层中的各种定义和枚举。如果是,则协议层依赖于逻辑层。其次是逻辑层是否应该定义如何存取协议的接口,如果是,逻辑层依赖于协议层。而在部分项目中,逻辑层本身不是跨平台的,如果协议层依赖于逻辑层,则协议层本身无法跨平台。最后我选择了以下方式:协议层在下,逻辑层在上。协议层中不要出现任何逻辑层中的定义和枚举,应该保持其自身的独立性。在协议定义时,可以选择使用int替代枚举,选择比逻辑层长的,通用的字符串长度替代逻辑层中的某种字符串长度。协议层本身应该只关注如何进行内存转储并在网络上传输,无需关心其内容的实际含义,也不需要对其内容的逻辑合法性进行检查。

引擎层:该层提供渲染,音效,网络等具体功能的封装和实现。无需考虑跨平台需求,只需对每种平台实际需要用到的功能单独提供模块即可。

应用层:该层实现客户端,服务器,编辑器和其他工具,无需考虑跨平台。但应该尽可能考虑在不同应用程序中,复用下层模块。

posted @ 2011-01-23 14:23 BadKeeper 阅读(118) 评论(0)  编辑
摘要: 通过对一种设计模式的讨论,思考MMORPG中如何简化C/S端间的数据同步,简化业务逻辑的变化对工程结构的影响阅读全文
posted @ 2011-01-23 13:13 BadKeeper 阅读(64) 评论(0)  编辑

记录时间:2009年12月14日

更依赖于事件的技能设计方案

如果我们将技能事件的定义和功能扩展,会发现其实远程技能,近程技能的共通点。前期设计中,我们只考虑了事件的瞬时性,换句话说,我们认为只有瞬时发生,并只发生一次的逻辑才属于技能事件。如果我们在事件上加入时间,变化性等特点,可以惊喜的发现原来近程/远程技能的逻辑是如此统一。

646-41266476fb746ebe

我们可以尝试用新的事件结构来描述近程和远程技能

646-41266477052de3ef 

646-412664770687c65a

就像上面所描绘的一样,近程技能和远程技能在设计上达成了统一。近程/远程技能在技能控制,时序更新,事件触发和结果派发的各个关键点上的逻辑达成了一致,它们之间的差异性被转移到事件本身中。采用这种设计,技能的实现并没有带来多余的效率损耗。

当然上面的设计也带来了其它层面上的设计变动,而且这种变动会深刻的影响与技能系统相关模块的设计。就是事件本身在终端间的同步。新的设计将事件本身设计为一个有时序的逻辑个体,就像对前期设计的剖析一样,一段有时序的逻辑,必然需要在时序的多个关键点上进行网络同步,因为我们不仅关心它的最终结果,也同样关心它的变化。

等一下再来考虑同步的问题,我们先集中精力设计一下技能事件的处理流程。

技能事件本身有可能会产生新的事件(考虑一次攻击可能会随机的带来DEBUFF效果)。当然我们也可以继续扩展事件本身,让它把所有可能产生的新逻辑都囊括到自己本身中,这样就不会有新事件产生了。但这种选择会导致技能事件本身变得过于庞大和脆弱,因为新的事件类型可能会在设计后期被发现,这时候需要重新回来扩展事件的结构,并且这种修改是线性的,一个新改动可能会影响所有已经定义好的设计。

最后我们还是选择了让事件本身保持简洁和概念一致,攻击事件   恢复事件   BUFF事件   等等等等,新的功能由新的事件来完成,旧的事件和处理方式不受到后期需求变动的影响。

就像前面剖析的一样,事件的结果对于双方是唯一一致的。事件也许会产生新的事件,但不要影响现在事件的处理。在前期设计中,我们产用事件产生时立即发放给双方的方式,这里的问题有三个

1.事件的处理不一致,这在前面已经说明了

2.可能会导致事件处理死循环,如果一个事件产生新事件,新事件再产生新事件。。。。。。。。。

3.只能处理没有生命期的事件

立即派发的方式不可取,我们可以选择采用LazySend的方式。在新的设计中,所有事件都在产生时被抛送到一个全局的事件管理器中,等待管理器去定期的更新和处理。当然这样事件的产生和处理就不在同一个时间片中执行了,但考虑到网络延时和事件管理器的更新频率,这个问题也没什么关系。

posted @ 2009-12-14 20:54 BadKeeper 阅读(245) 评论(0)  编辑