Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings
/ game_dev Public

基于skynet引擎的回合制游戏基本架构

Notifications You must be signed in to change notification settings

ifzz/game_dev

Folders and files

NameName
Last commit message
Last commit date

Latest commit

History

18 Commits

Repository files navigation

game_dev

基于skynet引擎的回合制游戏搭建


这里我们介绍的是一种单服的游戏服务器结构,即我们在一台物理机上只运行一个服务器进程,所有的游戏内容都运行在此进程中
影响服务器承载玩家上限的点主要有这么几点
1.机器性能:主要在机器的主频,内存,cpu缓存上
2.网络问题:带宽,IO读写
3.游戏代码结构设计:单进程单线程的设计,所有的事务都需要等待上一条事务执行完毕才能开始处理
4.游戏逻辑代码实现:游戏逻辑代码实现中需要注意编程语言的性能问题,以及一些玩法开发的耗时注意事项


基于skynet的开发,我们将我们的游戏结构设计成一个单进程多线程的结构,对于比较耗时的服务,如场景,战斗,
广播等一些功能我们可以另多开几条线程<lua虚拟机>去专门做这些处理,分散线程的压力。在此份代码中,可以看
到在service下回出现war/scene/broadcast 等目录;这就是针对上述说表的具体实现

1.游戏代码目录结构


game_dev -
| - shell/ --存放常用的shell指令,如启动、关闭服务器等
| - log/ --游戏日志输出
| - service/ --游戏逻辑玩法服务
| - config/ --游戏服务器基本配置信息
| - skynet/ --skynet引擎源码以及执行文件

2.skynet引擎搭建


a.检出一份skynet源代码,地址:https://github.com/cloudwu/skynet.git
b.注意:skynet版本我只检出至c91efa513435e71f24fd869e15ef409e0caf6c86,往后有大修改,部分代码无法支持
c.编译skynet代码,期间会遇到一些库缺失的问题,安装完后编译即可
编译完成后,在skynet下我们会看到一份skynet/skynet的执行文件,skynet的启动流程可以通过 追踪skynet-src/skynet_main.c进行了解,在这里不在做具体的阐述。游戏的启动脚本我把他放 在了shell/gs_run.sh脚本下,通过执行该脚本我们可以将游戏服务器运行起来,当然你得先配置 好config/gs_config.lua文件,通过配置我们可以知道最终的启动工具是通过snlua bootstrap启 动bootstrap,然后通过bootstrap启动gs_launcher,gs_launcher.lua去启动游戏逻辑中需要的 各项服务内容

3.游戏后台搭建


游戏后台在这套框架中称之为dictator; dictator主要实现的是后台指令,区别游戏中的gm 指令,dictator主要用来实现在线更新,是否开放玩家登陆,关闭存盘等相关指令;每个服务的启动 都会向该服务注册一份各服务的地址信息,dictator做为一个指令的发起方,将指令根据地址传送 到各个服务上;根据这个机制可以实现各个服务代码的在线更新,启动这个服务后,我们会监听 本机器上的dictator_port端口(在config/gs_config.lua中配置),通过nc localhost dictator_port 可以直连上后台,就可以输入相关的指令进行相关操作,如在线更新指令 update_code service/world/dictatorobj

与此同时,我们还启动了debug_console后台,用于监控各个服务的状态,相应代码位于 skynet/service/debug_console.lua下;

4.lua代码文件在线更新


参照云风博客:https://blog.codingnow.com/2008/03/hot_update.html
在这个系统中,载入一个文件我们使用了两种方式,一个是系统自带的require,另外一个是 自己实现的import;代码位于lualib/base/reload.lua下,这种划分相当于把文件按照能否在线更 新作为了标准;使用require的,我们默认认为此文件不能进行在线更新,使用import则是可以使用 lualib/base/reload.lua 下的reload文件进行在线更新
具体原理待阐述 在原生lua环境下,我们引用一个文件通常使用require,使用require后我们会把内容放置到package.loaded 下,如果我们要更新这个文件,首先我们会把package.loaded置控,然后重新require一次,但是这 种写法会使得一些地方无法更新到 如:local mod = require "mod"; 就算你重新require了一次, 这里的引用关系保持的还是旧的,除非我们把所有引用关系都遍历更新一次。而这个在线更新机制 在不解除旧有引用关系的前提下对内部数据进行了替换,详细实现参照代码: lualib/base/reload.lua

5.关于多服务数据共享(sharedata)


