节点间通信
设置启动选项
使用默认启动选项创建的进程,适合用来编写一些工具脚本,对于游戏服务器进程,通常需要设置一些选项的:
- thread: 工作线程数 默认是cpu核心数
- enable_stdout: 是否打印标准输出 默认是true
- logfile: 日志文件路径 默认不输出日志文件
- loglevel: 日志等级, 默认
DEBUG
. 可选DEBUG
,INFO
,WARN
,ERROR
- path: lua模块搜索路径,默认会包含
lualib
和service
路径
想要设置启动选项,必须在启动脚本第一行开始编写如下代码:
---__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