skynet.newservice接口分析

skynet.newservice接口分析

lua 复制代码
function skynet.newservice(name, ...)
    return skynet.call(".launcher", "lua" , "LAUNCH", "snlua", name, ...)
end

执行流程:

  • 向 .launcher 服务发送一个 Lua 调用
  • 命令:LAUNCH
  • 参数:"snlua" + 服务名 + 其他参数
  • 使用 skynet.call 同步等待结果

Launcher 服务实现

lua 复制代码
local skynet = require "skynet"
local core = require "skynet.core"
require "skynet.manager"    -- import manager apis
local string = string

local services = {}
local command = {}
local instance = {} -- for confirm (function command.LAUNCH / command.ERROR / command.LAUNCHOK)
local launch_session = {} -- for command.QUERY, service_address -> session

local function handle_to_address(handle)
    return tonumber("0x" .. string.sub(handle , 2))
end

local NORET = {}

function command.LIST()
    local list = {}
    for k,v in pairs(services) do
        list[skynet.address(k)] = v
    end
    return list
end

local function list_srv(ti, fmt_func, ...)
    local list = {}
    local sessions = {}
    local req = skynet.request()
    for addr in pairs(services) do
        local r = { addr, "debug", ... }
        req:add(r)
        sessions[r] = addr
    end
    for req, resp in req:select(ti) do
        local addr = req[1]
        if resp then
            local stat = resp[1]
            list[skynet.address(addr)] = fmt_func(stat, addr)
        else
            list[skynet.address(addr)] = fmt_func("ERROR", addr)
        end
        sessions[req] = nil
    end
    for session, addr in pairs(sessions) do
        list[skynet.address(addr)] = fmt_func("TIMEOUT", addr)
    end
    return list
end

function command.STAT(addr, ti)
    return list_srv(ti, function(v) return v end, "STAT")
end

function command.KILL(_, handle)
    skynet.kill(handle)
    local ret = { [skynet.address(handle)] = tostring(services[handle]) }
    services[handle] = nil
    return ret
end

function command.MEM(addr, ti)
    return list_srv(ti, function(kb, addr)
        local v = services[addr]
        if type(kb) == "string" then
            return string.format("%s (%s)", kb, v)
        else
            return string.format("%.2f Kb (%s)",kb,v)
        end
    end, "MEM")
end

function command.GC(addr, ti)
    for k,v in pairs(services) do
        skynet.send(k,"debug","GC")
    end
    return command.MEM(addr, ti)
end

function command.REMOVE(_, handle, kill)
    services[handle] = nil
    local response = instance[handle]
    if response then
        -- instance is dead
        response(not kill)  -- return nil to caller of newservice, when kill == false
        instance[handle] = nil
        launch_session[handle] = nil
    end

    -- don't return (skynet.ret) because the handle may exit
    return NORET
end

local function launch_service(service, ...)
    local param = table.concat({...}, " ")
    local inst = skynet.launch(service, param)
    local session = skynet.context()
    local response = skynet.response()
    if inst then
        services[inst] = service .. " " .. param
        instance[inst] = response
        launch_session[inst] = session
    else
        response(false)
        return
    end
    return inst
end

function command.LAUNCH(_, service, ...)
    launch_service(service, ...)
    return NORET
end

function command.LOGLAUNCH(_, service, ...)
    local inst = launch_service(service, ...)
    if inst then
        core.command("LOGON", skynet.address(inst))
    end
    return NORET
end

function command.ERROR(address)
    -- see serivce-src/service_lua.c
    -- init failed
    local response = instance[address]
    if response then
        response(false)
        launch_session[address] = nil
        instance[address] = nil
    end
    services[address] = nil
    return NORET
end

function command.LAUNCHOK(address)
    -- init notice
    local response = instance[address]
    if response then
        response(true, address)
        instance[address] = nil
        launch_session[address] = nil
    end

    return NORET
end

function command.QUERY(_, request_session)
    for address, session in pairs(launch_session) do
        if session == request_session then
            return address
        end
    end
end

-- for historical reasons, launcher support text command (for C service)

skynet.register_protocol {
    name = "text",
    id = skynet.PTYPE_TEXT,
    unpack = skynet.tostring,
    dispatch = function(session, address , cmd)
        if cmd == "" then
            command.LAUNCHOK(address)
        elseif cmd == "ERROR" then
            command.ERROR(address)
        else
            error ("Invalid text command " .. cmd)
        end
    end,
}

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)

skynet.start(function() end)

在 launcher.lua 中:

lua 复制代码
skynet.start(function() end)

这是一个空的启动函数,即launcher 服务启动后立即进入消息循环状态,等待处理命令

LAUNCH 命令处理

lua 复制代码
function command.LAUNCH(_, service, ...)
    launch_service(service, ...)
    return NORET
end

NORET 是一个特殊标记,表示这个命令不会立即返回结果

核心:launch_service 函数

lua 复制代码
local function launch_service(service, ...)
    -- 1. 构建参数字符串
    local param = table.concat({...}, " ")
    
    -- 2. 启动服务(核心调用)
    local inst = skynet.launch(service, param)
    
    -- 3. 获取当前会话上下文
    local session = skynet.context()
    
    -- 4. 创建响应对象
    local response = skynet.response()
    
    if inst then
        -- 5. 记录服务信息
        services[inst] = service .. " " .. param
        instance[inst] = response        -- 保存响应回调
        launch_session[inst] = session   -- 保存会话ID
        
        -- 注意:这里没有立即返回!
    else
        -- 启动失败,立即返回 false
        response(false)
        return
    end
    return inst
