最近项目的开发工作较少,因此有时间能捣鼓自己的东西。于是花了大概两个星期的时间,粗略的搭起了一个游戏服务器的框架。
对我而言重复造此*的意义有:
(1)在经历过一个上线游戏项目的洗礼之后,作为对这一年的开发工作、技术学习的一个总结,将自己这一年来所学所得所思所想,通过代码表达出来。
(2)作为一个对工作中不会接触到的一些新工具的使用的探索。项目组使用的技术已经是经过了时间的检验,并且趋于稳定。但是作为一个程序员,探索技术、保持学习能力乃是天职亦是乐趣。
在我看来,在实现一个游戏服务器之前,有必要先对其架构做一个分析。规划好哪些工作交给引擎、哪些工作交给脚本实现。由于我目标是实现一个放置类的数值游戏(类似于战舰少女R),先可以粗略的认为没有很高的性能指标。于是,我可以大力简化我的服务器代码,在引擎里只做核心且必要的工作,其它的包括游戏玩法逻辑统统交给脚本层实现。保证引擎代码的精简、干净是十分有益的。
总的来说,我认为有必要放在引擎里的功能有以下这些:
(1)负责收发消息,解包封包的网络功能。
(2)内嵌Python虚拟机的脚本功能。
(3)开放给脚本层的一系列注册事件触发的功能。比如消息事件触发、逻辑事件触发、超时事件触发、定时事件触发等。
其它的功能,包括连接管理、角色管理、战斗等功能全部由脚本实现。
通过上述的简要分析,决定采用有常见的单进程多线程模型,服务器进程一共有3个线程,分别是:
(1)网络线程(socket server)。使用libevent网络库实现,其工作是将从网络收到的消息包传递给逻辑线程、将逻辑线程投递来的写请求转换成网络消息包并发送出去。
(2)逻辑线程(game server)。这部分包括了内嵌python虚拟机、消息事件触发模块(msg)、逻辑事件触发模块(event)、超时事件触发模块(timeout)、定时事件触发模块(datetime)。
(3)日志线程(logger)。由于需要同步写日志、并且日志模块逻辑较为独立,因此单独独立出一个线程。
线程之间通信也是一个需要考虑的问题。而我的做法是,所有线程之间的通信都是基于消息的传递的:网络线程收完一个完整的消息包之后要通知逻辑线程,则将网络消息包的内容打包投递到逻辑线程的消息队列里。逻辑线程、网络线程需要打log则投递一个请求到logger线程的消息队列。这里说是投递消息,其实实现上是投递一个指向消息的指针。对消息的内存的管理方式是使用人为的约定,定义好消息指针的所有权,而非使用智能指针。规定由消息的的产生方负责申请消息所使用的内存,并将其指针传递给处理方。消息处理方在处理完一条消息之后,有责任要负责回收这块消息的内存。
逻辑线程请求发网络消息这个功能在实现上则稍有不同,不能做成简单的向消息队列投递消息。因为网络线程的事件循环是交给libevent控制的,因此不能实现成等待从队列里取消息。暂且做成读写一个本地互联的socket(类似于self pipe trick的变种),我称之为control socket。因此网络线程(socket server)管理两种不同的socket,分别是data socket和control socket。data socket是服务器与客户端连接的socket,control socket则是用于服务器内部线程通信的socket,负责传递逻辑线程对网络线程的请求,包括逻辑线程的请求发包、请求中断连接、请求主动连接等。
以上便是这个游戏服务器引擎实现的大体描述。今后会继续写相关博客记录后续相关模块的开发过程、遇到的问题、踩过的坑等。先写下此文以示决心:)