Nginx中通过lua嵌入C程序

为什么要在nginx中使用lua嵌入c程序?
1.性能上的考虑,lua毕竟是一个脚本语言,对于某些特定的功能如果用纯lua来实现性能上一般都会比用c要逊色一些。
2.没有现成的库供我们使用,实际上互联网上已经有很多现成的第三方lua库供我们使用,比如读取redis数据的resty.redis;将json转化为lua本地数据格式的cjson等。当找不到合适我们使用的第三方库的时候,我们就需要自己动手了。当然如果能够直接使用lua来写一个满足我们自己使用的库也是可以的,如果考虑到性能问题,用c来实现是一个不错的选择。



我们这里打算实现两个方法

1.对一个字符串做hash并对其hash值取摸,在lua中我们可以这样调用该方法:
  local value = demo.mod("abcdefe",8);
  其中demo是我们写好的c库;
  mod方法的第一个参数是要进行取摸的字符串;
  第二个参数是摸;

2.由于lua中的时间函数无法精确到毫秒级,我们这里实现一个可以获取系统毫秒的时间:
   local time = demo.time();

具体实现
首先引入lua的c api头文件和我们用到的时间头文件
#include <lua.h>
#include <lauxlib.h>
#include <lualib.h>
#include <sys/time.h>


实现一个murmurhash算法,用来hash我们的字符串
typedef unsigned long long uint64_t;
typedef long long int64_t;
typedef long int int32_t;
static uint64_t murmurhash64A(const void *key,size_t len,int32_t seed){

    int64_t m = 0xc6a4a7935bd1e995LL;
    int r = 47;

    uint64_t h = seed ^ (len * m);

    const uint64_t * data = (const uint64_t *)key;
    const uint64_t * end = data + (len >> 3);

    while(data != end){
            uint64_t k = *data++;

            k *= m;
            k ^= k >> r;
            k *= m;

            h ^= k;
            h *= m;
    }

    const unsigned char * data2 = (const unsigned char *)data;
    switch(len & 7){
        case 7: h ^= (uint64_t)data2[6] << 48;
        case 6: h ^= (uint64_t)data2[5] << 40;
        case 5: h ^= (uint64_t)data2[4] << 32;
        case 4: h ^= (uint64_t)data2[3] << 24;
        case 3: h ^= (uint64_t)data2[2] << 16;
        case 2: h ^= (uint64_t)data2[1] << 8;
        case 1: h ^= (uint64_t)data2[0];
                h *= m;
    };

    h ^= h >> r;
    h *= m;
    h ^= h >> r;

    return h;
}


我们看到上面这个方法是一个纯c函数,lua如何于c传递数据呢?我们看下面这个方法
// 时间上lua和c交换数据使用的是一个lua_State栈结构
// 当我们在lua中调用demo.mod("a",8);这个方法时,该方法的两个参数会被顺序的压入到栈顶
// 这个时候栈底是字符串"a",栈顶是数字8
static int mod(lua_State *L){
    //checks whether the key is a string
    size_t len;
    
    // 这个方法用来检查第一个参数是不是一个字符串或数字,如果不是那么lua在调用时就会报错
    // len这个变量保存的是字符串的长度,这个值是调用该方法后被回填的值。
    // 还有一个需要说明的是,在lua的栈中序号1代表栈底,序号2代表栈底的上一个元素
    // 所以这里我们向luaL_checklstring方法的第二个参数传入1
    const void *key = luaL_checklstring(L,1,&len);
    //checks if the num is a number
    int num = luaL_checknumber(L,2);

    uint64_t seed=0x1234ABCD;
    uint64_t h = murmurhash64A(key,len,seed);

    int index = h%num;
    
    // 将计算出的摸值压入栈顶
    lua_pushnumber(L,index); //the first return value

    // 返回值代表demo.mod()这个方法返回的参数个数
    // lua会根据返回数字的个数,从栈中逐个取出作为lua方法的返回值
    return 1;
}


好了,有拉上面mod方法的解释,我们获取系统时间的方法就比较容易理解了。

实现获取当前毫秒级的系统时间
static int currentTime(lua_State *l){
    struct timeval time;
    int status = gettimeofday(&time,NULL);
    if(status < 0){
        lua_pushinteger(l,-1);
        return 1;
    }

    int64_t millis = (int64_t)time.tv_sec * 1000 + (int64_t)time.tv_usec / 1000;
    lua_pushinteger(l,millis);
    return 1;
}


接下来就是要讲我们写好的c函数注册到lua中了
方法格式: int luaopen_xxx(lua_State *L)
其它都是固定的,只有xxx使我们自定义的库名,我们这里给它起个名字叫demo

int luaopen_demo(lua_State *L){

    //所有要注册到lua中的c函数放到luaL_Reg数组中
    const luaL_Reg methods[] = {
                        {"mod",mod},
                        {"time",currentTime},
                        {NULL,NULL}
    };

    //注册c方法到lua
    //此时栈顶的值是demo
    // 创建栈结构
    lua_newtable(L);
    // 这个方法使用lua5.2中摘抄过来的
    // 把数组methods中所有的函数注册到栈顶的demo中,并指定upvalue的个数
    // 因为我们这里没有用到upvalue值,所有传入0
    setfuncs(L,methods,0);

    //向栈顶压入一个字符串代表我们的版本号
    lua_pushliteral(L,"0.0.1");

    //将栈顶的值赋值到,栈中第二个元素的version字段上,并将栈顶弹出
    //demo.version=0.0.1
    lua_setfield(L,-2,"version");

    //返回栈顶的demo值
    return 1;
}


下面这个是我们从5.2中拿过来的代码
static void setfuncs (lua_State *l, const luaL_Reg *reg, int nup){
     int i;

     luaL_checkstack(l, nup, "too many upvalues");
     for (; reg->name != NULL; reg++) {  /* fill the table with given functions */
         for (i = 0; i < nup; i++)  /* copy upvalues to the top */
             lua_pushvalue(l, -nup);
         lua_pushcclosure(l, reg->func, nup);  /* closure with those upvalues */
         lua_setfield(l, -(nup + 2), reg->name);
     }
     lua_pop(l, nup);  /* remove upvalues */
}


实际上在lua5.1中使用lua_register()和luaL_register()等方法同样可以将c函数注册到lua中。

使用方法

将上面的代码编译成.so共享库
cc -c -O3 -Wall -pedantic -DNDEBUG  -I/luajit/include/luajit-2.1 -fpic -g -o lua_demo.o lua_demo.c
cc  -bundle -undefined dynamic_lookup -o demo.so lua_demo.o

/luajit/include/luajit-2.1  是luajit的头文件所在目录
lua_demo.c  是我们上面编写好的代码

将demo.so拷贝到一个指定目录,比如我们这里是/nginx/lualib

在nginx中像这样使用

  lua_package_cpath "/nginx/lualib/?.so;;";  //注意是两个分号

  server {
     listen       80;

     location ~ /test {
          content_by_lua '
              local demo = require "demo";
             
              local index = demo.mod("a",8);
              ngx.say("字符a的摸值是"..index);

              local time = demo.time();
              ngx.say("当前时间是"..time);
           ';
     }
  }

调用curl http://127.0.0.1/test 会输出相应的值

猜你喜欢

转载自deyimsf.iteye.com/blog/2250141