LoginServer <-----> GameServer
服务端主体分为LoginServer和GameServer, LoginServer做帐户认证, GameServer做游戏主逻辑,
中间也可以加一个CharServer啦, 做人物管理, 新建删除人物之类的, 也可以并到GameServer一
起, LoginServer和CharServer都比较简单, 略过.
通过LoginServer的验证后将分配给Client一个SessionID, 然后与GameServer或CharServer的通信,
都以此SessionID为认证码. Client只有发送正确的SessionID才能与GameServer建立连接.
2.GameServer层次结构
GameServer分为三层, 网络层<--->逻辑处理层<--->数据库层
每层都有一个消息处理队列, 存放待处理的消息. 消息队列可以采用先进先出队列的方式, 也可以
采用堆或者优先队列的方式, 按优先级对待处理消息进行简单的排序, 嘿嘿, 是不是有点类似QoS
的思想.
每层采用线程池技术, 预先建立一定数量的空闲线程, 不够时建立新线程, 过多时则销毁线程,
保证线程池中有指定数量的空闲线程(Min/Max), 主线程不断检查处理队列是否有待处理消息, 若
有则从线程池中分配一空闲线程处理之.
偶在Linux下线程池是用pthread_cond_wait和pthread_cond_signal实现的.
2.1.网络层
本层根据操作系统不同可以有多种实现, 主要功能是与客户端建立TCP连接, 将TCP流分割成一个个封包,
如果有加密就解密, 如果有压缩就解压缩, 加入事务层的处理队列, 同时把处理队列中待发送的消息发
送出去, 如果要加密就加密, 如果要压缩就压缩.
Windows下采用IOCP模型, Unix-like系统下可采用select/poll(epoll)/kqueue
偶在偶的服务端中采用了select方式, Linux单个端口的连接数有限制, 所以偶开了多个线程监听一组端口,
由LoginServer做负载均衡, 从而保证不会出现某个端口连接数过多的情况. 每当有新客户端要登录时,
LoginServer判断每个端口的连接数量, 选最小发送给客户端. 偶想这里也可以做成动态方式,
当每个端口平均连接数超过XXXX时, 就开新线程监听新端口, 并通知LoginServer.
2.2.逻辑处理层
本层是GameServer的核心.
根据操作码(OPcode)把消息分配到每个子模块里面处理. 最简单的方法就是用从0开始的连续的OPcode, 建立
一个与Opcode对应的处理函数的数组, Opcode作为数组的下标, 这样只需要O(1)的时间就可以调用到所需的函数
连Hash都省了, 又简单又高效.
子模块详见第7部分
2.3.数据库层
本层用于数据存储, 本质上就是把内存里的数据存到硬盘上, 要是你够拽的话, 可以不用现有的数据库,
自己写算法存储文本文件, 但为了方便起见, 也为了提高效率, 还是用数据库比较好.
windows下用MSSQL, 或者用MYSQL,
Unix-like系统下可以用的就多了, 能多兼容几种数据库最好
MYSQL的性能优异, 功能上稍差一点, 如果不需要用到存储过程的话, MYSQL还是首选的.
数据库层一般用单线程已经足够, 可以不需要做对象互斥, 编程方面也会简单一点. 但是需要注意的是,
数据库操作方面一定要用Transaction, 可以有效防止复制现象发生, 比如:交易操作一旦发生错误, 则
rollback到交易之前, 不会发生钱已交出, 东西却没拿到的情况.
3.消息格式定义
3.1.网络层<-->逻辑层消息格式(网络封包格式)
3.2.逻辑层<-->数据库层消息格式
4.游戏对象定义(Object)
object
|-------> item
| |----> container(容器类对象,如仓库、背包等)
|
|-------> unit
| |-----> player
| |-----> monster
| |-----> npc
| |-----> corpse(尸体对象)
|
|-------> gameobj
|-----> dynamicobj(如技能产生的临时对象)
5.地图场景管理
6.脚本系统
7.逻辑层模块化设计
对于地图场景管理打算采用这种方式
在服务器上把场景划分为小区域(视野大小)。每个区域对应一个list,场景中的所有对象按他们的位置加入到对应区域的list中,那么每次行走只需要把消息发送给几个相临区域的Player
一个建议:
可以把GameServer的网络层剥离出来做成一个应用级网关(Application-level Gateway),因为这部分虽然逻辑简单但消耗资源确是比较大的。做成单独的可以使你的系统伸缩性更好,GameServer和Gate可以是1:N的
对于一个分布式服务器架构来说要考虑的问题就很多:
首先要有一套设计良好的应用服务器框架和通讯中间件,应用服务器是上层应用的重要基础设施,用来实现诸如服务定位、名字注册、负载平衡、服务集群、故障重起、时间服务、LOG服务等等功能,通讯中间件也是不能少的,如果服务器之间通讯用手工写socket通讯的话,那很快你就会被协议处理、各种微妙的时序、同步等问题所困扰。可以说,你的服务器架构能实现到多复杂能堆多高,关键就看这两样实现的有多坚固耐用。
其次对象状态的序列化很重要,这关系到对象同步、对象persistent、状态迁移、故障恢复等等诸多问题。问题远不是把对象状态写到一个流里就完事那么简单的,做好这一块那就等于解决了一半的服务器逻辑问题了。
然后再谈谈分布的问题,基本上有两种思路,一种是按逻辑功能划分计算资源,一种是按容量划分计算资源。按逻辑功能划分是比较容易想到一种方式,简单的说就是将不同的功能模块实现到不同的进程里,这种方案看似不错,实则问题多多。首先是各模块之间必然要进行交互,这样就会引起很多细粒度的远过程通讯,一来造成性能损失二来增加了实现难度,其次是各模块负载并不均衡,很容易产生性能瓶颈,也很难实现负载平衡,另外这样的服务器程序调式起来将是一场噩梦。当然,凡事都没有绝对,一切设计都要根据实际情况来决定,有时候该按功能划分的还是要按功能划分,比如小高建议的划分一个应用级网关出来在某些情况下就很有效。现在已有的一些我们所能见到的超大无缝连接世界的分布式实现,其基本的思想是将世界划分成一系列相连的块,为每个块分配计算资源来实现并行分布运算。关键的问题在于处理相临块边界之间的同步问题,如果处理不好就可能造成同步失调、物品复制等等BUG,这需要一个设计良好的同步算法。关于计算资源的分配我们也 可以有两种选择,一种是使用性能强劲的多CPU服务器,在单进程里用多线程并行计算,即所谓的SMP架构,这样的优点是简单、高效,缺点是成本高,伸缩性不高。另一个就是服务器集群架构了,这样实现起来更复杂、而且服务器的瓶颈大多出在I/O而非计算上,使用集群以后增大了服务器之间的通讯量,设计不好反而造成性能损失,不过优点也很明显,降低成本,提高伸缩性,通过良好的设计还能实现故障恢复(也就是集群中任何一台服务器当机都不会影响游戏的运行)。
最后再谈谈游戏逻辑的处理,一种设计是让服务器处于被动状态,只有客户请求到达再进行逻辑处理,刷新对象状态,这样可以降低一些计算量,但是因为服务器不进行对象状态的模拟,缺乏一些必要的信息,会限制一些功能(特别是真三维世界运动)的实现。我现在更倾向于传统游戏的做法,让服务器运行一个游戏逻辑循环,定时的模拟对象状态。