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 =.. "./skynet/service/?. lua;" lua_ path = .. "./skynet/lualib/?. lua;" .. "./skynet/lualib/?/init.lua" lua_cpath = .. "./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"skynet.start(function() --初始化 --退出自身 skynet.exit() end)