跳到主要内容

节点间通信

设置启动选项

使用默认启动选项创建的进程,适合用来编写一些工具脚本,对于游戏服务器进程,通常需要设置一些选项的:

  • thread: 工作线程数 默认是cpu核心数
  • enable_stdout: 是否打印标准输出 默认是true
  • logfile: 日志文件路径 默认不输出日志文件
  • loglevel: 日志等级, 默认 DEBUG. 可选 DEBUG,INFO,WARN,ERROR
  • path: lua模块搜索路径,默认会包含lualibservice路径

想要设置启动选项,必须在启动脚本第一行开始编写如下代码:

---__init__--- 这一行是固定格式, 用于标记启动脚本是否有设置启动选项
if _G["__init__"] then
local arg = ... --- command line args
return {
-- 基础配置
thread = 8, -- 工作线程数
enable_stdout = true, -- 启用标准输出
logfile = string.format("log/node-%s-%s.log",
arg[1],
os.date("%Y-%m-%d-%H-%M-%S")
),
loglevel = "DEBUG", -- 日志级别

-- Lua搜索路径
path = table.concat({
"./?.lua", -- 当前目录
"./?/init.lua", -- 当前目录的init文件
"../lualib/?.lua", -- moon库
"../service/?.lua", -- moon服务
-- Append your lua module search path
}, ";")
}
end

这样做的好处是, 启动选项的配置文件和启动脚本在一起,方便维护。moon进程启动时会先检测代码的第一行是否包含---__init__---,如果包含就设置全局表的__init__,这样运行脚本时就拿到了启动选项。然后再次运行启动脚本,使用它创建第一个服务。

一个创建Node进程模板

---__init__---  初始化进程选项标识
if _G["__init__"] then
local arg = ... ---这里可以获取命令行参数, string[] 类型
return {
thread = 8, ---启动8条线程
enable_stdout = true,
logfile = string.format("log/game-%s.log", os.date("%Y-%m-%d-%H-%M-%S")),
loglevel = "DEBUG", ---默认日志等级
path = table.concat({ --- 注意: 工作目录会切换到当前脚本所在的路径
"./?.lua",
"./?/init.lua",
"../lualib/?.lua", -- moon lualib 搜索路径
"../service/?.lua", -- moon 自带的服务搜索路径,需要用到redisd服务
-- Append your lua module search path
}, ";")
}
end

--------开始编写第一个服务的逻辑代码----

local moon = require("moon")

local socket = require "moon.socket"

--初始化服务配置
local db_conf= {host = "127.0.0.1", port = 6379, timeout = 1000}

local gate_host = "0.0.0.0"
local gate_port = 8889
local client_timeout = 300

local services = {
{
unique = true,
name = "db",
file = "../service/redisd.lua",
threadid = 2, ---独占线程
poolsize = 5, ---连接池
opts = db_conf
},
{
unique = true,
name = "center",
file = "game/service_center.lua",
threadid = 3,
},
}

moon.async(function ()
for _, one in ipairs(services) do
local id = moon.new_service( one)
if 0 == id then
moon.exit(-1) ---如果唯一服务创建失败,立刻退出进程
return
end
end

local listenfd = socket.listen(gate_host, gate_port, moon.PTYPE_SOCKET_TCP)
if 0 == listenfd then
moon.exit(-1) ---监听端口失败,立刻退出进程
return
end

print("server start", gate_host, gate_port)

while true do
local id = moon.new_service( {
name = "user",
file = "game/service_user.lua"
})

local fd, err = socket.accept(listenfd, id)
if not fd then
print("accept",err)
moon.kill(id)
else
moon.send("lua", id,"start", fd, client_timeout)
end
end

end)

-- 注册进程退出回调
moon.shutdown(function ()
moon.async(function ()
-- 控制其它唯一服务的退出逻辑
assert(moon.call("lua", moon.queryservice("center"), "shutdown"))
moon.raw_send("system", moon.queryservice("db"), "wait_save")

---wait all service quit
while true do
local size = moon.server_stats("service.count")
if size == 1 then
break
end
moon.sleep(200)
print("bootstrap wait all service quit, now count:", size)
end

moon.quit()
end)
end)

节点(进程)间通信

对于搭建分布式游戏服务器,就需要节点间通信。moon对节点间通信做了简单的封装,能满足大部分需求。节点间通信主要用到 cluster 的两个API:

    ---向指定节点的唯一服务发送消息, 无返回值, 如果网络或其它原因造成消息不可达,消息会被丢弃
cluster.send(receiver_node, receiver_sname, ...)
---向指定节点的唯一服务发起RPC调用, 不管成功和失败一定会得到返回值,可以检测返回值判断执行是否成功
cluster.call(receiver_node, receiver_sname, ...)

并且需要以下条件:

  • Http服务提供通过 NODE ID 获得 节点的 host port
  • 每个节点注册 moon.env("NODE", node_id) 环境变量
  • 每个节点需要创建一个cluster 唯一服务,名且服务名字需要是cluster
  • 需要被访问的节点 需要在创建cluster服务之后,调用它的Listen函数

创建节点配置文件

node.json

[
{
"node": 1,
"host":"127.0.0.1",
"port":42345
},
{
"node": 2,
"host":"127.0.0.1",
"port":42346
}
]

创建配置中心节点

cluster_etc.lua, 提供配置中心http服务,cluster服务通过它获取其它节点的端口地址。这里用moon开启了一个简单的http-server实现,也可以使用其它方式。

代码链接

7.3.3 创建Node1-消息发送者

node1.lua 调用 node2 bootstrap 函数提供的函数

代码链接

创建Node2-消息接收者

node2.lua

代码链接

运行

按照如下顺序,开启三个终端运行

./moon cluster_etc.lua node.json
./moon node2.lua 2
./moon node1.lua 1