Skynet代码阅读随笔(保持更新)

c接口转Lua接口:

下面这个代码之后就可以直接通过client.socket进行导入,c接口转Lua接口:

LUAMOD_API int
luaopen_client_socket(lua_State *L) {
    luaL_checkversion(L);
    luaL_Reg l[] = {
        { "connect", lconnect },
        { "recv", lrecv },
        { "send", lsend },
        { "close", lclose },
        { "usleep", lusleep },
        { NULL, NULL },
    };
    luaL_newlib(L, l);

    struct queue * q = lua_newuserdata(L, sizeof(*q));
    memset(q, 0, sizeof(*q));
    pthread_mutex_init(&q->lock, NULL);
    lua_pushcclosure(L, lreadstdin, 1);
    lua_setfield(L, -2, "readstdin");

    pthread_t pid ;
    pthread_create(&pid, NULL, readline_stdin, q);

    return 1;
}

对应的,所有的c中的函数,例如lsend等,都需要把参数改为lua_State

加载skynet_module结构体(通过.so)

通过字符串获取skynet_module结构体:

struct skynet_module * skynet_module_query(const char * name) 

struct skynet_module {
    const char * name;
    void * module;
    skynet_dl_create create;
    skynet_dl_init init;
    skynet_dl_release release;
    skynet_dl_signal signal;
};

以snlua为例,获得了module之后,会继续调用skynet_module_instance_create, skynet_module_instance_init函数,然后就会分别调用模块的create, init函数

int
snlua_init(struct snlua *l, struct skynet_context *ctx, const char * args) {
    int sz = strlen(args);
    char * tmp = skynet_malloc(sz);
    memcpy(tmp, args, sz);
    skynet_callback(ctx, l , launch_cb);                    // 注册消息处理函数
    const char * self = skynet_command(ctx, "REG", NULL);   // 得到自身的服务的地址,是一个带冒号的字符串
    uint32_t handle_id = strtoul(self+1, NULL, 16);         // 得到数字地址
    // it must be first message
    //直接发送消息,以便snlua能第一个加载 相应的服务
    skynet_send(ctx, 0, handle_id, PTYPE_TAG_DONTCOPY,0, tmp, sz);  // 发消息给自己以便加载相应的服务模块
    return 0;
}

bootstrap="snlua bootstrap"为例,那么上面函数中的args将为”bootstrap”,将会作为msg通过skynet_send传个自己,然后再调用对应的lauch_cb函数

static int
launch_cb(struct skynet_context * context, void *ud, int type, int session, uint32_t source , const void * msg, size_t sz) {
    assert(type == 0 && session == 0);
    struct snlua *l = ud;
    skynet_callback(context, NULL, NULL);                   // 将消息处理函数置空,以免别的消息发过来
    int err = init_cb(l, context, msg, sz);                 // 消息处理:通过 loader.lua 加载 lua 代码块
    if (err) {
        skynet_command(context, "EXIT", NULL);
    }

    return 0;
}

其中snlua结构体如下(似乎是代表着一个lua服务?):

struct snlua {
    lua_State * L;
    struct skynet_context * ctx;
    size_t mem;
    size_t mem_report;
    size_t mem_limit;
};

Bootstrap

bootstrap 这个配置项关系着 skynet 运行的第二个服务。通常通过这个服务把整个系统启动起来。默认的 bootstrap 配置项为 “snlua bootstrap” ,这意味着,skynet 会启动 snlua 这个服务,并将 bootstrap 作为参数传给它。snlua 是 lua 沙盒服务,bootstrap 会根据配置的 luaservice 匹配到最终的 lua 脚本。如果按默认配置,这个脚本应该是 service/bootstrap.lua 。