在skynet中有一个叫做sharedatad的全局服务, 代码路径:skynet/lualib/sharedata.lua 支持new|query|update 三种方法; sharedata.new(name, v, ...) 通过此方法可以创建一个新的sharedata数据块,v可以是字符串,table sharedata.query(name) 通过此方法可以获得已经创建的sharedata数据块,但是我们发现实际美一次 query都会去创建一次新的table,实际上我们可以在上层的使用方法上去规避他 sharedata.update(name, v) 可以将名字为name的sharedata使用v进行一次更新

同时,在我的设想中,这个框架对于sharedata的数据应用应该只停留在读取游戏中的配置数据,一般来 讲只能是导表数据,停留在只读层面上, 我们不会去也不应该去改写sharedata上的数据,有数据需要 需要变动的时候,我们通过在线调用update的方式去更新这些数据

由上述分析可以知道,我们要实现这写功能,需要有一个专门的服务去启动sharedatad和在线更新 sharedata 同时还需要有个文件去做数据加载的功能,需要对query做一层封装,规避重复创建table问题, 把sharedata设置为只读

6.关于数据存储


在这套框架中,我们的数据库采用mongodb;没有特别的原因,只是刚好我在使用这个数据库而已, 想要使用mysql也是可以的。在使用mongodb的时候要注意规避内存OOM的问题,因为mongodb引擎采用的 是内存换效率的策略,如果不加已限制,则会导致机器的内存被完全耗尽。

skyent 封装了一套mongodb的lua接口,位于skyent/lualib/mongo.lua;这个框架的所有存储实现都基于 这个模块去做的实现。

根据skyent的单进程多线程模型,我们对整个游戏启动了一个专门处理数据存储的服务,叫做gamedb 在gamedb中,我们实现了一套适用于游戏存储的一套方法,位于:service/gamedb/gamedb.lua下 其他服务如果有存储的需求,都会通过actor模型,将需要存储的数据发送到gamedb服务下,去做存储 有效的去分担主线程的压力

在这套回合制游戏框架中,我们的存盘策略采用的是相对来说比较简单的策略,目前的想法只涉及到 [定时存盘]:对于容错率相对来说比较高的数据块,我们采用打脏标记,5分钟一次的存盘策略 [及时存盘]:对于容错率低的数据块,则我们采用的是即时存盘,比如玩家的ID分配等一些相关内容 [关联存盘]:对于数据关联性比较大的多个块,我们采用关联存盘,就算回档也会回到同一时间段, 比如玩家之间的交易行为

7.跨服务数据交互


在[5]中我们提到了要对共享数据进行在线更新,而共享数据也是单独的一个服务,我们怎么做到 在dictator后台对sharedatad服务的数据进行更新?在[6]中我们也提到了gamedb用于存储逻辑服务产生 的存盘数据,但是我们怎么将存盘块数据发送到gamedb中进行存储,需要的使用我们又怎么从gamedb中 获取到相关数据。这就是我们需要做跨服务数据交互通讯的理由。

在skynet中,我们每启动一个服务都会有个专门的标识,在log/gs.log中我们可以看到一串的十六进制 字符,这个其实就是每个服务的一个标识符,也可以说是每个服务的地址,我们通过这个地址在整个进 程中找到这个服务,与此同时我们在每启动一个服务的时候也会向skynet注册一个服务的别名。如在启 动gamedb的时候,我们有skynet.register(".gamedb"),只要注册了这个别名,我们也可以通过该别名 去访问相应的服务线程

在interactive中我们定义了另外通信协议PTYPE_LOGIC,专门用于游戏逻辑服务间的通讯,在每个服务 启动的时候我们需要调用interactive.dispatch_logic进行一次初始化

具体的代码实现在lualib/base/interactive.lua下。

8.客户端服务端协议交互(protobuf)


CS之间的通讯我们引入protobuf,协议的解析我们采用云风写的pbc 首先我们需要在机器上安装一个protobuf,我采用的是直接检出git安装的,git地址: git clone https://github.com/google/protobuf.git 在运行./autogen.sh 之前,我们还需要安装一些必要的内容,有unzip,autoconf,automake,libtool
1.执行 ./autogen.sh
2.执行 ./configure 执行过程中可能会遇到一些问题,通常是库缺失,自行安装即可
3.make
4.make check
5.make install


编译pbc的时候采用了一种比较取巧的方式,直接将pbc:https://github.com/cloudwu/pbc.git 下中我们所需要的文件放入到skynet下的pbc文件夹下,有makefile,src,pbc.h,将pbc/binding下 的pbc-lua53放到pbc下,然后通过改写skynet/Makefile文件直接进行编译导入,最终,我们的使用方 式和pbc/binding下介绍的一致,只是我们将protobuff.lua文件放到了game_dev/lualib/base下,这样 我们就可以直接require "base.protobuf" 使用它

About

基于skynet引擎的回合制游戏基本架构

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

AltStyle によって変換されたページ (->オリジナル) /