skynet中的每一个服务都有一个独立的lua虚拟机,逻辑上服务之间是相互隔离的,那么你就不能使用传统意义上的LUA全局变量来进行服务间通信了。
在skynet中服务之间可以通过skynet消息调度机制来完成通信。skynet中的服务是基于actor模型设计出来的,每个服务都可以接收消息,处理消息,发送应答消息。
每条 skynet 消息由 6 部分构成:消息类型、session 、发起服务地址 、接收服务地址 、消息 C 指针、消息长度。
在 skynet 中消息分为多种类别,对应的也有不同的编码方式(即协议),消息类型的宏定义可以查看 skynet.h 中:
//表示一个回应包 //广播消息 //用来处理网络客户端的请求消息 //系统消息 //跨节点消息 //套接字消息 //错误消息,一般服务退出的时候会发送error消息给关联的服务 //lua类型的消息,最常用 //snax服务消息
上面的消息类型有多种,但是最常用的是PTYPE_LUA,对应到lua层,叫做lua消息
,大部分服务一般使用这种消息,默认情况下,PTYPE_REPSONSE、PTYPE_ERROR、PTYPE_LUA三种消息类型已经注册(查看源码了解情况),如果想使用其他的消息类型,需要自己显示注册消息 类型。
当我们需要在一个服务中监听指定类型的消息,就需要在服务启动的时候先注册该类型的消息的监听,通常是在服务的入口函数 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)
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).
--用 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会等到该消息处理完后,自动释放掉它的内存。
--用默认函数打包消息,向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: 1 [:0100000a] arg2: nengzhong [:0100000a] arg3: true
对lua消息响应的时候,使用的是PTYPE_REPSONSE这种消息,也是需要打包,打包的时候必须与接收到的消息打包方法一致。
skynet.ret --目标服务消息处理后需要通过该函数将结果返回 skynet.retpack(...) --将消息用skynet.pack 打包,并调用 ret 回应。
修改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 return 100, false end skynet.start(function() --注册"lua"类型消息的回调函数 skynet.dispatch("lua", function(session, address, ...) --skynet.sleep(500) skynet.retpack(dosomething(session, address, ...)) --申请响应消息C内存 --或者skynet.ret(skynet.pack(dosomething(session, address, ...))) end)--skynet.dispatch完成后,释放调用接收消息C内存 skynet.register(".testluamsg") end)
先运行testluamsg.lua再运行testcallmsg.lua, 结果如下:
$ ./skynet examples/config testluamsg [:0100000a] LAUNCH snlua testluamsg testcallmsg [:0100000b] LAUNCH snlua testcallmsg [:0100000a] session 2 [:0100000a] address :0100000b [:0100000a] arg1: 1 [:0100000a] arg2: nengzhong [:0100000a] arg3: true [:0100000b] skynet.call return value: 100 #第一个call返回,返回值为100 [:0100000a] session 3 #通过skynet.call方式发送消息session才起作用了 [:0100000a] address :0100000b [:0100000a] arg1: 2 [:0100000a] arg2: nengzhong [:0100000a] arg3: false [:0100000b] skynet.rawcall return value: 100 false #第二个call也返回
注意:
1、应答消息打包的时候,打包方法必须与接收消息的打包方式一致。
2、skynet.ret不需要指定应答消息是给哪个服务的。
因为每次接收到消息时,服务都会启动一个协程来处理,并且把这个协程与源服务地址绑定在一起了(其实就是把协程句柄作为key,源服务地址为value,记录在一张表中)。需要响应的时候可以根据协程句柄找到源服务地址。
mydb.lua
local skynet = require "skynet" require "skynet.manager" -- import skynet.register local db = {} local command = {} function command.GET(key) return db[key] end function command.SET(key, value) db[key] = value end skynet.start(function() --注册该服务的lua消息回调函数 skynet.dispatch("lua", function(session, address, cmd, ...) --接受到的第一参数作为命令使用 cmd = cmd:upper() local f = command[cmd] --查询cmd命令的具体处理方法 if f then --执行查询到的方法,并且通过skynet.ret将执行结果返回 skynet.retpack(f(...)) else skynet.error(string.format("Unknown command %s", tostring(cmd))) end end) skynet.register ".mydb" --给当前服务注册一个名字,便于其他服务给当前服务发送消息 end)
testmydb.lua
local skynet = require "skynet" local key,value = ... function task() --给.mydb服务发送一个lua消息,命令为set --发送成功后直接返回,不管接收消息端的是否调用skynet.ret,skynet.send的返回值都为0 local r = skynet.send(".mydb", "lua", "set", key, value) skynet.error("mydb set Test", r) --给.mydb服务发送一个lua消息,命令为get --如果接收端没有调用skynet.ret,则skynet.call将一直阻塞 r = skynet.call(".mydb", "lua", "get", key) skynet.error("mydb get Test", r) skynet.exit() end skynet.start(function() skynet.fork(task) end)
session只有在使用skynet.call或者skynet.rawcall发送消息的时候才有意义。
因为有可能一个服务开了多个协程去call消息,然后多个协程都在等待应答消息,回来了一个应答,那么到底是唤醒哪个协程,就可以通过session来判断了,skynet中的session能保证本服务中发出的消息是唯一的。消息与响应一一对应起来。
例如
echoluamsg.lua
skynet = require "skynet" skynet.start(function() --注册"lua"类型消息的回调函数 skynet.dispatch("lua", function(session, address, msg) skynet.sleep(math.random(100, 500)) skynet.retpack(msg:upper()) end) skynet.register(".echoluamsg") end)
testforkcall.lua:
skynet = require "skynet" local function task(id) for i = 1,5 do skynet.error("task"..id .." call") skynet.error("task"..id .." return:", skynet.call(".echoluamsg", "lua", "task"..id)) end end skynet.start(function() skynet.fork(task, 1) --开两个线程去执行 skynet.fork(task, 2) end)
先运行echoluamsg,再运行testforkcall,结果如下:
testforkcall [:0100000b] LAUNCH snlua testforkcall [:0100000b] task1 call [:0100000b] task2 call [:0100000b] task2 return: TASK2 #消息能够一一对应在一起 [:0100000b] task2 call [:0100000b] task1 return: TASK1 [:0100000b] task1 call [:0100000b] task2 return: TASK2 [:0100000b] task2 call [:0100000b] task1 return: TASK1 [:0100000b] task1 call [:0100000b] task1 return: TASK1 [:0100000b] task1 call [:0100000b] task2 return: TASK2 [:0100000b] task2 call [:0100000b] task1 return: TASK1 [:0100000b] task1 call [:0100000b] task1 return: TASK1 [:0100000b] task2 return: TASK2 [:0100000b] task2 call [:0100000b] task2 return: TASK2
在使用skynet.ret或者skynet.retpack进行应答时,必须要保证与接受请求时在同一个协程中(源服务地址与协程句柄已经一一对应),也就是说在哪个协程接受的请求也必须在这个协程去做响应。看下面的一个例子:
errormydb.lua
local skynet = require "skynet" require "skynet.manager" -- import skynet.register local db = {} local command = {} function command.GET(key) return db[key] end function command.SET(key, value) db[key] = value end skynet.start(function() skynet.dispatch("lua", function(session, address, cmd, ...) skynet.error("lua dispatch ", coroutine.running()) --这个协程接收消息的 skynet.fork(function(cmd, ...) --开启一个新的协程来处理响应 skynet.error("fork ", coroutine.running()) cmd = cmd:upper() local f = command[cmd] if f then skynet.retpack(f(...)) else skynet.error(string.format("Unknown command %s", tostring(cmd))) end end, cmd, ...) end) skynet.register ".mydb" end)
运行效果:
$ ./skynet examples/config errormydb #先运行errormydb [:0100000a] LAUNCH snlua errormydb testmydb name nengzhong #再运行testmydb [:0100000b] LAUNCH snlua testmydb name nengzhong [:0100000b] mydb set Test 0 [:0100000a] lua dispatch thread: 0x7ff359169088 false [:0100000a] fork thread: 0x7ff35928e0c8 false [:0100000a] lua call [100000b to :100000a : 0 msgsz = 19] error : ./lualib/skynet.lua:534: ./lualib/skynet.lua:178: dest address type (nil) must be a string or number. #报错误 stack traceback: [C]: in function 'assert' ./lualib/skynet.lua:534: in function 'skynet.manager.dispatch_message' [:0100000a] lua dispatch thread: 0x7ff359169088 false [:0100000a] fork thread: 0x7ff35928e1a8 false [:0100000a] lua call [100000b to :100000a : 2 msgsz = 9] error : ./lualib/skynet.lua:534: ./lualib/skynet.lua:178: dest address type (nil) must be a string or number. stack traceback: [C]: in function 'assert' ./lualib/skynet.lua:534: in function 'skynet.manager.dispatch_message'
在skynet中,当一个服务收到一个消息的时候,会启动一个协程来处理,并且把协程句柄与发送消息的服务地址进行一一对应记录在table中,当需要响应时,就使用当前调用skynet.ret
的协程句柄去table中查询对应的服务地址,然后把消息发给这个服务地址。
如果开了一个新的协程去调用skynet.ret,那么这个时候使用新启动的协程句柄去查询服务地址,肯定是查不到的。
当不想接受请求与响应请求在同一个协程中完成时,我可以使用response替代ret。
local skynet = require "skynet" --参数pack指定应答打包函数,不填默认使用skynet.pack, 必须根据接收到消息的打包函数一致 --返回值是一个闭包函数 local response = skynet.response(pack) --闭包函数使用方式 --参数ok的值可以是 "test"、true、false,为"test"时表示检查接收响应的服务是否存在,为true时表示发送应答PTYPE_RESPONSE,为false时表示发送PTYPE_ERROR错误消息。 response(ok, ...)
代码如下:
local skynet = require "skynet" require "skynet.manager" -- import skynet.register local db = {} local command = {} function command.GET(key) return db[key] end function command.SET(key, value) db[key] = value end skynet.start(function() skynet.dispatch("lua", function(session, address, cmd, ...) --先把发送服务地址以及session打包到闭包中,可以在任意地方调用 local response = skynet.response(skynet.pack) --指定打包函数,必须根据接收到的消息打包函数一致 skynet.fork(function(cmd, ...) --开启一个新的协程来处理响应 skynet.sleep(500) cmd = cmd:upper() local f = command[cmd] if f then response(true, f(...)) --第一个参数true表示应答成功,false则应答个错误消息 else skynet.error(string.format("Unknown command %s", tostring(cmd))) end end, cmd, ...) end) skynet.register ".mydb" end)
把发送服务地址以及session打包到闭包中,就可以在任意地方调用了。尽量多用skynet.response进行应答。
当一个服务发起请求skynet.call
后等待应答,但是响应服务却退出了(调用skynet.exit
) ,响应服务退出的时候,会自动给未答复的请求发送一个error
消息,告诉它可以从skynet.call阻塞返回了,请求的服务会直接报一个错误。
noresponse.lua如下:
local skynet = require "skynet" require "skynet.manager" -- import skynet.register skynet.start(function() skynet.dispatch("lua", function(session, address, cmd, ...) skynet.exit() --在这里退出了服务 end) skynet.register ".noresponse" end)
testnoresponse.lua如下:
local skynet = require "skynet" function task() r = skynet.call(".noresponse", "lua", "get") skynet.error("get Test", r) skynet.exit() end skynet.start(function() skynet.fork(task) end)
运行结果:
$ ./skynet examples/config noresponse [:01000010] LAUNCH snlua noresponse testnoresponse [:01000012] LAUNCH snlua testnoresponse [:01000010] KILL self [:01000012] lua call [1000010 to :1000012 : 2 msgsz = 0] error : ./lualib/skynet.lua:534: ./lualib/skynet.lua:156: ./lualib/skynet.lua:391: call failed #call报错 stack traceback: [C]: in function 'error' ./lualib/skynet.lua:391: in upvalue 'yield_call' ./lualib/skynet.lua:402: in function 'skynet.call' ./my_workspace/testnoresponse.lua:4: in function 'task' ./lualib/skynet.lua:468: in upvalue 'f' ./lualib/skynet.lua:106: in function <./lualib/skynet.lua:105> stack traceback: [C]: in function 'assert' ./lualib/skynet.lua:534: in function 'skynet.dispatch_message'
同一个 skynet 服务中的一条消息处理中,如果调用了一个阻塞 API ,那么它会被挂起。挂起过程中,这个服务可以响应其它消息。这很可能造成时序问题,要非常小心处理。
换句话说,一旦你的消息处理过程有外部请求,那么先到的消息未必比后到的消息先处理完。且每个阻塞调用之后,服务的内部状态都未必和调用前的一致(因为别的消息处理过程可能改变状态)。
来看一个服务重入的情况:
改造一下mydb.lua
local skynet = require "skynet" require "skynet.manager" -- import skynet.register local db = {} local command = {} function command.GET(key) skynet.sleep(1000) --这里加了一个阻塞函数 return db[key] end function command.SET(key, value) db[key] = value end skynet.start(function() skynet.dispatch("lua", function(session, address, cmd, ...) cmd = cmd:upper() local f = command[cmd] if f then skynet.retpack(f(...)) else skynet.error(string.format("Unknown command %s", tostring(cmd))) end end) skynet.register ".mydb" end)
testmydb.lua还是一样
local skynet = require "skynet" local key,value = ... function task() local r = skynet.send(".mydb", "lua", "set", key, value) skynet.error("mydb set Test", r) r = skynet.call(".mydb", "lua", "get", key) skynet.error("mydb get Test", r) skynet.exit() end skynet.start(function() skynet.fork(task) end)
运行情况:
$ ./skynet examples/config mydb #先启动mydb [:01000010] LAUNCH snlua mydb testmydb name xm #再启动一个testmydb设置设置键值对name=xm,并获取name的值 [:01000012] LAUNCH snlua testmydb name xm [:01000012] mydb set Test 0 testmydb name xh #再启动一个testmydb设置设置键值对name=xh,并获取name值 [:01000019] LAUNCH snlua testmydb name xh [:01000019] mydb set Test 0 [:01000012] mydb get Test xh #获取到的值都是xh,我们希望获取的是xm [:01000012] KILL self [:01000019] mydb get Test xh #获取到的值都是xh [:01000019] KILL self
上面出现的情况,就是因为,mydb服务处理上一个请求还没结束时,又来了一个新的请求,并且新的请求改变的mydb中name的值,所以等到第一个请求从阻塞状态恢复时,获取的到值也变了。
上面的例子即使把skynet.sleep去掉,只要请求发送足够频繁,依然存在重入的问题。
skynet.queue 模块可以帮助你回避这些服务重入或者伪并发引起的复杂性。
local queue = require "skynet.queue" local cs = queue() --获取一个执行队列 cs(f, ...) --将f丢到队列中执行
改进mydb.lua服务如下:
local skynet = require "skynet" require "skynet.manager" -- import skynet.register local queue = require "skynet.queue" local cs = queue() --获取一个执行队列 local db = {} local command = {} function command.GET(key) skynet.sleep(1000) --这里加了一个阻塞函数 return db[key] end function command.SET(key, value) db[key] = value end skynet.start(function() skynet.dispatch("lua", function(session, address, cmd, ...) cmd = cmd:upper() local f = command[cmd] if f then --将f丢到队列中去执行,队列中的函数严格按照先后顺序进行执行 skynet.retpack(cs(f, ...)) else skynet.error(string.format("Unknown command %s", tostring(cmd))) end end) skynet.register ".mydb" end)
同样的执行步骤:
$ ./skynet examples/config mydb #先启动mydb [:01000012] LAUNCH snlua mydb testmydb name xm #再启动一个testmydb设置设置键值对name=xm,并获取name的值 [:01000019] LAUNCH snlua testmydb name xm [:01000019] mydb set Test 0 testmydb name xh #再启动一个testmydb设置设置键值对name=xh,并获取name值 [:01000020] LAUNCH snlua testmydb name xh [:01000020] mydb set Test 0 [:01000019] mydb get Test xm #获取到的值都是xm [:01000019] KILL self [:01000020] mydb get Test xh #获取到的值都是xh [:01000020] KILL self
上面的输出结果就是我们想要的了,把所有不希望重入的函数丢到cs队列中去执行,队列将依次执行每一个函数,前一个函数还没执行完的时候,后面的函数永远不会被执行。
执行队列虽然解决了重入的问题,但是明显降低了服务的并发处理能力,所以使用执行队列的时候尽量缩小临界区的颗粒度大小。
如果想要使用其他的消息,那么需要显示注册一下:
othermsg.lua代码
local skynet = require "skynet" require "skynet.manager" -- inject skynet.forward_type skynet.register_protocol { --注册system消息 name = "system", id = skynet.PTYPE_SYSTEM, --pack = skynet.pack, unpack = skynet.unpack, --unpack必须指定一下,接收到消息后会自动使用unpack解析 } skynet.start(function() skynet.dispatch("system", function(session, address, ...) --使用unpack解包 skynet.ret(skynet.pack("nengzhong")) --使用skynet.retpack的时候,必须要在skynet.register_protocol指定pack --skynet.retpack("nengzhong") end) skynet.register ".othermsg" end)
testothermsg.lua代码
skynet = require "skynet" skynet.register_protocol { --注册system消息 name = "system", id = skynet.PTYPE_SYSTEM, --pack = skynet.pack, --unpack = skynet.unpack } skynet.start(function() local othermsg = skynet.localname(".othermsg") local r = skynet.unpack(skynet.rawcall(othermsg, "system", skynet.pack(1, "nengzhong", true))) --使用skynet.call的时候必须要在skynet.register_protocol指定pack与unpack --local r = skynet.call(othermsg, "system", 1, "nengzhong", true) skynet.error("skynet.call return value:", r) end)
需要注意,error、lua、response都是已经默认注册的消息类型,不要尝试修改他们的协议定义。
在 skynet 中,有时候为一个服务实现一个前置的代理服务是很有必要的。所谓代理服务,就是向真正的功能服务发起请求时,请求消息发到另一个代理服务中,由这个代理服务转发这个请求给真正的功能服务。同样,回应消息也会被代理服务转发回去。
示例代码:proxy.lua
local skynet = require "skynet" require "skynet.manager" local realsvr = ... skynet.start( function() --注册消息处理函数 skynet.dispatch("lua", function (session, source, ...) --接收到消息msg,sz skynet.ret(skynet.rawcall(realsvr, "lua", skynet.pack(...))) --根据参数列表重新打包消息转发 end)--释放消息msg,sz skynet.register(".proxy") end)
示例代码:testmydb.lua
local skynet = require "skynet" local key,value = ... function task() local r = skynet.send(".proxy", "lua", "set", key, value) --修改为.proxy,发给代理 skynet.error("mydb set Test", r) r = skynet.call(".proxy", "lua", "get", key) --修改为.proxy,发给代理 skynet.error("mydb get Test", r) skynet.exit() end skynet.start(function() skynet.fork(task) end)
运行结果:
$ ./skynet examples/config mydb [:0100000a] LAUNCH snlua mydb proxy .mydb [:0100000b] LAUNCH snlua proxy .mydb testmydb name nengzhong [:0100000c] LAUNCH snlua testmydb name nengzhong [:0100000c] mydb set Test 0 [:0100000c] mydb get Test nengzhong [:0100000c] KILL self
这份代理重复的打包解包效率上有很大问题。
上一节效率的问题是因为服务默认接收到lua消息后,会解包消息,但是lua消息已经注册了无法更改,那么我么可以使用skynet.forward_type进行协议转换。
使用skynet.forward_type也是启动服务的一种方法,跟skynet.start类似,只不过skynet.forward_type还需要提供一张消息转换映射表forward_map, 其他的方法与skynet.start一样。
local skynet = require "skynet" require "skynet.manager" -- inject skynet.forward_type skynet.register_protocol { --注册system消息 name = "system", id = skynet.PTYPE_SYSTEM, unpack = function (...) return ... end, --unpack直接返回不解包了 } local forward_map = { --发送到代理服务的lua消息全部转成system消息,不改变原先LUA的消息协议处理方式 [skynet.PTYPE_LUA] = skynet.PTYPE_SYSTEM, --如果接收到应答消息,默认情况下会释放掉消息msg,sz,forward的方式处理消息不会释放掉消息msg,sz [skynet.PTYPE_RESPONSE] = skynet.PTYPE_RESPONSE, } local realsvr = ... skynet.forward_type( forward_map ,function() --注册消息处理函数 skynet.dispatch("system", function (session, source, msg, sz) --直接转发给realsvr,接收到realsvr响应后也不释放内存,直接转发 skynet.ret(skynet.rawcall(realsvr, "lua", msg, sz)) end)--处理完不释放内存msg skynet.register(".proxy") end)
mydb.lua代码不变,运行结果:
$ ./skynet examples/config mydb #先运行mydb [:01000010] LAUNCH snlua mydb proxy .mydb #启动代理服务,并且告诉它真正的处理服务是.mydb [:01000012] LAUNCH snlua proxy .mydb testmydb name xx #启动testmydb服务 [:01000019] LAUNCH snlua testmydb name xx [:01000019] mydb set Test 0 #send方法成功 [:01000019] mydb get Test xx #get方法成功 [:01000019] KILL self
伪造其他服务地址来发送一个消息,可以使用到skynet.redirect
local skynet = require "skynet" --使用source服务地址,发送typename类型的消息给dest服务,不需要接收响应,(source,dest只能是服务ID) --msg sz一般使用skynet.pack打包生成 skynet.redirect(dest,source,typename, session, msg, sz)
例如:testredirect.lua
local skynet = require "skynet" local source, dest = ... skynet.start(function() source = skynet.localname(source) dest = skynet.localname(dest) skynet.redirect(source, dest, "lua", 0, skynet.pack("nengzhong", 8.8, false)) end)
运行结果:
testluamsg #先启动testluamsg.lua [:0100000a] LAUNCH snlua testluamsg testsendmsg #再启动testsendmsg.lua [: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 testredirect .testluamsg .testsendmsg #伪造testsendmsg给testluamsg发送一个消息 [:0100000c] LAUNCH snlua testredirect .testluamsg .testsendmsg [:0100000a] session 0 [:0100000a] address :0100000b [:0100000a] arg1: nengzhong [:0100000a] arg2: 8.8 [:0100000a] arg3: false
globalluamsg.lua
skynet = require "skynet" require "skynet.manager" skynet.start(function() --注册"lua"类型消息的回调函数 skynet.dispatch("lua", function(session, address, msg) skynet.retpack(msg:upper()) end) skynet.register("globalluamsg") end)
globalcall.lua
skynet = require "skynet" harbor = require "skynet.harbor" skynet.start(function() local globalluamsg = harbor.queryname("globalluamsg") local r = skynet.call(globalluamsg, "lua", "nengzhong") skynet.error("skynet.call return value:", r) end)
先在节点1运行globalluamsg,然后在节点2运行globalcall.
节点1运行情况:
globalluamsg [:0100000a] LAUNCH snlua globalluamsg
节点2运行情况:
globalcall [:02000008] LAUNCH snlua globalcall [:02000008] skynet.call return value: NENGZHONG