skynet.newservice简介:服务的启动
skynet是一个轻量级的游戏服务器框架。
简介
在skynet
的体系中,服务是一个基础概念。通常,我们使用skynet.newservice
来启动一个snlua
服务。
那么,当我们写下local addr = skynet.newservice("test")
这行代码时,系统是怎么运作的呢?
思考一下这些问题:
- 调用
skynet.newservice
会不会发生阻塞? - 如果
test
服务在skynet.start
时调用了skynet.exit
,addr
会是什么值? - 如果
test
服务在skynet.start
时出现错误,addr
又会是什么值? test
服务是不是一定要调用skynet.start
?- 如果要传一些复杂的参数,又要怎么做?
skynet.newservice的代码实现
1 |
|
skynet.newservice
的代码很简单,只调用了一个skynet.call
,而skynet.call
是阻塞的,所以skynet.newservice
也是阻塞的。
这个call
发了一条lua
消息给.launcher
服务,接下来看看.launcher
服务相关的代码:
1 |
|
这里又调用到skynet.launch
,实际上是发了个LAUNCH
指令到底层,这里创建了一个snlua
服务:
1 |
|
要注意前面的.launcher
服务的command.LAUNCH
函数是忽略返回的,所以此时skynet.newservice
还处于阻塞状态,等待.launcher
的返回。
那什么时候会返回响应呢?
回到前面的launch_service
函数,可以看到skynet.launch
成功后并没有直接返回,而是生成一个响应函数response
,存储在表instance
中。
搜索这个instance
,我们可以在command.LAUNCHOK
中找到它的使用:
1 |
|
也就是说,要等到.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
:
1 |
|
这里看到,新服务发送了REMOVE
指令到.launcher
服务,而.launcher
对REMOVE
的处理如下:
1 |
|
对于刚启动的服务来说,这里会调用到对应的response
,response
需要两个参数,这里第一个参数是true
,第二个参数为nil
,而第二个参数是返回地址,也就是说,调用者收到的addr
是nil
值。
新服务启动报错的话,又返回什么呢
新服务启动的时候,无论是用skynet.start
还是skynet.forward_type
,最终都是调用skynet.init_service
,来看看代码:
1 |
|
可以看到,对start
函数的调用,是通过xpcall
来调用的,如果报错的话,会发送ERROR
到.launcher
服务。
1 |
|
这里response
参数是false
,response
是skynet.response
生成的一个函数,相关代码如下:
1 |
|
可以看到,当传入的ok
是false
的时候,会发送一个PTYPE_ERROR
类型的消息给调用者。
而当我们require"skynet"
时,对PTYPE_ERROR
默认的处理函数是_error_dispatch
,具体的流程可以看看源码,这里简而言之,就是调用call
的那条协程会触发一个call fail
的error
。
所以,当新服务的启动函数出错时,在新服务中会报错,中断,而调用者在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
消息来做初始化,这样就能传送复杂的参数了:
1 |
|
总结
现在,我们可以回答最初的问题了:
-
调用
skynet.newservice
会不会发生阻塞?- 会阻塞,如果服务没启动完,会一直等待下去。
-
如果
test
服务在start
时调用了exit
,addr
会是什么值?- nil
-
如果
test
服务在start
时出现错误,addr
又会是什么值?- skynet.newservice会报错,没有返回值
-
test
服务是不是一定要调用skynet.start
?- 不一定,也可以调用
skynet.forward_type
或skynet.filter
- 不一定,也可以调用
-
如果要传一些复杂的参数,又要怎么做?
- 将服务的创建和启动分开,创建后发送
lua
消息初始化服务。
- 将服务的创建和启动分开,创建后发送
最后再思考一个问题:启动系统的时候,第一个服务又是什么时候启动的呢?答案可以看看这里:skynet 之 main 服务的启动