菜鸟-创作你的创作

skynet.call使用详细解析

下面给你整理一份 《skynet.call 使用详细解析(超全版)》,从底层原理到使用示例,让你彻底弄懂 Skynet 微服务框架中最核心的服务间通信 API。


一、skynet.call 是什么?

在 Skynet 框架中(Lua 版分布式游戏服务器框架),所有逻辑都是服务(service)之间通过消息通信完成的
消息分两类:

skynet.call 是同步 RPC 调用,会阻塞等待返回结果

local ret = skynet.call(address, "lua", "methodName", arg1, arg2)

调用后当前 coroutine 会挂起,直到目标服务处理完并返回结果。


二、skynet.call 的参数解析

skynet.call 的原型:

skynet.call(addr, typename, ...)

参数解释:

参数说明
addr服务地址,如 :100004serviceName
typename消息协议类型(常用 lua
...实际发送的参数

例如:

local result = skynet.call("userCenter", "lua", "getUserInfo", uid)


三、skynet.call 和 skynet.send 的区别

功能skynet.sendskynet.call
是否阻塞❌ 不阻塞✔ 阻塞等待返回
是否有返回值❌ 无✔ 有
使用场景命令、事件推送需要返回数据的 RPC
性能更轻量略有开销

简单理解:


四、skynet.call 的底层原理(工作流程)

  1. 生成 sessionid
    • 类似于请求唯一 ID
  2. 挂起当前 coroutine
    • 执行 call 的协程被挂起,进入等待队列
  3. 将请求包装成消息发送给目标服务
    • 消息结构:{source, session, data}
  4. 目标服务处理消息并返回应答(response)
  5. 从 session 唤醒协程,并把返回值恢复到协程中
  6. 继续执行 call 后面的代码

过程是完全异步非阻塞的,但对调用者表现为同步。


五、服务端如何响应 skynet.call?

你必须在服务端使用:

方式 1:skynet.dispatch 处理 RPC

local skynet = require "skynet"

skynet.start(function()
    skynet.dispatch("lua", function(session, address, cmd, ...)
        if cmd == "add" then
            local a, b = ...
            skynet.ret(skynet.pack(a + b))  -- 返回结果
        end
    end)
end)

方式 2:使用 skynet.response

skynet.dispatch("lua", function(session, address, cmd, ...)
    local reply = skynet.response()
    if cmd == "hello" then
        reply(true, skynet.pack("Hello from server"))
    end
end)

两种方法都可以正常响应调用者的 call


六、客户端调用示例

local ret = skynet.call(serviceAddr, "lua", "add", 10, 20)
print(ret)   -- 30

非常常用。


七、skynet.call 的返回值详解

建议统一使用 pcall 捕获:

local ok, ret = pcall(skynet.call, addr, "lua", "getInfo")
if not ok then
    skynet.error("RPC call failed:", ret)
end


八、skynet.call 超时机制

Skynet 默认没有超时机制,如果目标服务不返回,调用会永久等待。

常见防止死锁的方法:

  1. 控制子服务逻辑时间
  2. 使用 skynet.timeout 自己模拟超时
  3. 用专门的 timer / watchdog 服务监控

九、call 死锁场景分析(重点)

下面这个情况很危险:

服务 A → call 服务 B

服务 B 又 call 回 A

典型死锁:

A.call(B)
    B.dispatch → call(A)
        A.wait session → 等 B 返回

A 等 B → B 等 A → 死锁

解决方式:


十、skynet.call 的使用最佳实践

✔ 定义 API 层(模块化 RPC 入口)

例如 userService.lua:

local CMD = {}

function CMD.get(uid)
    return db.query(uid)
end

function CMD.add(uid, delta)
    return db.update(uid, delta)
end

return CMD

✔ 使用 skynet.dispatch 来分发

skynet.dispatch("lua", function(session, addr, cmd, ...)
    local f = CMD[cmd]
    skynet.ret(skynet.pack(f(...)))
end)

✔ 调用端统一封装

local user = {}

function user.get(uid)
    return skynet.call(".user", "lua", "get", uid)
end

return user


十一、完整示例(从启动到调用)

1. 服务端 user.lua

local skynet = require "skynet"

local CMD = {}

function CMD.getName(uid)
    return "User_" .. uid
end

skynet.start(function()
    skynet.dispatch("lua", function(session, source, cmd, ...)
        local f = CMD[cmd]
        skynet.ret(skynet.pack(f(...)))
    end)
end)

2. 注册服务

skynet.newservice("user")

3. 客户端 call 调用

local name = skynet.call(".user", "lua", "getName", 1001)
print(name)

输出:

User_1001


十二、总结

功能说明
作用同步 RPC 调用
阻塞✓ 阻塞等待结果
使用场景需要服务器返回值的情况
实现原理session + coroutine 挂起与恢复
注意点死锁、无返回的永久等待

skynet.call 是 Skynet 的基础通信方式,也是构建分布式游戏服务器逻辑的关键。

退出移动版