zoukankan      html  css  js  c++  java
  • skynet源码分析之snlua服务的启动流程(一)

    skynet绝大部分服务类型是snlua,它是运行Lua脚本的服务,在用skynet框架上开发游戏服务器时,大部分逻辑都是snlua服务,90%以上只需写Lua代码即可,所以很有必要了解snlua服务相关内容。由于篇幅较多,打算分三篇文章介绍,都写完后再一起发布出去。本篇主要介绍snlua服务的启动流程,相关代码主要在service-src/service_snlua.c,lualib-src/lua-skynet.c,lualib/skynet.lua,lualib/loader.lua。bootstrap服务是skynet启动时创建的第一个snlua服务,以bootstrap为例说明snlua服务的启动流程。

    1 // skynet-src/skynet_start.c
    2 static void
    3 bootstrap(struct skynet_context * logger, const char * cmdline) {
    4      ...
    5      struct skynet_context *ctx = skynet_context_new(name, args); // name="snlua" args="bootstrap"
    6      ...
    7 }

     创建一个snlua类型的ctx,会调用snlua_init,注册消息回调函数launch_cb(第7行),然后给自己发第一条消息(第11行),至此ctx创建完成,但snlua服务的初始流程还未完成。

     1 // service-src/service_snlua.c
     2 int
     3 snlua_init(struct snlua *l, struct skynet_context *ctx, const char * args) {
     4     int sz = strlen(args);
     5     char * tmp = skynet_malloc(sz);
     6     memcpy(tmp, args, sz);
     7     skynet_callback(ctx, l , launch_cb);
     8     const char * self = skynet_command(ctx, "REG", NULL);
     9     uint32_t handle_id = strtoul(self+1, NULL, 16);
    10     // it must be first message
    11     skynet_send(ctx, 0, handle_id, PTYPE_TAG_DONTCOPY,0, tmp, sz);
    12     return 0;
    13 }

    服务收到第一条消息后,先把消息回调函数至为NULL(之前设置的回调函数已失效,之后在Lua层会重新设置),然后调用消息回调函数init_cb,

    1 // service-src/service_snlua.c
    2 static int
    3 launch_cb(struct skynet_context * context, void *ud, int type, int session, uint32_t source , const void * msg, size_t sz) {
    4     ...
    5     skynet_callback(context, NULL, NULL);
    6     int err = init_cb(l, context, msg, sz);
    7     ...
    8 }

     在init_cb里进行Lua层的初始化,比如初始化LUA_PATH,LUA_CPATH,LUA_SERVICE等全局变量,主要有几个点:

    1. 第7,8行,将ctx设置到LUA_REGISTRYINDEX里,以便在C与Lua的交互中可以获取到ctx

    2. 10-12行,设置全局变量LUA_PRELOAD

    3. 18行,加载loader.lua脚本

    4. 25行,运行loader.lua,参数是“bootstrap”

     1 // service-src/service_snlua.c
     2 static int
     3 init_cb(struct snlua *l, struct skynet_context *ctx, const char * args, size_t sz) {
     4     lua_State *L = l->L;
     5     l->ctx = ctx;
     6     ...
     7     lua_pushlightuserdata(L, ctx);
     8     lua_setfield(L, LUA_REGISTRYINDEX, "skynet_context");
     9     ...
    10     const char *preload = skynet_command(ctx, "GETENV", "preload");
    11     lua_pushstring(L, preload);
    12     lua_setglobal(L, "LUA_PRELOAD");
    13 
    14     lua_pushcfunction(L, traceback);
    15     assert(lua_gettop(L) == 1);
    16 
    17     const char * loader = optstring(ctx, "lualoader", "./lualib/loader.lua");
    18     int r = luaL_loadfile(L,loader);
    19     if (r != LUA_OK) {
    20         skynet_error(ctx, "Can't load %s : %s", loader, lua_tostring(L, -1));
    21         report_launcher_error(ctx);
    22         return 1;
    23     }
    24     lua_pushlstring(L, args, sz);
    25     r = lua_pcall(L,1,0,1);
    26     if (r != LUA_OK) {
    27         skynet_error(ctx, "lua loader error : %s", lua_tostring(L, -1));
    28         report_launcher_error(ctx);
    29         return 1;
    30     }
    31     ...
    32     return 0;
    33 }

     在loader.lua里,主要做几点:

    1. 第7行,设置全局变量SERVICE_NAME,因此在Lua层可以用SERVICE_NAME获取当前服务的名称

    2. 11-22行,获取需启动的服务的Lua脚本(比如bootstrap.lua)的路径,并加载它(loadfile)

    3. 24-28行,如果skynet启动配置里设置了LUA_PRELOAD,加载并运行它。每个snlua服务都加载了LUA_PRELOAD,所以经常把一个游戏里一些公用的配置放到LUA_PRELOAD里

    4. 30行,运行Lua服务的入口脚本,比如bootstrap.lua,除第一个参数以外的所有参数(第一个参数是服务的名称)

     1  -- lualib/loader.lua
     2   local args = {}
     3   for word in string.gmatch(..., "%S+") do
     4        table.insert(args, word)
     5   end
     6   
     7   SERVICE_NAME = args[1]
     8   
     9   local main, pattern
    10   
    11   local err = {}
    12   for pat in string.gmatch(LUA_SERVICE, "([^;]+);*") do
    13        local filename = string.gsub(pat, "?", SERVICE_NAME)
    14        local f, msg = loadfile(filename)
    15        if not f then
    16             table.insert(err, msg)
    17        else
    18              pattern = pat
    19              main = f
    20              break
    21        end
    22  end
    23  ...
    24  if LUA_PRELOAD then
    25          local f = assert(loadfile(LUA_PRELOAD))
    26          f(table.unpack(args))
    27          LUA_PRELOAD = nil
    28  end
    29 
    30  main(select(2, table.unpack(args)))

    Lua服务的入口脚本必须包含2点:1. require "skynet",这样才能使用skynet.lua里的接口;2. 调用skyne.start函数

    1 local skynet = require "skynet"
    2 skynet.start(function()
    3     ...
    4 end)

    skynet.lua提供了很多api供Lua服务调用,第1行代码是local c = require "skynet.core",skynet.core是由C编写的so库,so库里提供很多api供Lua层调用(lualib-src/lua-skynet.c)。require "skynet"过程中还做了其他事情放在下一篇介绍。

    第6行,提供了很多注册函数供Lua层调用

    26-31行,从LUA_REGISTERINDEX表中获取ctx(在init_cb里设置的),这些注册函数共用ctx这个上值,在C api里通过lua_upvalueindex(1)获取这个ctx,然后对ctx进行相应处理。

     1 // lualib-src/lua-skynet.c
     2 LUAMOD_API int
     3 luaopen_skynet_core(lua_State *L) {
     4      uaL_checkversion(L);
     5 
     6      luaL_Reg l[] = {
     7          { "send" , lsend },
     8          { "genid", lgenid },
     9          { "redirect", lredirect },
    10          { "command" , lcommand },
    11          { "intcommand", lintcommand },
    12          { "error", lerror },
    13          { "tostring", ltostring },
    14          { "harbor", lharbor },
    15          { "pack", luaseri_pack },
    16          { "unpack", luaseri_unpack },
    17          { "packstring", lpackstring },
    18          { "trash" , ltrash },
    19          { "callback", lcallback },
    20          { "now", lnow },
    21          { NULL, NULL },
    22     };
    23 
    24     luaL_newlibtable(L, l);
    25 
    26     lua_getfield(L, LUA_REGISTRYINDEX, "skynet_context");
    27     struct skynet_context *ctx = lua_touserdata(L,-1);
    28     if (ctx == NULL) {
    29         return luaL_error(L, "Init skynet context first");
    30     }
    31     luaL_setfuncs(L,l,1);
    32 
    33     return 1;
    34 }

     Lua服务入口的第二件事是调用skynet.start,重新设置消息回调函数(第3行,之前设置的launch_cb回调函数已经失效了)

    1  -- lualib/skynet.lua
    2  function skynet.start(start_func)
    3      c.callback(skynet.dispatch_message)
    4      ...
    5  end

    调用C层的lcallback,通过lua_upvalueindex获取函数的上值ctx,然后设置服务的消息回调函数为_cb,此时Lua堆栈上有且仅有一个元素lua函数(skynet.dispatch_message)

     1 // lualib/lua-skynet.c
     2 static int
     3 lcallback(lua_State *L) {
     4     struct skynet_context * context = lua_touserdata(L, lua_upvalueindex(1));
     5     int forward = lua_toboolean(L, 2);
     6     luaL_checktype(L,1,LUA_TFUNCTION);
     7     lua_settop(L,1);
     8     lua_rawsetp(L, LUA_REGISTRYINDEX, _cb);
     9 
    10     lua_rawgeti(L, LUA_REGISTRYINDEX, LUA_RIDX_MAINTHREAD);
    11     lua_State *gL = lua_tothread(L,-1);
    12 
    13     if (forward) {
    14         skynet_callback(context, gL, forward_cb);
    15     } else {
    16         skynet_callback(context, gL, _cb);
    17     }
    18 
    19     return 0;
    20 }

    在_cb里,最终会调用Lua层的dispatch_message,参数依次是:type, msg, sz, session, source。所以,snlua类型的服务收到消息时最终会调用Lua层的消息回调函数skynet.dispatch_message。

     1 // lualib/lua-skynet.c
     2 static int
     3 _cb(struct skynet_context * context, void * ud, int type, int session, uint32_t source, const void * msg, size_t sz) {
     4     ...
     5     lua_pushinteger(L, type);
     6     lua_pushlightuserdata(L, (void *)msg);
     7     lua_pushinteger(L,sz);
     8     lua_pushinteger(L, session);
     9     lua_pushinteger(L, source);
    10 
    11     r = lua_pcall(L, 5, 0 , trace);
    12     ...
    13 end

     这就是snlua服务的启动流程。除了以上介绍,剩余的一些事情放到下一篇介绍,比如require "skynet"过程中还处理了额外的事情。

  • 相关阅读:
    javascript 创建字典
    IE显示PNG
    透明PNG背景图片 For IE 6.0 Firefox Opera
    IE FireFox对CSS的不同解释收集
    javascript中replace()
    netstat 指令返回结果中state含义
    FIREFOX层的自适应高度
    select options的操作
    事件冒泡
    C++第三讲笔记
  • 原文地址:https://www.cnblogs.com/RainRill/p/8485024.html
Copyright © 2011-2022 走看看