一次配置错误引发对skynet源码的学习

发表时间: 2019-08-08作者: 何景松 0 32

    一次配置错误引发对skynet源码的学习

    前言

    之前公司的服务端引擎都是自研并且闭源的,一些底层的实现细节只能通过接口去猜测大概的实现方式,虽然这并不影响平时的工作,毕竟平时的工作内容就是用脚本怼业务。。。

    后来5月份换了份工作,入职了现在这家公司,项目中使用的是云风的skynet,工作中用了段时间,觉得也比较容易上手。于是打算在闲暇时间自己搭一套环境。

    发现问题

    当时使用的配置是这样的

    // 基础配置忽略
    logger = "./log/logic"
    logservice = "logger"

    结果一运行就报错了 w(゚Д゚)w

    [root@localhost test]# ./skynet/skynet config/logic.conf 
    Can't launch logger service

    经过一番折腾之后,决定从源码查找问题 (°ー°〃)

    分析

    程序入口是从skynet_main.c的main函数开始,经过一些初始化操作之后,调用skynet_start函数。

    skynet_start函数会调用skynet_context_new函数,创建logservice

    // ...
    struct skynet_context *ctx = skynet_context_new(config->logservice, config->logger);
    if (ctx == NULL) {
        fprintf(stderr, "Can't launch %s service\n", config->logservice);
        exit(1);
    }

    可见报错是因为ctx为空,也就是调用skynet_context_new失败引起的。

    skynet_context_new函数会调用skynet_module_query,查找相关的模块。 这是几个返回空的地方。

    struct skynet_context * 
    skynet_context_new(const char * name, const char *param) {
        struct skynet_module * mod = skynet_module_query(name);
    
        if (mod == NULL)
            return NULL;
    
        void *inst = skynet_module_instance_create(mod);
        if (inst == NULL)
            return NULL;
        // ...
    
        int r = skynet_module_instance_init(mod, inst, ctx, param);
        if (r == 0) {
            // ...
        } else {
            // ...
            return NULL;
        }
    }

    skynet_module_query会根据模块名查找相关模块(也就是动态库),如果该模块已经加载,则返回。如果没有则调用tryopen函数加载动态库,因为在cservice/目录下面存在logger.so文件,所以不会是这里的问题。

    当动态库加载完毕之后,会调用open_sym获取相关的函数,因为这里是logger模块,所以这4个函数分别是 "logger_create", "logger_init", "logger_release", "logger_signal"。也就是说,如果要编写C服务的话,在模块里面添加这4个接口就行了。看了下service-src/service_logger.c文件里面确实存在这4个接口,所以也不是这个问题。

    回到上一层函数,因为skynet_module_query不会返回空,所以应该是skynet_module_instance_create函数的问题。

    skynet_module_instance_create函数调用的是logger模块的create函数,也就是logger_create函数,后来看logger_create函数,也只是分配内存,初始化而已。所以应该也不是这里的问题。

    struct logger *
    logger_create(void) {
        struct logger * inst = skynet_malloc(sizeof(*inst));
        inst->handle = NULL;
        inst->close = 0;
        inst->filename = NULL;
    
        return inst;
    }

    所以只能是skynet_module_instance_init这个函数的问题了。

    int
    skynet_module_instance_init(struct skynet_module *m, void * inst, struct skynet_context *ctx, const char * parm) {
        return m->init(inst, ctx, parm);
    }

    可见,调用的是logger_init方法

    int
    logger_init(struct logger * inst, struct skynet_context *ctx, const char * parm) {
        if (parm) {
            inst->handle = fopen(parm,"w");
            if (inst->handle == NULL) {
                return 1;
            }
            inst->filename = skynet_malloc(strlen(parm)+1);
            strcpy(inst->filename, parm);
            inst->close = 1;
        } else {
            inst->handle = stdout;
        }
        if (inst->handle) {
            skynet_callback(ctx, inst, logger_cb);
            return 0;
        }
        return 1;
    }

    parm参数是配置文件里面的 “logger”,所以问题已经很清晰了,就是inst->handle为空导致的。也就是fopen函数执行失败,因为logger配置的值是“./log/logic”,而我目录下面没有log目录,所以就是GG了。

    创建了log目录后,完美运行起了skynet。

    [root@localhost log]# tail logic -f -n 100
    [:00000002] LAUNCH snlua bootstrap
    [:00000003] LAUNCH snlua launcher
    [:00000004] LAUNCH snlua cdummy
    [:00000005] LAUNCH harbor 0 4
    [:00000006] LAUNCH snlua datacenterd
    [:00000007] LAUNCH snlua service_mgr
    [:00000008] LAUNCH snlua main
    [:00000008] Hello World.
    [:00000002] KILL self

    另外的一些

    因为service_logger.c里面fopen函数打开文件的方式是"w",每次重启服都会把上一次的日志清空,这不是我想要的。

    不过还好,经过上面的分析,现在已经可以写出自己的logservice。

    在service-src目录下创建service_loggerx.c文件,只需要提供这四个函数即可

    struct loggerx* loggerx_create(void);
    void loggerx_release (struct loggerx * inst);
    static int loggerx_cb(struct skynet_context * context, void *ud, int type, int session, uint32_t source, const void * msg, size_t sz);
    int loggerx_init(struct loggerx * inst, struct skynet_context *ctx, const char * parm);

    然后在Makefile里面,找到 CSERVICE 在后面添加 loggerx。

    重新编译即可。