end

skynet.launch 的底层实现

skynet.launch 是 C 函数,在 skynet_server.c 中实现:

c 复制代码
static const char *
cmd_launch(struct skynet_context * context, const char * param) {
    size_t sz = strlen(param);
    char tmp[sz+1];
    strcpy(tmp,param);
    char * args = tmp;
    char * mod = strsep(&args, " \t\r\n");
    args = strsep(&args, "\r\n");
    struct skynet_context * inst = skynet_context_new(mod,args);
    if (inst == NULL) {
        return NULL;
    } else {
        id_to_hex(context->result, inst->handle);
        return context->result;
    }
}

它调用底层的 skynet_command 函数,命令为 "LAUNCH"

服务启动的异步流程

关键点 : 服务启动是异步的!
时序图

bash 复制代码
caller  ->  launcher(LAUNCH)  ->  C层创建服务  ->  新服务初始化  ->  通知launcher
   |              |                    |                  |              |
   |-- 等待响应  --|                    |                  |              |
   |              |-- 记录pending响应  -|                  |              |
   |              |                    |-- 启动snlua服务  -|              |
   |              |                    |                  |-- 执行init  --|
   |              |                    |                  |-- 成功: 发送"" --|
   |              |                    |                  |-- 失败: 发送"ERROR"|
   |              |<- 收到通知,调用response ------------|
   |<- 收到响应  --|

服务初始化完成的通知

当新服务初始化完成后,会通过文本协议通知 launcher:

lua 复制代码
skynet.register_protocol {
    name = "text",
    id = skynet.PTYPE_TEXT,
    unpack = skynet.tostring,
    dispatch = function(session, address, cmd)
        if cmd == "" then
            command.LAUNCHOK(address)  -- 启动成功
        elseif cmd == "ERROR" then
            command.ERROR(address)     -- 启动失败
        else
            error ("Invalid text command " .. cmd)
        end
    end,
}

LAUNCHOK 和 ERROR 处理

lua 复制代码
function command.LAUNCHOK(address)
    -- 初始化成功通知
    local response = instance[address]
    if response then
        response(true, address)        -- 回调:返回 true 和地址
        instance[address] = nil        -- 清理
        launch_session[address] = nil
    end
    return NORET
end

function command.ERROR(address)
    -- 初始化失败
    local response = instance[address]
    if response then
        response(false)                -- 回调:返回 false
        launch_session[address] = nil
        instance[address] = nil
    end
    services[address] = nil            -- 从服务列表中移除
    return NORET
end

关键数据结构说明

lua 复制代码
-- 全局服务表:地址 -> 服务描述
local services = {}

-- 待确认的服务实例:地址 -> response回调函数
local instance = {}

-- 启动会话映射:地址 -> 会话ID  
local launch_session = {}

完整的启动流程总结

  1. 调用阶段:newservice → launcher:LAUNCH
  2. 记录阶段:launcher 记录 pending 状态,不立即返回
  3. 创建阶段:C 层创建新服务进程
  4. 初始化阶段:新服务执行初始化代码
  5. 通知阶段:新服务向 launcher 发送启动结果
  6. 回调阶段:launcher 调用对应的 response 回调
  7. 返回阶段:原始调用方收到启动结果
lua 复制代码
-- 用户调用
local my_service = skynet.newservice("myservice", "param1", "param2")

-- 实际执行:
-- 1. 调用 .launcher:LAUNCH("snlua", "myservice", "param1", "param2")
-- 2. launcher 调用 skynet.launch("snlua", "myservice param1 param2")
-- 3. 新服务 myservice 开始初始化
-- 4. 初始化成功:myservice 向 launcher 发送空字符串 ""
-- 5. launcher 调用 response(true, address)
-- 6. 用户收到服务地址

错误处理机制

lua 复制代码
-- 如果 skynet.launch 返回 nil,立即失败
if inst then
    -- 正常流程
else
    response(false)  -- 立即返回失败
    return
end

-- 如果服务初始化过程中出错
function command.ERROR(address)
    response(false)  -- 异步返回失败
    services[address] = nil
end

为什么使用异步设计?

  • 服务初始化可能需要时间(加载文件、连接数据库等)
  • 避免阻塞 launcher 服务处理其他请求
  • 支持服务的依赖初始化
相关推荐
陈小桔3 小时前
Springboot之常用注解
java·spring boot·后端
我的xiaodoujiao3 小时前
从 0 到 1 搭建 Python 语言 Web UI自动化测试学习系列 15--二次开发--封装公共方法 3
python·学习·测试工具
立志成为大牛的小牛3 小时前
数据结构——十四、构造二叉树(王道408)
数据结构·笔记·学习·程序人生·考研
数据知道3 小时前
Go基础:一文掌握Go语言泛型的使用
开发语言·后端·golang·go语言
RanceGru3 小时前
LLM学习笔记5——本地部署ComfyUI和Wan2.1-T2V-1.3B文生视频模型
笔记·学习·stable diffusion·transformer
koo3644 小时前
李宏毅机器学习笔记18
笔记
molong9314 小时前
Activity/Service/Broadcast/ContentProvider 生命周期交互
android·学习·交互
楼田莉子4 小时前
python学习:爬虫+项目测试
后端·爬虫·python·学习
x_lrong4 小时前
个人AI环境快速搭建
人工智能·笔记