首页 > 其他分享 >skynet.newservice简介:服务的启动

skynet.newservice简介:服务的启动

时间:2024-05-22 22:58:48浏览次数:27  
标签:end service -- 简介 newservice lua skynet response

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的代码实现

--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服务相关的代码:

--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服务:

--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中找到它的使用:

--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_service

新服务启动时,调用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_service_exit

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

新服务启动的时候,无论是用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_service_error

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

skynet.newservice(service_name, ...)后面是可以带多个参数的,但这些参数只能是数字或字符串,回看前面的skynet.launch的代码,里面是调用了c.command("LAUNCH", table.concat({...}," ")),这里可以看到,传递的参数通过table.concat打包成字符串,以空格隔开。如果我们的参数中带有空格,或者我们想要传个table,那就不支持了。
通常来说,我们可以先启动服务,在skynet.start中做些简单的功能,调用skynet.dispatch("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 服务的启动

标签:end,service,--,简介,newservice,lua,skynet,response
From: https://www.cnblogs.com/lcc9527/p/18207305

相关文章

  • 封装 ECharts 为 Vue 组件:X-ECharts 简介
    ECharts是一个广泛使用的开源可视化库,它提供了丰富的图表类型和灵活的配置选项,适用于复杂的数据可视化需求。而X-ECharts是一个基于ECharts封装的Vue组件库,旨在提供更简洁的集成方式,同时兼容Vue2和Vue3,使得开发者能够在不同版本的Vue项目中无缝使用ECharts。Eng......
  • Flowable工作流简介(二)
    1、简介Flowable提供了一个组高效的核心开源业务流程引擎,为开发人员,系统管理员和业务用户提供工作流和业务流程管理(BPM)平台。全部用Java编写,并且基于Apache2.0许可的开源,代码在社区维护。其核心是一个快速,经过试验和测试的动态BPMN流程引擎,附带DMN决策表和CMMNCase管理引擎.2......
  • Flowable工作流简介
    1. 简介Flowable是一个使用Java编写的轻量级业务流程引擎。Flowable流程引擎可用于部署BPMN2.0流程定义(用于定义流程的行业XML标准),创建这些流程定义的流程实例,进行查询,访问运行中或历史的流程实例与相关数据等。Flowable可以十分灵活地加入你的应用/服务/构架。可以将JAR形式......
  • skynet框架:单点服务性能优化思路
    skynet框架下的业务开发,单点服务是存在理论瓶颈的。当业务上存在并发请求的场景时,服务会成为性能热点,达到服务的消费瓶颈,出现过载。原则上讨论,当业务需求一个执行单位成为并发热点,那么实现这个执行单位就需要是足够支撑业务上限的方案。基于这个思路,讨论几个优化:解耦;降低过载......
  • 工作流框架Flowable 简介
     Flowable基本操作1.创建ProcessEngine创建一个基本的maven工程,可以是Eclipse也可以是其他IDEA。然后添加两个依赖Flowable流程引擎。使我们可以创建一个ProcessEngine流程引擎对象,并访问FlowableAPI。一个是MySQL的数据库驱动在pom.xml文件中添加下列行:<dependency>......
  • 消防二总线通信原理简介
    典型应用图 首先需要设计一种可编码寻址通用联动控制接口件。一方面它作为二总线上的挂接设备——能被区域控制器寻址,同时返回自身的状态信息;另一方面当区域控制器需要联动它所控制的消防设备时能提供控制触点。如图①,通用联动接口通过桥式输入电路与总线相联,桥式输入电......
  • OpenVX代码优化裁减简介
    OpenVX代码优化裁减简介 在OpenVX中,裁减(Reduction)是一种操作,它对数组或图像中的元素执行聚合操作。这里的“裁减”是指将大型数组或图像减少到单一数值的过程。OpenVX提供了几种不同的裁减操作,包括求和(Summation)、平均(Average)、最小值(Minimum)、最大值(Maximum)和累加器(Accumulat......
  • [20240515]vim bccalc_XXX.vim使用插件简介.txt
    [20240515]vimbccalc_XXX.vim使用插件简介.txt--//这是我改写vim.org网站的一个调用bc做计算的一个插件bccalc.vim,感觉自己越写越复杂.做一个介绍,便于自己查阅.--//另外注意如果选择多行,结尾要像C语言一样使用分号(;).--//我已经统一在selectvisualnormal模式都是<leader>作......
  • Android/iOS版本号机制简介
    Android和iOS有各自的版本号机制,但都根据版本号的用途,分为了2类:程序向的构建版本号:用户不可见影响APP的功能:覆盖安包(低版本覆盖高版本会提示异常或失败)影响APP商店提审:每个提审包都必须必上一次版本号高用户向的显示版本号:用户可见影响APP的版本号显示:系统设置里可......
  • Linux 文件系统(三) --- overlayfs简介
    PS:要转载请注明出处,本人版权所有。PS:这个只是基于《我自己》的理解,如果和你的原则及想法相冲突,请谅解,勿喷。环境说明  无前言  对于overlay文件系统来说,我以前只是听过,具体貌似docker里面使用了相关技术,但是也仅仅限于听过了。  最近,由于需要通过tar来备份一个系......