skynet中的每一个服务都有一个独立的lua虚拟机,逻辑上服务之间是相互隔离的,那么你就不能使用传统意义上的LUA全局变量来进行服务间通信了。
在skynet中服务之间可以通过skynet消息调度机制来完成通信。skynet中的服务是基于actor模型设计出来的,每个服务都可以接收消息,处理消息,发送应答消息。
每条 skynet 消息由 6 部分构成:消息类型、session 、发起服务地址 、接收服务地址 、消息 C 指针、消息长度。
7.1消息类型
在 skynet 中消息分为多种类别,对应的也有不同的编码方式(即协议),消息类型的宏定义可以查看 skynet.h 中:
//表示一个回应包 //广播消息 //用来处理网络客户端的请求消息 //系统消息 //跨节点消息 //套接字消息 //错误消息,一般服务退出的时候会发送error消息给关联的服务 //lua类型的消息,最常用 //snax服务消息
上面的消息类型有多种,但是最常用的是PTYPE_LUA,对应到lua层,叫做lua消息
,大部分服务一般使用这种消息,默认情况下,PTYPE_REPSONSE、PTYPE_ERROR、PTYPE_LUA三种消息类型已经注册(查看源码了解情况),如果想使用其他的消息类型,需要自己显示注册消息 类型。
7.2 注册消息处理函数
当我们需要在一个服务中监听指定类型的消息,就需要在服务启动的时候先注册该类型的消息的监听,通常是在服务的入口函数 skynet.start
处通过调用 skynet.dispatch
来注册绑定:
--服务启动入口
skynet.start(function()
--注册"lua"类型消息的回调函数
skynet.dispatch("lua", function(session, address, ...)
dosomething(...)
end)
end)
一旦注册成功,那么只要是发送给这个服务的消息是lua类型消息,那么都会调用我们注册的function进行处理。
例如testluamsg.lua:
skynet = require "skynet"
require "skynet.manager"
local function dosomething(session, address, ...)
skynet.error("session", session)
skynet.error("address", skynet.address(address))
local args = {...}
for i,v in pairs(args) do
skynet.error("arg"..i..":", v)
end
end
skynet.start(function()
--注册"lua"类型消息的回调函数
skynet.dispatch("lua", function(session, address, ...)
dosomething(session, address, ...)
end)
skynet.register(".testluamsg")
end)
7.3 打包与解包消息
skynet中的消息在发送之前必须要把参数进行打包,然后才发送,接受方收到消息后会自动根据指定的解包函数进行解包,最常用的打包解包函数为skynet.pack与skynet.unpack.
skynet.pack(...)
打包后,会返回两个参数,一个是C指针msg指向数据包的起始地址,sz一个是数据包的长度。msg指针的内存区域是动态申请的。
skynet.unpack(msg, sz)
解包后,会返回一个参数列表。需要注意这个时候C指针msg指向的内存不会释放掉。如果msg有实际的用途,skynet框架会帮你在合适的地方释放掉,如果没有实际的用途,自己想释放掉可以使用skynet.trash(msg, sz)
释放掉。
例如:
local msg, sz = skynet.pack("nengzhong", 8.8, false)
local arg1, arg2, arg3 = skynet.unpack(msg, sz)
skynet.error(arg1, arg2, arg3)
local arglist = {
skynet.unpack(msg, sz)}
for i,v in pairs(arglist) do
skynet.error("arg"..i..":", v)
end
skynet.trash(msg, sz) --没有用到skynet框架中,所以用完了需要自己释放一下
注意:skynet.pack返回的msg与sz只用在skynet.unpack中使用才有意义。不要这么使用table.unpack(msg, sz).
7.4 发送消息的方法
7.4.1 发送无需响应的消息
--用 type 类型向 addr 发送未打包的消息。该函数会自动把...参数列表进行打包,默认情况下lua消息使用skynet.pack打包。addr可以是服务句柄也可以是别名。
skynet.send(addr, type, ...)
--用 type 类型向 addr 发送一个打包好的消息。addr可以是服务句柄也可以是别名。
skynet.rawsend(addr, type, msg, sz)
例如testsendmsg.lua:
skynet = require "skynet"
skynet.start(function()
skynet.register(".testsendmsg")
local testluamsg = skynet.localname(".testluamsg")
--发送lua类型的消息给testluamsg,发送成功后立即返回,r的值为0
local r = skynet.send(testluamsg, "lua", 1, "nengzhong", true) --申请了C内存(msg,sz)已经用与发送,所以不用自己再释放内存了。
skynet.error("skynet.send return value:", r)
--通过skynet.pack来打包发送
r = skynet.rawsend(testluamsg, "lua", skynet.pack(2, "nengzhong", false)) --申请了C内存(msg,sz)已经用与发送,所以不用自己再释放内存了。
skynet.error("skynet.rawsend return value:", r)
end)
先运行testluamsg.lua再运行testsendmsg.lua, 结果如下:
$ ./skynet examples/config testluamsg [:0100000a] LAUNCH snlua testluamsg testsendmsg [:0100000b] LAUNCH snlua testsendmsg [:0100000b] skynet.send return value: 0 #发送完消息马上返回 [:0100000b] skynet.rawsend return value: 0 #发送完消息马上返回 [:0100000a] session 0 #接收端接到消息 [:0100000a] address :0100000b [:0100000a] arg1: 1 [:0100000a] arg2: nengzhong [:0100000a] arg3: true [:0100000a] session 0 [:0100000a] address :0100000b [:0100000a] arg1: 2 [:0100000a] arg2: nengzhong [:0100000a] arg3: false
上面的代码我们隐式或显示调用了skynet.pack,一共申请了两段C内存,但是并不需要我们释放C内存。因为已经把这段内存用于发送了,skynet会等到该消息处理完后,自动释放掉它的内存。
7.4.2 发送必须响应的消息
--用默认函数打包消息,向addr发送type类型的消息并等待返回响应,并对回应信息进行解包。(自动打包与解包。)
skynet.call(addr, type, ...)
--直接向addr发送type类型的msg,sz并等待返回响应,不对回应信息解包。(需要自己打包与解包)
skynet.rawcall(addr, type, msg, sz)
例如testcallmsg.lua:
skynet = require "skynet"
skynet.start(function()
skynet.register(".testcallmsg")
--发送lua类型的消息给service,发送成功,该函数将阻塞等待响应返回,r的值为响应的返回值
local r = skynet.call(".testluamsg", "lua", 1, "nengzhong", true)
skynet.error("skynet.call return value:", r)
--通过skynet.pack来打包发送,返回的值也需要自己解包
r = skynet.unpack(skynet.rawcall(".testluamsg", "lua", skynet.pack(2, "nengzhong", false)))
skynet.error("skynet.rawcall return value:", r)
end)
先运行testluamsg.lua再运行testcallmsg.lua, 结果如下:
$ ./skynet examples/config testluamsg [:0100000a] LAUNCH snlua testluamsg testcallmsg [:0100000b] LAUNCH snlua testcallmsg [:0100000a] session 2 #只发送出第一个消息,现在已经阻塞住,由于testluamsg并没有返回应答。 [:0100000a] address :0100000b [:0100000a] arg1: