Skynet服务器框架(四) Lua服务创建和启动剖析
2017-04-17 14:13
561 查看
前言:
之前从Skynet启动过程,解读了skynet的启动部分C语言编写的底层源码 Skynet服务器框架(二)C源码剖析启动流程,最后成功启动了引导的lua服务bootstrap.lua,接下来我们要尝试自定义一个lua服务,并让它启动起来。
bootstrap实现功能:
bootstrap.lua
源码:
local skynet = require "skynet" local harbor = require "skynet.harbor" require "skynet.manager" -- import skynet.launch, ... local memory = require "memory" skynet.start(function() local sharestring = tonumber(skynet.getenv "sharestring" or 4096) memory.ssexpand(sharestring) local standalone = skynet.getenv "standalone" local launcher = assert(skynet.launch("snlua","launcher")) skynet.name(".launcher", launcher) local harbor_id = tonumber(skynet.getenv "harbor" or 0) if harbor_id == 0 then assert(standalone == nil) standalone = true skynet.setenv("standalone", "true") local ok, slave = pcall(skynet.newservice, "cdummy") if not ok then skynet.abort() end skynet.name(".cslave", slave) else if standalone then if not pcall(skynet.newservice,"cmaster") then skynet.abort() end end local ok, slave = pcall(skynet.newservice, "cslave") if not ok then skynet.abort() end skynet.name(".cslave", slave) end if standalone then local datacenter = skynet.newservice "datacenterd" skynet.name("DATACENTER", datacenter) end skynet.newservice "service_mgr" --根据Conifg中配置的start项启动下一个lua服务,假如无此项配置,则启动main.lua pcall(skynet.newservice,skynet.getenv "start" or "main") --退出bootstrap服务 skynet.exit() end)
源码解析:
内容解析,在官方的文档Bootstrap已经做了详细的阐述:这段脚本通常会:
根据
standalone配置项判断你启动的是一个
master 节点还是
slave 节点。如果是
master 节点还会进一步的通过
harbor是否配置为 0 来判断你是否启动的是一个单节点 skynet 网络。
单节点模式下:
是不需要通过内置的
harbor 机制做节点间通讯的。但为了兼容(因为你还是有可能注册全局名字),需要启动一个叫做
cdummy的服务,它负责拦截对外广播的全局名字变更。
多节点模式:
对于
master 节点,需要启动
cmaster 服务作节点调度用。此外,每个节点(包括
master 节点自己)都需要启动
cslave 服务,用于节点间的消息转发,以及同步全局名字。
接下来在
master 节点上,还需要启动
DataCenter 服务。
然后,启动用于
UniqueService管理的
service_mgr。
最后,它从
config中读取
start这个配置项,作为用户定义的服务启动入口脚本运行。成功后,把自己退出。
这个
start配置项,才是用户定义的启动脚本,默认值为
"main",对应
main.lua脚本。
创建自定义lua服务:
在编写自定义的lua服务时,可以查询skynet lua开发的官方API首先在test目录下(也可以选择其他目录,只要是
config配置文件中
luaservice项包含的目录即可)创建一个我们自己的lua脚本,这里我取名为
firsttest.lua,表示自定义第一个服务的测试,简单功能是直接在回调函数中输出一段日志,然后按照以下步骤来实现代码:
引入或者说是创建一个
skynet服务:
local skynet = require "skynet"
调用
skynet.start接口,并定义传入回调函数:
skynet.start(function() skynet.error("Server First Test") end)
启动lua服务:
1.原理解析:
参考bootstrap.lua中的源码,我们得到在lua中启动一个服务的接口:
skynet.newservice(name, ...)
它的定义在
lualib/skynet.lua中,传入参数
name是用来创建lua服务的 lua脚本名称,这些lua脚本存放的目录取决于
config 配置文件中
luaservice项的配置信息,例如这里我们配置信息为:
luaservice = root.."service.lua;"..root.."test.lua;"..root.."examples.lua"
表示skynet通过
skynet.newservice查询与
name参数匹配的lua脚本会查找
service、
test和
example这三个目录下的
.lua脚本。
2.实现步骤:
从bootstrap服务的解析,我们知道最后被启动的用户第一个lua服务是main.lua我们要让上面的自定的
firsttest.lua运行启动,有两种办法:
方法一:直接修改config配置文件中的start项为
"firsttest":
start = "firsttest"
方法二:在main.lua中通过
skynet.newservice启动:
skynet.newservice("firsttest")
用上述的任何一个步骤完成配置之后,回到skynet根目录,运行skynet服务器启动指令,如下:
linsh@ubuntu:/application/skynet$ ./skynet examples/config [:01000001] LAUNCH logger [:01000002] LAUNCH snlua bootstrap [:01000003] LAUNCH snlua launcher [:01000004] LAUNCH snlua cmaster [:01000004] master listen socket 0.0.0.0:2013 [:01000005] LAUNCH snlua cslave [:01000005] slave connect to master 127.0.0.1:2013 [:01000004] connect from 127.0.0.1:52686 4 [:01000006] LAUNCH harbor 1 16777221 [:01000004] Harbor 1 (fd=4) report 127.0.0.1:2526 [:01000005] Waiting for 0 harbors [:01000005] Shakehand ready [:01000007] LAUNCH snlua datacenterd [:01000008] LAUNCH snlua service_mgr [:01000009] LAUNCH snlua main [:01000009] Server start [:0100000a] LAUNCH snlua protoloader [:0100000b] LAUNCH snlua console [:0100000c] LAUNCH snlua firsttest [:0100000c] Server First Test! [:0100000d] LAUNCH snlua debug_console 8000 [:0100000d] Start debug console at 127.0.0.1:8000 [:0100000e] LAUNCH snlua simpledb [:0100000f] LAUNCH snlua watchdog [:01000010] LAUNCH snlua gate [:01000010] Listen on 0.0.0.0:8888 [:01000009] Watchdog listen on 8888 [:01000009] KILL self [:01000002] KILL self
服务分类:
1.全局唯一服务:
全局唯一的服务等同于单例,即不管调用多少次创建接口,最后都只会创建一个此类型的服务实例且全局唯一。创建接口:
skynet.uniqueservice(global,...)
当参数
global=true时,则表示此服务在所有节点之间是唯一的。
查询接口:
假如不清楚当前创建了此全局服务没有,可以通过以下接口来查询:
skynet.queryservice(global,...)
global=true时如果还没有创建过目标服务则一直等下去,直到目标服务被(其他服务触发而)创建。
2.普通服务:
每调用一次创建接口就会创建出一个对应的服务实例,可以同时创建成千上万个,用唯一的id来区分每个服务实例。使用的创建接口是:skynet.newservice(name, ...)
3.创建区别:
关于全局服务的创建,其实也跟普通服务一样,是用skynet.call(异步变同步)的方式让
service_mgr服务创建目标服务(类似于通知
launch服创建服务一样)。区别在于创建全局服务时,假如
service_mgr这边如已创建则直接返回服务地址;如没则创建;如正在创建则等结果。
skynet.newservice
源码剖析:
上面的操作我们已经知道如何调用此接口来创建和启动lua服务了,但是关于启动的具体实现过程需要深入剖析一下:首先,在
lualib/skynet.lua中找到了
skynet.newservice接口定义的位置:
function skynet.newservice(name, ...) return skynet.call(".launcher", "lua" , "LAUNCH", "snlua", name, ...) end
通过调用
skynet.call接口,向
laucher服务(源码是laucher.lua)发送一个
LAUNCH消息,从而调用
local function launch_service(service, ...)接口来实现的。也就是说,所有创建skynet服务(除了
laucher服务自身)的操作都由
laucher服务统一实现和管理的。
1. skynet.call
接口:
function skynet.call(addr, typename, ...) --获取服务的处理进程 local p = proto[typename] --向指定服务发送消息 local session = c.send(addr, p.id , nil , p.pack(...)) if session == nil then error("call to invalid address " .. skynet.address(addr)) end return p.unpack(yield_call(addr, session)) end
先在
proto进程管理器字典中获取指定服务类型
typename对应的进程:
local p = proto[typename]
再通过
skynet.core这个C模块的
send功能,向指定服务发送数据:
local session = c.send(addr, p.id , nil , p.pack(...))
addr是服务的地址;
p是处理
addr对应服务的进程;
c是一个C模块:
local c = require "skynet.core",即
skynet.core模块。
2. skynet.send
接口:
skynet.core模块的源码在
lualib-src/lua-skynet.c中,查看
luaopen_skynet_core可以看到
{ "send" , lsend },,即
skynet.core.send其实就对应
lsend方法:
//lua-skynet.c /* uint32 address/string address integer type integer session string message lightuserdata message_ptr integer len */ static int lsend(lua_State *L) { struct skynet_context * context = lua_touserdata(L, lua_upvalueindex(1)); //获取第一个形参(uint32或string) uint32_t dest = (uint32_t)lua_tointeger(L, 1); const char * dest_string = NULL; if (dest == 0) { if (lua_type(L,1) == LUA_TNUMBER) { return luaL_error(L, "Invalid service address 0"); } dest_string = get_dest_string(L, 1); } //获取第二个形参 int type = luaL_checkinteger(L, 2); int session = 0; if (lua_isnil(L,3)) { type |= PTYPE_TAG_ALLOCSESSION; } else { //获取第三个形参 session = luaL_checkinteger(L,3); } //获取第四个形参 int mtype = lua_type(L,4); switch (mtype) { case LUA_TSTRING: { size_t len = 0; void * msg = (void *)lua_tolstring(L,4,&len); if (len == 0) { msg = NULL; } if (dest_string) { session = skynet_sendname(context, 0, dest_string, type, session , msg, len); } else { session = skynet_send(context, 0, dest, type, session , msg, len); } break; } case LUA_TLIGHTUSERDATA: { void * msg = lua_touserdata(L,4); //获取第五个形参 int size = luaL_checkinteger(L,5); if (dest_string) { session = skynet_sendname(context, 0, dest_string, type | PTYPE_TAG_DONTCOPY, session, msg, size); } else { session = skynet_send(context, 0, dest, type | PTYPE_TAG_DONTCOPY, session, msg, size); } break; } default: luaL_error(L, "skynet.send invalid param %s", lua_typename(L, lua_type(L,4))); } if (session < 0) { // send to invalid address // todo: maybe throw an error would be better return 0; } lua_pushinteger(L,session); return 1; }
当然,
launch服务最终还是调用的
skynet.launch("snlua","xxx")来创建服务。
总结:
在skynet中的lua服务都是通过laucher服务(laucher.lua)来创建的(当然,除了
laucher服务自身),而不是直接在调用
skynet.newservice的服务上直接执行创建操作,这种实现方式的好处就是方便所有服务的跟踪和管理。
参考资料:
skynet浅析相关文章推荐
- Skynet服务器框架(六) Socket服务源码剖析和应用
- Skynet服务器框架(二) C源码剖析启动流程
- skynet如何启动一个lua服务
- Skynet服务器框架(七) Lua中调用自定义C库
- 2017-06-13共享时出现错误,没有启动服务器服务,此时尚未创建共享资源”的解决办法
- Skynet服务器框架(一) Linux下的安装和启动
- Skynet服务器框架(一) Linux下的安装和启动
- 试图共享 本地磁盘D 时出现错误,没有启动服务器服务,此时尚未创建共享资源
- 不能启动EasyConfig时如何创建一个新的网络服务
- mscrm服务器SQL Server Reporting Services (MSSQLSERVER) 服务无法自动启动
- 使用ServletContextListener在服务器启动和关闭时创建和关闭缓存
- WCF技术剖析之五:利用ASP.NET兼容模式创建支持会话(Session)的WCF服务
- 无法向会话状态服务器发出会话状态请求请。确保 ASP.NET State Service (ASP.NET 状态服务)已启动
- 使用ServletContextListener在服务器启动和关闭时创建和关闭缓存
- 启动服务及创建和维护数据库,SQL Server 2005系列之二
- 三层结构,在自动启动服务器的应用服务的一个小问题.请教大虾们
- Exchange 2007服务器启动后,Information Store和System Attendant服务不能自动启动
- 设置Linux的服务自动启动Oracle服务器
- 不能启动 Easy Config时如何创建一个新的网络服务
- Linux 服务器中实现服务的开机启动