项目源码托管于:https://git.oschina.net/dreamyouxi/MMOGame

1. 网络库的选择

从零开始用原始网络 API 编写网络层会耗费大量精力,选择一个成熟的网络库是更合理的方案。

可选的底层网络库很多,常见的有 asio、libevent、ace 等,它们分别对应两种高性能 I/O 设计模式:

  • Proactor 的典型实现:IOCP、asio
  • Reactor 的典型实现:EPOLL、libevent

相比之下,asio 的 Proactor 模型更受青睐,框架设计更简单——每次网络事件触发时数据已经接收完毕,只需专注于业务逻辑处理。

不过,asio 在 Linux 下运行时效率会有所折扣。考虑到期望在 Windows 下开发、Linux 下运维的场景,若要充分压榨服务器性能,跨平台方案很难把网络库固定为单一选择。

无论采用哪种 I/O 方式,网络消息的分发都可以由一个中间件统一负责(可以是 master,也可以是 mgr)。这样切换底层网络库时,上层服务器逻辑无需改动。kbe 同样采用了这种思路,它支持 epoll、select 等多种 I/O 机制,所有网络事件通过统一的 dispatcher 进行分发。

因此,设计上可以将网络 I/O 层独立出来,代码结构大致如下:

若需切换到其他 I/O 实现,只需增加一个适配层即可。以下是 asio 的适配示例:

2. 服务器并发模型

常见的并发基本模型有三种:多进程单线程、多进程多线程、单进程多线程(这里的"线程"特指执行游戏逻辑的线程)。

多进程单线程模型类似于 kbe 的架构:每个 cell 或 base 只有一个游戏逻辑线程,大幅降低了开发难度,效率也相对较高。例如将一个 cell 用作场景服务器,多个 cell 协同工作,玩家跨场景时在 cell 之间切换。

在这种模型中,数据迁移的速度至关重要,也是实现无缝跨地图的核心因素。kbe 这类 bigworld 架构的数据迁移主要依赖 socket、共享内存、Redis 等手段,但速度都有一定瓶颈——能用内存的场景尽量用内存。

针对上述不足,另一种方案是将 cell 和 base 直接放入同一个进程,数据迁移就简单得多:加锁后转移数据指针所有权即可。此时 cell 相当于进程内的线程,cellMgr 作为主线程负责维护和调度所有 cell,进程间通信的 socket 也就替换为了直接的内存交互。

这种方案的缺点是将所有 cell 和 cellMgr 限制在同一台物理机上,单机并发量受到限制;一个 cell 崩溃若处理不当,可能导致整个进程崩溃,影响整个系统的稳定性。此外,局域网 socket 通信延迟本就可以控制在 1ms 以内,节省的数据迁移开销其实有限。如果客户端仍然需要等待加载画面,这种优化的收益就更加微薄了(PS:反正都要读条)。

TODO