万丈高楼平地起,既然这是个“大项目”,就要有大项目的样子,就要有所规划,下面先把项目的目录结构搭起来。
1、目录结构
建议把Skynet框架放到一个文件夹里,把所有自己编写的内容都放到外层的文件夹里。建立如下表所示的目录结构:
文件(夹) | 说明 |
etc | 存放服务配置的文件夹 |
luaclib | 存放一些C模块(.so文件) |
lualib | 存放Lua模块 |
service | 存放各种服务的Lua代码 |
skynet | Skynet框架,我们不会改动Skynet的任何内容。如果后续Skynet有更新,直接替换该文件夹即可 |
start.sh | 启动服务器的脚本 |
创建完目录结构之后如下图所示:
service文件夹用于存放各种服务的代码,每个服务的代码都放到一个以服务名称命名的文件夹里。如下图所示:
服务端会开启gateway、login、agent等多种服务,我们光给每个服务建立对应的文件夹。主服务是节点启动后第一个被加载的服务,用于启动其他各个服务,它比较特殊,我们不给它创建对应的文件夹,而是为它创建一个main.lua文件。
2、配置文件
更改了目录结构,需要重新编写Skynet的配置文件,让Skynet可以加载项目代码。在etc文件夹下新建文本文件config.node1和config.node2,它们代表各个节点的配置。
config.node1中的代码如下所示:
--必须配置
thread = 8 --启用多少个工作线程
cpath = "./skynet/cservice/?.so" --用C编写的服务模块的位置
bootstrap = "snlua bootstrap" --启动的第一个服务
--bootstrap配置项
start = "main" --主服务入口
harbor = 0 --不使用主从节点模式
--lua配置项
lualoader = "./skynet/lualib/loader.lua"
luaservice = "./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的默认配置没有太大区别,但有一些需要注意的地方,具体如下:
- 因为Skynet引擎被放置到skynet文件夹下了,所以要重设cpath、lualoader、luaservice、lua_path、lua_cpath的路径;
- 由于自定义服务位于service文件夹下,因此要修改luaservice配置项,让它搜索该文件夹。按照上面代码的设置,它会依次查找service/[服务名].lua、service/[服务名]/init.lua作为服务的启动文件。如果查找失败,才去搜索skynet提供的服务。
- 依据代码中lua_path项的配置,当程序需要加载Lua模块时,它会依次查找etc/[模块名].lua、lualib/[模块名].lua,再查找skynet提供的模块;
- 自定义环境变量“node”,代表节点名称;
- 使用cluster集群模式,设置harbor=0;
- 主服务为main,根据luaservice项的配置,skynet会启动service/main.lua作为主服务;
- config.node2与config.node1的内容一样,只是将node="node1"改成了node="node2"。
3、主服务
先编写个最简单的主服务,用于测试。首先要让系统能启动,后面才好编写功能逻辑。
local skynet = require "skynet"
skynet.start(function()
--初始化
skynet.error("[start main]")
--退出自身
skynet.exit()
end)
4、启动脚本
因为启动脚本的命令很长:“./skynet/skynet./etc/config.node1”,不方便输入,在我们之前创建的start.sh中编写如下所示的代码:
./skynet/skynet ./etc/config.node$1
在start.sh所在的目录执行“sh start.sh 1”即可启动程序,执行“sh start.sh 2”即可开启第二个节点,方便多了。启动主服务后如下图所示:
倒数第三行的“[start main]”正是主服务打印出的内容,如果能看到此信息,说明启动成功。
5、服务配置
- 服务端支持横向拓展,每个节点可以开启不同数量的gateway、login,此处需要通过一份配置文件来描述服务端的拓扑结构;
- 各个服务也需要根据这份配置文件来查找其他服务的位置;
- 比如login服务器需要与agentmgr通信,那么它就需要知道agentmgr在哪个节点,配置文件会提供这个信息;
- 服务配置还会提供服务所需的一些参数,比如每个gateway监听哪个端口号。
新建文件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] = {},
},
},
}
代码说明:
- cluster指明服务端系统包含两个节点,分别为node1和node2。各个节点需要通信,其中node1的地址为“127.0.0.1:7771”,node2的地址为“127.0.0.1:7772”;
- agentmgr指明全局唯一的agentmgr服务位于node1处;
- scene指明在node1开启编号为1001和1002的两个战斗场景服务,语句“node2={1003}”代表在node2开启编号为1003的场景服务。为了方便前期开启单个节点来调试功能,我们先把node2={1003}这行代码注释掉,用时再开启;
- node1和node2描述了各节点的“本地”服务。两个节点分别开启了两个gateway和两个login,node1处的两个gateway的监听端口分别是8001和8002,node2的是8011和8012。
下图对代码中各项也做出了解释:
6、服务测试
该如何读取这份配置文件呢?我们可以做个简单测试,在./service/main.lua文件中添加两句代码,代码如下所示:
local skynet = require "skynet"
local runconfig = require "runconfig"
skynet.start(function()
--初始化
skynet.error(runconfig.agentmgr.node)
--退出自身
skynet.exit()
end)
在start.sh所在的目录下执行“sh start.sh 1”,主服务应该能把“runconfig.agentmgr.node”的值“node1”打印出来:
这段代码仅是范例,大家可以根据项目需要自行修改。如果游戏在线人数很多,要配置更多节点,开启更多gateway。
后面的主程序会读取runconfig.lua,决定节点内要启动哪些服务。gateway也会读取它,用于设置监听端口。