百万在线:大型游戏服务端开发
上QQ阅读APP看书,第一时间看更新

3.3 搭架子:目录结构和配置

开始编码吧!既然这是个“大项目”,就要有大项目的样子,就要有所规划,下面先把项目的架子搭起来。

3.3.1 目录结构

建立如图3-5所示的目录结构,各个文件(夹)的作用如表3-3所示。建议把Skynet框架放到一个文件夹里,把所有自己编写的内容都放到外层的文件夹里。笔者见过的不少实际项目都是使用的类似结构。

表3-3 各个文件(夹)的作用

service文件夹用于存放各种服务的代码,如图3-6所示。每个服务的代码都放到一个以服务名称命名的文件夹里。按照3.2.1节的设计,服务端会开启gateway、login、agent等多种服务,我们光给每个服务建立对应的文件夹。主服务是节点启动后第一个被加载的服务,用于启动其他各个服务,它比较特殊,我们不给它创建对应的文件夹,而是为它创建一个Lua文件——main.lua。

图3-5 游戏项目目录结构

图3-6 service文件夹的内容

3.3.2 配置文件

更改了目录结构,需要重新编写Skynet的配置文件,让Skynet可以加载项目代码。在etc文件夹下新建文本文件config.node1和config.node2,它们代表各个节点的配置。config.node1中的代码如代码3-1所示,需注意标注了底纹的部分。

代码3-1 etc/config.node1

(资源:Chapter3/rill4)


--必须配置
thread = 8                          --启用多少个工作线程
cpath = "./skynet/cservice/?.so"    --用C编写的服务模块的位置
bootstrap = "snlua bootstrap"       --启动的第一个服务

--bootstrap配置项
start = "main"                      --主服务入口
harbor = 0                          --不使用主从节点模式

--lua配置项
lualoader = "./skynet/lualib/loader.lua"
luas ervice  =  "./service/?.lua;" .."./service/?/init.lua;".. "./skynet/service/?.
    lua;"
lua_ path  =  "./etc/?.lua;" .. "./lualib/?.lua;" ..  "./skynet/lualib/?.
    lua;" .. "./skynet/lualib/?/init.lua"
lua_cpath = "./luaclib/?.so;" .. "./skynet/luaclib/?.so"

--后台模式(必要时开启)
--daemon = "./skynet.pid"
--logger = "./userlog"

--节点
node = "node1"

这份配置与Skynet的默认配置没有太大区别,但有一些需要注意的地方,具体如下:

1)因为Skynet引擎被放置到skynet文件夹下了,所以要重设cpath、lualoader、luaservice、lua_path、lua_cpath的路径。

2)由于自定义服务位于service文件夹下,因此要修改luaservice配置项,让它搜索该文件夹。按照代码3-1的设置,它会查找service/[服务名].lua或service/[服务名]/init.lua作为服务的启动文件。如果查找失败,才去搜索Skynet提供的服务。

3)依据代码中lua_path项的配置,当程序需要加载Lua模块时,它会依次查找etc/[模块名].lua、lualib/[模块名].lua,再查找skynet提供的模块。

4)自定义环境变量“node”,代表节点名称。

5)使用cluster集群模式,设置harbor=0。

6)主服务为main,根据luaservice项的配置,skynet会启动service/main.lua作为主服务。

config.node2与config.node1的内容一样,只是将node="node1"改成了node="node2"。

3.3.3 第1版主服务

先编写个最简单的主服务,用于测试。首先要让系统能启动,后面才好编写功能逻辑。下面的代码3-2仅仅能打印出“[start main]”。

代码3-2 service/main.lua


local skynet = require "skynet"
skynet.start(function()
    --初始化
    skynet.error("[start main]")
    --退出自身
    skynet.exit()
end)

3.3.4 启动脚本

编译Skynet后,即可启动程序,在start.sh所在的目录执行“./skynet/skynet./etc/config.node1”启动程序,图3-7所示是成功启动服务端项目的信息,倒数第三行的“[start main]”正是主服务打印出的内容,如果能看到此信息,说明启动成功。

图3-7 成功启动服务端项目

“./skynet/skynet./etc/config.node1”这句话很长,不方便输入,在start.sh中编写如代码3-3所示的代码以后,只需执行“sh start.sh 1”即可开启第一个节点,执行“sh start.sh 2”即可开启第二个节点,方便多了。

代码3-3 ./start.sh


./skynet/skynet ./etc/config.node$1

3.3.5 服务配置

服务端支持横向拓展,每个节点可以开启不同数量的gateway、login,此处需要通过一份配置文件来描述服务端的拓扑结构。各个服务也需要根据这份配置文件来查找其他服务的位置。比如login服务器需要与agentmgr通信,那么它就需要知道agentmgr在哪个节点,配置文件会提供这个信息。服务配置还会提供服务所需的一些参数,比如每个gateway监听哪个端口号。

新建文件etc/runconfig.lua,内容如代码3-4所示。

代码3-4 etc/runconfig.lua


return {
    --集群
    cluster = {
      node1 = "127.0.0.1:7771", 
      node2 = "127.0.0.1:7772",
    },
    --agentmgr
    agentmgr = { node = "node1" },
    --scene
    scene = {
          node1 = {1001, 1002},  
      --node2 = {1003},
    },
    --节点1
    node1 = {
        gateway = {
          [1] = {port=8001},
          [2] = {port=8002},
        },
        login = {
            [1] = {},
            [2] = {},
        },
    },

    --节点2
    node2 = {
        gateway = {
          [1] = {port=8011},
          [2] = {port=8022},
        },
        login = {
            [1] = {},
            [2] = {},
        },
    },
}

代码3-4虽然看起来比较长,含义却很简单,图3-8对代码中各项做出了解释,对于图3-3涉及的agent和nodemgr,因为无须配置,所以不在图里展现。以下是代码3-4的具体说明。

1)cluster项指明服务端系统包含两个节点,分别为node1和node2。各个节点需要通信,其中node1的地址为“127.0.0.1:7771”,node2的地址为“127.0.0.1:7772”。

2)agentmgr项指明全局唯一的agentmgr服务位于节点1处。

3)scene项指明在节点1开启编号为1001和1002的两个战斗场景服务,语句“node2={1003}”代表在节点2开启编号为1003的场景服务。为了方便前期开启单个节点来调试功能,我们先把node2={1003}这行代码注释掉,用时再开启。

4)node1和node2描述了各节点的“本地”服务。两个节点分别开启了两个gateway和两个login,节点1处的两个gateway的监听端口分别是8001和8002,节点2的是8011和8012。

这段代码仅是范例,读者可以根据项目需要自行修改。如果游戏在线人数很多,要配置更多节点,开启更多gateway。

后面的主程序会读取runconfig.lua,决定节点内要启动哪些服务。gateway也会读取它,用于设置监听端口。

图3-8 代码3-4描述的结构

该如何读取这份描述文件呢?可以按照代码3-5做个简单测试,主服务应该能把“runconfig.agentmgr.node”的值“node1”打印出来。

代码3-5 ./service/main.lua


local skynet = require "skynet"
local runconfig = require "runconfig"

skynet.start(function()
    --初始化
    skynet.error(runconfig.agentmgr.node)
    --退出自身
    skynet.exit()
end)