包装skynet.call&send远程调用接口

从语法层的来看,方法调需要涉及三项内容,目标对象、方法名和参数。对于远程调用来讲,还需要一项,即目标对象所处的位置。所处位置是一个抽象的说法,它可能是物理设备、节点实例、服务实例等标识ID,对于本文谈及的skynet来说,它是服务的标识ID。

不论本地还是远端服务,skynet统一使用skynet.call或send,第一个参数指向服务实例标识ID(再次强调不论本地/远端),第三个参数指向调用该service的方法名称,从第四个开始后面参数是传给远端的参数。读者可能注意到这里跳过第二个参数的说明,是因为它与本文讨论的问题无关,忽略它并不影响本文内容的理解。

基于skynet实现一套业务框架,在接口隔离性方面,我有几点考虑

  1. skynet作为一个具体的实现,其提供的底层接口尽可能的不出现在业务层/应用层。如skynet.call/send不应该出现在业务层,它的第二个参数,更不必透露给最未端的代码。
  2. 第三参数的参数方法名称是字符串类型,绝大多数编程语言以点号或冒号作为方法调用的修饰,使用字符串发起方法的调用,对程序员来感受并不直观。另外有些编辑器插件,会基于上下文信息,在输入点号或冒号时会智能补全类型的方法名称。如果是字符串参数则无法利用这些功能

笔者尝试利用lua元素方法,做个简单远程调用接口的外层包装。只为抛砖引玉,而且实际项目必须进行二次开发。

  1. 包装实现中,会以特定的前缀来区分是本节的服务调用,还是其它节点的调用。如示例中用native表示本节调用,battle表示战斗节点调用,你可以扩展定义多个节点
  2. 节点名之后是区分同步还是异步调用,实际就是内部用来区分skynet.call还是skynet.send
  3. 远程调用包装实现,用到lua以下元素方法,假设你对这些元素方法有基本的了解 a. __index监控点号操作,此处__index的返回值有点特殊,是语法糖对象本身。目的是为了便于统一收集目标对象及其调用方法,而且此例中不支持多层的调用链,即__index收集到的第一个名字必定是对象的引用名称,第二个必定此对象的方法名称。 b. __call监控冒号操作,实现具体的远程请求接口。需要注意的是,__index收集的信息是临时的且会被下一次复用,所以在二次开发时,最安全的做法必定是先把所有收集到信息,放到当前方法环境的upvalue中,再进行其它会离开当前上下文环境的操作。
local chain = {}
rpc = {
    __index = function (t, k)
        chain[#chain+1] = k
        return t
    end,

    __call = function (t, ...)
        assert(#chain == 3)
        local type = chain[1]
        local srv = chain[2]
        local fn = chain[3]
        chain[1] = nil
        chain[2] = nil
        chain[3] = nil
        -- 不要在以上提取信息的过程,离开当前上下文环境(协程跳出)

        print(t.node_type, type, srv, fn, ...)

        -- 此处是二次开发,调用具体的远程调用接口
    end
}

-- 定义节点参数,根据需要扩展更多节点
native = setmetatable({node_type=1}, rpc)
battle = setmetatable({node_type=2}, rpc)
native.call.role_mgr.kick_role(1,2)
native.send.role_mgr.kick_role("3")

battle.call.batttle_mgr.start_battle({})
battle.send.batttle_mgr.start_battle({1}, 2)

输出的内容如下:

1       call    role_mgr        kick_role       1       2
1       send    role_mgr        kick_role       3
2       call    batttle_mgr     start_battle    table: 0x1981cc0
2       send    batttle_mgr     start_battle    table: 0x1981da0        2

如果同一个游戏大区业务,划分在不同节点管理,你可能期望在远程调用时指定游戏区号。所以再次强调以上的远程调用封装,仅仅是一个示例程序,展示的重点是如何利用lua的元表方法,收集远程调用的信息。对于诸如此类项目本身特定的需要,有必要进行二次封闭,同时尽量做到对实际项目最为适用的调用方式。

原文:
https://lizijie.github.io/2023/05/14/%E5%8C%85%E8%A3%85skynet.call&send%E8%BF%9C%E7%A8%8B%E8%B0%83%E7%94%A8%E6%8E%A5%E5%8F%A3.html
作者github:
https://github.com/lizijie

PREVIOUSSourceTree自定义菜单命令 拉取所有仓库最新代码
NEXTJenkins流水线SCM Step检出git和svn代码