skynet.newservice简介:服务的启动

skynet是一个轻量级的游戏服务器框架。

简介

skynet的体系中,服务 是一个基础概念。通常,我们使用skynet.newservice来启动一个snlua服务。

那么,当我们写下local addr = skynet.newservice("test")这行代码时,系统是怎么运作的呢?

思考一下这些问题:

  • 调用skynet.newservice会不会发生阻塞?
  • 如果test服务在skynet.start时调用了skynet.exitaddr会是什么值?
  • 如果test服务在skynet.start时出现错误,addr又会是什么值?
  • test服务是不是一定要调用skynet.start
  • 如果要传一些复杂的参数,又要怎么做?

skynet.newservice的代码实现

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

skynet.newservice的代码很简单,只调用了一个skynet.call,而skynet.call是阻塞的,所以skynet.newservice也是阻塞的。

这个call发了一条lua消息给.launcher服务,接下来看看.launcher服务相关的代码:

lua 复制代码
--skynet/service/launcher.lua
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

这里又调用到skynet.launch,实际上是发了个LAUNCH指令到底层,这里创建了一个snlua服务:

lua 复制代码
--skynet/lualib/skynet/manager.lua
function skynet.launch(...)
	local addr = c.command("LAUNCH", table.concat({...}," "))
	if addr then
		return tonumber(string.sub(addr , 2), 16)
	end
end

要注意前面的.launcher服务的command.LAUNCH函数是忽略返回的,所以此时skynet.newservice还处于阻塞状态,等待.launcher的返回。

那什么时候会返回响应呢?

回到前面的launch_service函数,可以看到skynet.launch成功后并没有直接返回,而是生成一个响应函数response,存储在表instance中。

搜索这个instance,我们可以在command.LAUNCHOK中找到它的使用:

lua 复制代码
--skynet/service/launcher.lua
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

也就是说,要等到.launcher服务收到LAUNCHOK的指令之后,才会返回给newservice的调用者。

问题又来了,什么时候发送LAUNCHOK呢?答案是在skynet.init_service中。

而调用skynet.init_service的,一共有三个函数:

  • skynet.start
  • skynet.forward_type
  • skynet.filter

所以,在服务的启动脚本中,我们必须调用这三个函数中的其中一个(通常都是skynet.start),否则的话,调用方永远都收不到返回的数据。

以在main服务中,创建新服务test为例,流程如下图所示:

新服务启动时,调用skynet.exit,调用者收到的addr是什么?

我们看一下skynet.exit:

复制代码
--skynet/lualib/skynet.lua
function skynet.exit()
	fork_queue = { h = 1, t = 0 }	-- no fork coroutine can be execute after skynet.exit
	skynet.send(".launcher","lua","REMOVE",skynet.self(), false)

	--其他代码...
	--...
end

这里看到,新服务发送了REMOVE指令到.launcher服务,而.launcherREMOVE的处理如下:

复制代码
--skynet/service/launcher.lua
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

对于刚启动的服务来说,这里会调用到对应的responseresponse需要两个参数,这里第一个参数是true,第二个参数为nil,而第二个参数是返回地址,也就是说,调用者收到的addrnil值。

新服务启动报错的话,又返回什么呢

新服务启动的时候,无论是用skynet.start还是skynet.forward_type,最终都是调用skynet.init_service,来看看代码:

复制代码
--skynet/lualib/skynet.lua
function skynet.init_service(start)
	local function main()
		skynet_require.init_all()
		start()
	end
	local ok, err = xpcall(main, traceback)
	if not ok then
		skynet.error("init service failed: " .. tostring(err))
		skynet.send(".launcher","lua", "ERROR")
		skynet.exit()
	else
		skynet.send(".launcher","lua", "LAUNCHOK")
	end
end

可以看到,对start函数的调用,是通过xpcall来调用的,如果报错的话,会发送ERROR.launcher服务。

复制代码
--skynet/service/launcher.lua
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

这里response参数是falseresponseskynet.response生成的一个函数,相关代码如下:

