skynet源码分析(9)--LUA C API

作者:[email protected],转载请注明作者

这篇文章是分析skynet消息注册前的知识准备,skynet的消息注册,C服务和lua服务设置回调走的函数是不同的。C的回调可以直接调,但是lua的回调不行,它需要一个默认的回调C函数,将返回参数转换为lua能理解的格式,遵循lua的api协议,传递到lua层。这个回调的名字叫_cb。具体它是怎么工作的我们下一篇再分析,这一篇要先了解一些必要的lua api的规范,才能进行下一步。这个lua参考手册是随时需要翻的:http://www.lua.org/manual/5.3/manual.html

首先是lua调c,lua到C层的参数是按顺序来的,也就是说第一个参数就是1,第二个就是2....第n个就是n。从C层传递到lua层也是一样,但是会多一个参数个数。举个栗子吧,干讲是不好理解。能看懂英文的同学最好详细看一下,看不懂的呢,也没什么大碍。

In order to communicate properly with Lua, a C function must use the following protocol, 
which defines the way parameters and results are passed:
a C function receives its arguments from Lua in its stack in direct order (the first argument is pushed first). 
So, when the function starts, lua_gettop(L) returns the number of arguments received by the function. 
The first argument (if any) is at index 1 and its last argument is at index lua_gettop(L). 
To return values to Lua, a C function just pushes them onto the stack,
in direct order (the first result is pushed first), and returns the number of results.
Any other value in the stack below the results will be properly discarded by Lua. 
Like a Lua function, a C function called by Lua can also return many results.

As an example, the following function receives a variable number of numeric arguments and returns their average and their sum:

这是一个lua调C以后,C返回的例子
     static int foo (lua_State *L) {
       int n = lua_gettop(L);    /* number of arguments */  参数个数
       lua_Number sum = 0.0;
       int i;
       for (i = 1; i <= n; i++) {
         if (!lua_isnumber(L, i)) { //参数是不是数字
           lua_pushliteral(L, "incorrect argument"); //不是数字报错
           lua_error(L);
         }
         sum += lua_tonumber(L, i); //取第i个参数
       }
       lua_pushnumber(L, sum/n);        /* first result */ 第一个返回值
       lua_pushnumber(L, sum);         /* second result */ 第二个返回值
       return 2;                   /* number of results */ 一共两个返回值
     }

然后是C层调LUA的函数,它的具体过程就是把参数填好,然后调LUA的函数。为了防止异常导致服务不稳定,skynet中调用lua回调函数的时候使用的是lua_pcall,这个函数在lua层也是有的,LUA层就叫pcall。

lua_pcall的介绍请大家到参考手册里去找,我这里贴个参数列表就好了。

int lua_pcall (lua_State *L, int nargs, int nresults, int msgh);
lua_state就是lua环境了
nargs表示有多少个参数
nresults表示有多少个返回值
msgh为0表示返回初始错误对象,其它值表示消息处理函数所在的栈索引。

下面以skynet中的_cb函数来说明一下C调用lua函数的场景。

static int
_cb(struct skynet_context * context, void * ud, int type, int session, uint32_t source, const void * msg, size_t sz) {
    lua_State *L = ud;
    int trace = 1;
    int r;
    int top = lua_gettop(L); //获取栈参数个数
    if (top == 0) {
        lua_pushcfunction(L, traceback); //index为1,是一个错误处理函数
        lua_rawgetp(L, LUA_REGISTRYINDEX, _cb); //index为2,是lua的回调函数
    } else {
        assert(top == 2);
    }

    lua_pushvalue(L,2); //lua回调函数入栈
    lua_pushinteger(L, type);  //回调参数1,类型
    lua_pushlightuserdata(L, (void *)msg);  //回调参数2,消息体
    lua_pushinteger(L,sz); //回调参数3,消息长度
    lua_pushinteger(L, session); //回调参数4,session
    lua_pushinteger(L, source); //回调参数5,source

    //nargs为5,表示有5个参数
    //nresults为0,表示返回值为0
    //trace为1,表示出错时调用前面设置的traceback函数
    r = lua_pcall(L, 5, 0 , trace); 

    if (r == LUA_OK) {
        return 0;
    }
    const char * self = skynet_command(context, "REG", NULL);
    switch (r) {
    case LUA_ERRRUN:
        skynet_error(context, "lua call [%x to %s : %d msgsz = %d] error : " KRED "%s" KNRM, source , self, session, sz, lua_tostring(L,-1));
        break;
    case LUA_ERRMEM:
        skynet_error(context, "lua memory error : [%x to %s : %d]", source , self, session);
        break;
    case LUA_ERRERR:
        skynet_error(context, "lua error in error : [%x to %s : %d]", source , self, session);
        break;
    case LUA_ERRGCMM:
        skynet_error(context, "lua gc error : [%x to %s : %d]", source , self, session);
        break;
    };

    lua_pop(L,1);

    return 0;
}

根据上面的代码分析,当服务是lua实现的时候,skynet底层核心框架在处理完消息以后,回调lua层服务的回调函数时,要先经过一次lua api协议的处理,将参数准备好以后,然后调用lua服务中的回调函数。

你可能感兴趣的:(skynet源码分析(9)--LUA C API)