root = "./"
thread = 8  -- 启动多少个线程
logger = nil  -- 决定内建的skynet_error会输出到什么文件,nil将输出到标准输出
harbor = 1  -- 1-255的任意数,每个节点必须有一个编号
address = "127.0.0.1:2526"  -- 当前skynet节点的地址和端口
master = "127.0.0.1:2013"  -- skynet控制中心的地址和端口.如果配置了standalone,则通常与standalone相同
start = "main"  -- bootstrap最后一个环节将启动的lua服务,也就是你定制的skynet节点的主程序.默认为main,即启动main.lua脚本
bootstrap = "snlua bootstrap"   -- skynet启动的第一个服务及其启动参数
standalone = "0.0.0.0:2013" -- 如果把这个 skynet 进程作为主进程启动,那么需要配置standalone 这一项,表示这个进程是主节点,它需要开启一个控制中心,监听一个端口,让其它节点接入。
luaservice = root.."service/?.lua;"..root.."test/?.lua;"..root.."examples/?.lua"
lualoader = "lualib/loader.lua"
snax = root.."examples/?.lua;"..root.."test/?.lua"
cpath = root.."cservice/?.so" -- 用 C 编写的服务模块的位置,通常指 cservice 下那些 .so 文件

main.lua

通过skynet.newservice启动新的lua服务:

skynet.newservice("debug_console",8000)
skynet.newservice("simpledb")
local watchdog = skynet.newservice("watchdog")

skynet.newservice函数调用skynet.call(),然后call再调用laucher.lua来启动lua服务.

function skynet.newservice(name, ...)
    return skynet.call(".launcher", "lua" , "LAUNCH", "snlua", name, ...)   -- launcher 就是 launcher.lua
end

skynet.cal会调用skynet.send可以向服务发送消息.skynet.send会调用c接口中的lsend(lua_State *L)(在lua-skynet.c中),lsend又会调用skynet_send(context, 0, dest, type, session , msg, len),然后通过skynet_send调用skynet_context_push(destination, &smsg)将消息压入目的地址服务的消息队列中供work线程取出.

-- 调用此接口发送消息(需要返回值)
function skynet.call(addr, typename, ...)
    local p = proto[typename]  
    local session = c.send(addr, p.id , nil , p.pack(...))      -- 发送消息
    -- 由于skynet.call是需要返回值的,所以c.send的第三个参数表示由框架自动分配一个session,以便返回时根据相应的session找到对应的协程进行处理
    if session == nil then
        error("call to invalid address " .. skynet.address(addr))
    end
    return p.unpack(yield_call(addr, session))                  -- 阻塞等待返回值
end

.laucher

.launcher服务是在service/bootstrap.lua中被注册的:

local launcher = skynet.launch("snlua", "launcher")
skynet.name(".launcher", launcher)

它载入了Lua服务,并将注册后的服务的handle返回。launcher通过launch_service来启动服务,而launch_service又通过skynet.launch(在manager.lua中)来启动服务

local function launch_service(service, ...)
    local param = table.concat({...}, " ")
    local inst = skynet.launch(service, param)  -- service 为要启动的服务名 param 为以空格分开的参数
    local response = skynet.response()
    if inst then
        services[inst] = service .. " " .. param
        instance[inst] = response
    else
        response(false)
        return
    end
    return inst
end

local c = require "skynet.core"
function skynet.launch(...)  --为要启动的服务名加上参数组成的字符串 参数以空格分隔
    local addr = c.command("LAUNCH", table.concat({...}," "))
    if addr then
        return tonumber("0x" .. string.sub(addr , 2))
    end
end

skynet.launch调用c.command()来启动服务,这个时候,就转到了c的接口了.调用lcommand(lua_State *L),然后再是skynet_command(与skynet_send类似,定义在skynet.h中,由skynet_sever.c实现.
再之后,则会调用cmd_launch,通过skynet_context_new来创建新服务.

static const char *
cmd_launch(struct skynet_context * context, const char * param) {
    size_t sz = strlen(param);
    char tmp[sz+1];
    strcpy(tmp,param);
    char * args = tmp;
    char * mod = strsep(&args, " \t\r\n");
    args = strsep(&args, "\r\n");
    struct skynet_context * inst = skynet_context_new(mod,args);
    if (inst == NULL) {
        return NULL;
    } else {
        id_to_hex(context->result, inst->handle);
        return context->result;
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值