Skynet是由一个或多个进程构成的,每个进程被称为一个Skynet节点。
启动Skynet节点时需使用 skynet config_filename
命令,运行启动指令时需要传入一个配置文件的文件名config_filename
作为启动参数,Skynet会读取配置文件中参数完成初始化等操作。
Skynet进程启动后,首先会读取config_filename
配置文件,然后解析这个Lua文件,再把相关配置信息设置到Lua的环境变量中。
以源码为例,Skynet启动时使用example
文件夹下的config.lua
配置文件。
./skynet example/config
查看skynet_main.c
文件中C代码的main
入口函数,位于skynet-src/skynet_main.c
文件中。
skynet_main.c
文件是干什么用的呢?
skynet_main.c
主要是设置Lua的环境、设置默认的配置、打开配置文件、修改默认配置,最后会调用skynet_start
函数。
skynet_start
函数位于skynet-src/skynet_start.c
文件中,其主要作用是初始化Skynet中的各个模块,包括horbor
节点、handle
服务ID、mq
消息队列、module
模块加载动态链接库、timer
时钟、socket
套接字以及加载部分服务的logger
日志服务、master
主服务、harbor
节点服务、snlua
加载Lua模块的服务等。最后启动线程,包括_monitor
、_timer
、_socket
,并根据线程数量启动多个工作线程。
int main(int argc, char *argv[])
{
const char *config_file = NULL;//定义配置文件路径
if (argc > 1)
{
config_file = argv[1];//保存配置文件名称
}
else
{
fprintf(stderr, "Need a config file. Please read skynet wiki : https://github.com/cloudwu/skynet/wiki/Config\n"
"usage: skynet configfilename\n");
}
sigign(); // 设置信号处理函数
env_env_init();//初始化保存配置文件的环境,该函数在env.c文件中,与之对应的是skynet_env.c文件。
}
需要注意的是配置文件的路径会保存到config_file
指针上,那么它是如何加载的呢?这一点要涉及到Lua C API。
打开example/config
文件,了解下配置文件中的各个参数。
$ cat example/config.lua
include "config.path"
-- preload = "./examples/preload.lua" -- run preload.lua before every lua service run
thread = 8
logger = nil
logpath = "."
harbor = 1
address = "127.0.0.1:2526"
master = "127.0.0.1:2013"
start = "main" -- main script
bootstrap = "snlua bootstrap" -- The service for bootstrap
standalone = "0.0.0.0:2013"
-- snax_interface_g = "snax_g"
cpath = root.."cservice/?.so"
-- daemon = "./skynet.pid"
根据config.lua
配置文件可以发现,它首先需要加载当前文件夹下的config.path.lua
路径配置文件。
include "config.path"
$ cat example/config.path.lua
root = "./"
luaservice = root.."service/?.lua;"..root.."test/?.lua;"..root.."examples/?.lua;"..root.."test/?/init.lua;"..root.."workspace/?.lua"
lualoader = root .. "lualib/loader.lua"
lua_path = root.."lualib/?.lua;"..root.."lualib/?/init.lua"
lua_cpath = root .. "luaclib/?.so"
snax = root.."examples/?.lua;"..root.."test/?.lua"
Skynet是如何读取配置文件的呢?
启动Skynet时传入配置文件的example/config.lua
,实际上会被当做一段Lua代码加载并以key = value
键值对的形式为配置项赋值。
Skynet中必要的配置会保存在struct skynet_config config
结构体中,暂时不用的配置项会以字符串的形式保存在Skynet内部的env
表中,用户自定义的配置可通过skynet.getenv
获取。
Skynet配置参数可以分为哪几类呢?
- 必要配置项
- 可选配置项
- Lua环境配置项
- 集群配置项
- 用户配置项
必要配置项
thread
thread = 8
thread
表示启动的工作线程数量,通常不要将工作线程的数量设置的超过实际拥有的CPU的核心数量。
可以将thread
配置到SKYNET_THREAD
环境变量中
thread = $SKYNET_THREAD
bootstrap
bootstrap = "snalu bootstrap"
Skynet中log
和bootstrap
是启动时的两个服务,默认的boostrap
配置项为snlua bootstrap
,意味着Skynet会启动snlua
这个服务,并将bootstrap
作为参数传递给它。
bootstrap
设置Skynet启动时的第一个服务以及其启动参数,默认配置为snlua bootstrap
,即启动一个名为bootstrap
的lua
服务,通常指的是service/bootstrap.lua
。
snlua
是Lua沙盒服务,bootstrap
会根据luaservice
匹配到最终Lua脚本。若按默认配置,这个脚本是service/bootstrap.lua
。
bootstrap
最后一行会从config
中读取start
配置项,这个配置项才是用户定义的启动脚本。作为用户定义的服务启动入口脚本运行,启动成功后则会把自己退出。
其实bootstrap
是Skynet启动的第二个服务,第一个服务应该是logger
,为什么这么说呢?
Skynet通过skynet_context_new
函数实例化一个服务,它首先会从logger.so
文件中把模块加载进来。当启动logger
服务完成后,接下来要启动的服务才是bootstrap
,不过在这之前需要先加载snlua
模块,因为所有的Lua服务都属于snlua
模块的实例。和加载logger
服务类似,Skynet先会把snlua.so
文件作为模块加载进来,然后调用模块自身的_create
函数来产生一个snlua
实例(service_snlua.c)。产生snlua
实例的目的在于生成一个新的Lua虚拟机,也就是Lua的沙盒环境。这一点非常重要,因为所有的Lua服务都是在一个独立的Lua虚拟机中运行的。
skynet_context_new
函数是干什么用的呢?
在C语言实现中skynet_context_new
函数位于skynet-src/skynet_server.c
文件中,skynet_context_new
函数主要用来实例化服务动态链接库中的_create
和_init
函数,以及为服务创建一个私有的消息队列,并填充到struct skynet_context
结构中。
start
start
设置的是bootstrap
的最后一个节点,也就是将要启动的Lua服务,即定制Skynet节点的主程序,默认为main
。简单来说,就是启动main.lua
这个脚本。
这个Lua服务的路径由luaservice
指定。简单来说,main
是bootstrap
结束后启动的用户自定义服务,可以把它当作是启动项。
cpath
cpath = skynet_root.."cservice/?.so"
cpath
设置使用C编写的服务模块的位置,通常指cservice
目录下.so
文件,若系统的动态库不是以.so
为后缀,只需做相应的修改,这个路径可以配置多项,并以;
分割。
start = "main"
日志配置
logger
logger = nil
logger
决定了Skynet内置的skynet_error
这个C语言API将日志信息输出的位置。
若logger
设置为nil
则表示输出到标准输出,可以指定一个路径和文件名用来将日志输入到文件中。
logservice
logservice = "logger"
logservice
配置用户定制的日志服务,可参考service_logger.c
来实现。如果希望使用Lua编写这个服务,可设置为snlua
,然后在logger
配置项中设置具体的Lua服务的名字,可参考example/config.userlog
文件。
logpath
logpath = ""
logpath
配置日志保存的文件名,当运行时为某个服务打开log
时,这个服务所有的输入消息都会被记录到此目录下,文件名为服务地址。
网络配置
harbor
harbor = 0
Skynet可以启动多个节点,不同节点内的服务地址是相互唯一的,服务地址是一个32bit的整数。同一进程内的地址是高8位相同,低8位区分一个服务处理哪一个节点。
每个系欸但中都有一个特殊的服务叫做handor
(港口),当一个消息的目的地的高8位和当前节点不同时,消息被投递到harbor
服务中,最后通过TCP连接传输的目的地节点的harbor
服务。
harbor
可以是1到255之间的任意整数,一个Skynet网络最多支持255个节点。每个节点必有一个唯一的编号。如果harbor
为0则表示Skynet工作在单节点模式下(单点模式),此时master
、address
、standalone
都不必设置。
master
不同的Skynet节点之间是如何建立其网络的呢?
这依赖一个叫做master
的服务,master
服务可以单独作为一个进程,也可以附属在某个Skynet节点内部(默认配置)。
master
服务会监听一个端口(在配置文件中的standalone
选项设置),每个Skynet节点都根据配置中的master
去连接最终一个有5个节点的Skynet网络。
master = "127.0.0.1:2013"
master
设置Skynet控制中心的地址和端口,若已经配置了standalone
选项,那么此配置项和standalone
相同。
address
address = "127.0.0.1:2526"
address
设置当前Skynet节点的地址和端口,方便其它节点进行组网。即使只有一个Skynet节点也需要开启控制中心,并额外配置节点的地址和端口。
standalone
standalone = "0.0.0.0:2013"
如果把Skynet进程作为主进程启动,由于Skynet可以由分布在多台机器上的多个进程构成。那么就需要配置standalone
这个选项,表示这个进程是主节点,它需要开启一个控制中心,监听一个端口,让其它节点接入。
集群配置
cluster
cluster = ""
cluster
配置决定了集群配置文件的路径
Lua环境配置项
Lua服务由snlua
提供,snlua
会查询一些配置项以加载Lua代码。
lualoader
lualoader = skynet_root.."lualib/loader.lua"
lualoader
用于配置调用哪一段Lua代码加载Lua服务,通常配置为lualib/loader.lua
,由这段代码解析服务名称,进一步加载Lua代码。
snlua
会将核心配置项取出,放置在初始化好的Lua虚拟机的全局变量中。
-
SERVICE_NAME
服务名称 -
LUA_PATH
配置文件中的lua_path
-
LUA_CPATH
配置文件中的lua_cpath
-
LUA_PRELOAD
配置文件中的lua_preload
-
LUA_SERVICE
配置文件中的luaservice
luaservice
luaservice
指定了Lua服务代码所在的位置,可配置多项以分号;
分隔。
如果在创建Lua服务时是以一个文件夹而非单个文件,那么最终找到的路径还是会被添加到package.path
中。
lua_path
lua_path = skynet_root.."lualib/?.lua;"..skynet_root.."lualib/?/init.lua"
将添加到package.path
中的路径提供给require
调用
lua_cpath
lua_cpath = skynet_root.."luaclib/?.so;"
将添加到package.cpath
中的路径提供给require
调用
preload
preload = ""
在设置完package
中的路径后,加载Lua服务代码前,loader
会尝试先运行一个preload
指定的脚本,默认为空。
snax
snax = ""
使用snax
框架编写的服务的查找路径
其它配置
root
root = "./"
root
表示根目录是Skynet启动时的目录,用于设置当前项目的根目录。
skynet_root
skynet_root = "lib/skynet/"
设置Skynet的根目录
daemon
daemon = "./skynet.pid"
daemon
可以以后台模式启动Skynet,注意需同时配置logger
项的输出log
。
最佳实践
$ vim config/config_linux.tpl
root="../"
thread = 4
logger = nil
logpath = "."
harbor = 0
-- main script
start = "main"
-- The service for bootstrap
bootstrap = "snlua bootstrap"
luaservice = "../service/?.lua;../business/?.lua;../libs/service/?.lua;"
lualoader = "../libs/lualib/loader.lua"
lua_path = "../lualib/?.lua;../libs/lualib/?.lua;../libs/lualib/?/init.lua;../business/?.lua;../model/?.lua;../const/?.lua"
lua_cpath = "../luaclib/?.so;../libs/luaclib/?.so"
cpath = "../libs/cservice/?.so"
-- daemon
-- daemon = "../game.pid"
-- debug_console
debugport = 9000
wshost = "0.0.0.0:8888"
httpport = 9999
db_mysql_master = '{
"host":"",
"port":3306,
"user":"",
"password":"",
"database":""
}'
db_mysql_slave = '[
{
"host":"",
"port":3306,
"user":"",
"password":"",
"database":""
}
]'
db_redis = '[
{
"host":"",
"port":6379,
"db":4,
"auth":""
}
]'
http_snlua_num = 10
mysql_snlua_num = 2
redis_snlua_num = 10
未完待续...