在 skynet service 中,对 lua 消息处理的常见的代码是这样的:
https://github.com/cloudwu/skynet/blob/master/test/testselect.lua
skynet.start(function()
skynet.dispatch("lua", function(_,_, cmd, ...)
skynet.ret(skynet.pack(COMMAND[cmd](...)))
end)
end)
这应该是最短的一种处理 lua 消息的代码了,这种写法是所有消息处理都打包返回值,即使是用 skynet.send 发过来的请求。
下面这种是区分了消息是否有返回值的情况,也就是有些接口是只能用 skynet.send 调用的,不能使用 skynet.call 的,比如:
https://github.com/cloudwu/skynet/blob/master/service/multicastd.lua
local NORET = {}
function command.DELR(source, c)
channel[c] = nil
channel_n[c] = nil
return NORET
end
skynet.start(function()
skynet.dispatch("lua", function(_,source, cmd, ...)
local f = assert(command[cmd])
local result = f(source, ...)
if result ~= NORET then
skynet.ret(skynet.pack(result))
end
end)
end)
这种方式巧妙的使用一个临时 table NORET 来做唯一值,可以用来区分是否有返回值。
还有这样也是一样,只是加多了个判断了 cmd 是否存在。
https://github.com/cloudwu/skynet/blob/master/service/launcher.lua
skynet.dispatch("lua", function(session, address, cmd , ...)
cmd = string.upper(cmd)
local f = command[cmd]
if f then
local ret = f(address, ...)
if ret ~= NORET then
skynet.ret(skynet.pack(ret))
end
else
skynet.ret(skynet.pack {"Unknown command"} )
end
end)
这个也判断了 cmd 是否存在,但是和上面的不一样,上面的 cmd 不存在会返回一个字符串,下面这个则不返回,直接 error 。其实这种和第一种效果是一样的,第一种 cmd 不存在也会报错的。
https://github.com/cloudwu/skynet/blob/master/examples/simpledb.lua
skynet.dispatch("lua", function(session, address, cmd, ...)
local f = command[cmd]
if f then
skynet.ret(skynet.pack(f(...)))
else
error(string.format("Unknown command %s", tostring(cmd)))
end
end)
举了这么多例子,我是在想一个问题,能不能封装一个接口来做这件事情?怎么封装接口更合适?
回想之前使用 skynet.call 时,好像是目标服务如果没有返回消息,也就是没有使用 skynet.ret 时,会导致本服务卡住。现在无法找到这样的例子复现了。目前测试的情况是,目标服务报错,无论是手动 error,还是其他自动报错,比如第一个例子 cmd 不存在会报 "xxx is not callable (a nil value)" 。同时本服务会在 yield_call 中报错 "call failed" 。
当没有报错,且没有使用 skynet.ret 返回消息时,本服务会有个日志 "Maybe forgot response session 4 from :01000010" 。
上面的写法中还有一个区别的地方, multicastd.lua 中给 cmd 处理接口传入的 source ,那么封装接口的时候是否需要把 source 传入呢?我个人认为需要用到 source 的地方还是很少的,特别是写玩法逻辑这些。因此我更偏向于使用第一种写法。
NORET 那种写法我认为是过去的写法,具体可以看这个提交: cloudwu/skynet@16d8f4c#diff-04b2be7c9db589731e4653ef8705ee349b85b54a6ebd27572659601830a7a12dL461-L477 。这个提交中把 skyent.ret 接口修改了,也就是 skynet.send 过来的消息是会丢掉的,测试了下,逻辑会进入到 c.trash
把数据丢掉,也就不需要区分 skynet.call 和 skynet.send 的 cmd 函数了,非要说区别也就是对 cmd 函数返回值多 pack 了一次,我个人的观点是这个可以忽略不记,起码 cmd 函数实现起来不用担心调用者是否是用 send 还是 call 过来的。
综上所述,我封装了一个接口:
local skynet = require "skynet"
local M = {}
function M.dispatch(CMD)
skynet.dispatch("lua", function(_, _, cmd, ...)
local f = CMD[cmd]
if not f then
error("Unknown command:", cmd)
end
skynet.ret(skynet.pack(f(...)))
end)
end
return M
对于已知的错误使用 error 更好理解,其他未知的错误就让它自己报错就行。
使用起来也统一了,从多行代码改成一行就行:
local cmd_api = require "cmd_api"
local CMD = {}
skynet.start(function()
cmd_api.dispatch(CMD)
end)