复制代码
--skynet/lualib/skynet.lua
function skynet.response(pack)
    --其他代码...
    --...
    local function response(ok, ...)
        --其他代码...
        --...
        if ok then
            ret = c.send(co_address, skynet.PTYPE_RESPONSE, co_session, pack(...))
            if ret == false then
                -- If the package is too large, returns false. so we should report error back
                c.send(co_address, skynet.PTYPE_ERROR, co_session, "")
            end
        else
            ret = c.send(co_address, skynet.PTYPE_ERROR, co_session, "")
        end
        --其他代码...
        --...
    end
    --其他代码...
    --...

    return response
end

可以看到,当传入的okfalse的时候,会发送一个PTYPE_ERROR类型的消息给调用者。

而当我们require"skynet"时,对PTYPE_ERROR默认的处理函数是_error_dispatch,具体的流程可以看看源码,这里简而言之,就是调用call的那条协程会触发一个call failerror

所以,当新服务的启动函数出错时,在新服务中会报错,中断,而调用者在skynet.call()中也会报call fail的错,从而中断执行,也就不会有addr的返回了。

如果启动服务要传比较复杂的参数,要怎么做比较好

skynet.newservice(service_name, ...)后面是可以带多个参数的,但这些参数只能是数字或字符串,回看前面的skynet.launch的代码,里面是调用了c.command("LAUNCH", table.concat({...}," ")),这里可以看到,传递的参数通过table.concat打包成字符串,以空格隔开。如果我们的参数中带有空格,或者我们想要传个table,那就不支持了。

通常来说,我们可以先启动服务,在skynet.start中做些简单的功能,调用skynet.dispatch("lua", ...)来处理lua消息,通过lua消息来做初始化,这样就能传送复杂的参数了:

lua 复制代码
local addr  = skynet.newservice("test")
skynet.send(addr, "lua", "start", {address='0.0.0.0',port=8888,nodelay=true})

总结

现在,我们可以回答最初的问题了:

  • 调用skynet.newservice会不会发生阻塞?

    • 会阻塞,如果服务没启动完,会一直等待下去。
  • 如果test服务在start时调用了exitaddr会是什么值?

    • nil
  • 如果test服务在start时出现错误,addr又会是什么值?

    • skynet.newservice会报错,没有返回值
  • test服务是不是一定要调用skynet.start

    • 不一定,也可以调用skynet.forward_typeskynet.filter
  • 如果要传一些复杂的参数,又要怎么做?

    • 将服务的创建和启动分开,创建后发送lua消息初始化服务。

最后再思考一个问题:启动系统的时候,第一个服务又是什么时候启动的呢?答案可以看看这里:skynet 之 main 服务的启动

相关推荐
孟无岐5 小时前
【Laya】LocalStorage 本地存储
typescript·游戏引擎·游戏程序·laya
怣疯knight11 小时前
外部类触发角色状态切换
游戏程序
孟无岐2 天前
【Laya】Byte 二进制数据处理
网络·typescript·游戏引擎·游戏程序·laya
孟无岐2 天前
【Laya】ClassUtils 类反射工具
typescript·游戏引擎·游戏程序·laya
孟无岐3 天前
【Laya】Ease 缓动函数
typescript·游戏引擎·游戏程序·laya
孟无岐4 天前
【Laya】Scene3D 介绍
typescript·游戏引擎·游戏程序·laya
孟无岐4 天前
【Laya】Sprite3D 介绍
typescript·游戏引擎·游戏程序·laya
WaWaJie_Ngen5 天前
C++实现一笔画游戏
c++·算法·游戏·游戏程序·课程设计
Dr.勿忘5 天前
MUMU模拟器adb连接失败:cannot connect to 127.0.0.1:16384: 由于目标计算机积极拒绝,无法连接。 (10061)
游戏·unity·adb·游戏程序·调试·模拟器
真鬼1235 天前
植物大战僵尸杂交版v3.14与重置版v0.14最新版本(附下载链接)
游戏程序