skynet skynet_context、message_queue、handle_storage、global_queue

创建一个module实例,只是创建了一个服务的私有数据,以及绑定了callback,接受消息后的逻辑处理函数。那么这个module实例是怎么被底层框架所驱动呢???

先看个数据结构skynet_context:

// skynet_server.c
struct skynet_context {
    void * instance;                // 由指定module的create函数,创建的数据实例指针,同一类服务可能有多个实例,
                                    // 因此每个服务都应该有自己的数据
        
    struct skynet_module * mod;     // 引用服务module的指针,方便后面对create、init、signal和release函数进行调用
    void * cb_ud;                   // 调用callback函数时,回传给callback的userdata,一般是instance指针
    skynet_cb cb;                   // 服务的消息回调函数,一般在skynet_module的init函数里指定
    struct message_queue *queue;    // 服务专属的次级消息队列指针
    FILE * logfile;                 // 日志句柄
    char result[32];                // 操作skynet_context的返回值,会写到这里
    uint32_t handle;                // 标识唯一context的服务id
    int session_id;                 // 在发出请求后,收到对方的返回消息时,通过session_id来匹配一个返回,对应哪个请求
    int ref;                        // 引用计数变量,当为0时,表示内存可以被释放
    bool init;                      // 是否完成初始化
    bool endless;                   // 消息是否堵住
    
    CHECKCALLING_DECL
};

module可以看做是皮囊,而skynet_context就是骨骼,skynet_context是为了底层框架所封装的数据结构。

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;
    struct skynet_context * ctx = skynet_malloc(sizeof(*ctx));
    CHECKCALLING_INIT(ctx)
    ctx->mod = mod;
    ctx->instance = inst;
    ctx->ref = 2;
    ctx->cb = NULL;
    ctx->cb_ud = NULL;
    ctx->session_id = 0;
    ctx->logfile = NULL;
    ctx->init = false;
    ctx->endless = false;
    ctx->cpu_cost = 0;
    ctx->cpu_start = 0;
    ctx->message_count = 0;
    ctx->profile = G_NODE.profile;
    // Should set to 0 first to avoid skynet_handle_retireall get an uninitialized handle
    ctx->handle = 0;    
    ctx->handle = skynet_handle_register(ctx);
    struct message_queue * queue = ctx->queue = skynet_mq_create(ctx->handle);
    // init function maybe use ctx->handle, so it must init at last
    context_inc();

    CHECKCALLING_BEGIN(ctx)
    int r = skynet_module_instance_init(mod, inst, ctx, param);
    CHECKCALLING_END(ctx)
    if (r == 0) {
        struct skynet_context * ret = skynet_context_release(ctx);
        if (ret) {
            ctx->init = true;
        }
        skynet_globalmq_push(queue);
        if (ret) {
            skynet_error(ret, "LAUNCH %s %s", name, param ? param : "");
        }
        return ret;
    } else {
        ....
        return NULL;
    }
}

在skynet_context_new中先创建了皮囊module,然后创建骨骼skynet_context,并对skynet_context进行初始化操作。服务别名:
首先看ctx->handle = skynet_handle_register(ctx);这里是生成一个句柄id,作为服务的唯一id来标识服务。将服务保存在handle_storage中。对于服务handle 和服务的别名使用handle_storage进行管理。服务的handle 高八位为harbor,这样不同节点中的handle不会重复,在发送消息时也可以区分出是本地消息还是远程消息。

struct handle_name {
    char * name;       --服务别名数组
    uint32_t handle;   --服务句柄
};

struct handle_storage {
    struct rwlock lock;

    uint32_t harbor;                //本进程的harborid  每个进程唯一 在配置中设置
    uint32_t handle_index;          //为了计算hash值的自增id
    int slot_size;                    //服务的hash数组大小                    
    struct skynet_context ** slot;  //skynet_context* 的hash 数组
    
    int name_cap;                    //别名容量
    int name_count;                    //别名数组当前大小
    struct handle_name *name;        //服务的别名数组
};

可以服务别名数组是一个排序的数组,在插入时保证次序。这里用一个有序数组是为了查找时更快更稳定性。可能作者认为查找别名的机率要大于插入别名。所以牺牲了插入效率来换取查找效率。

const char * 
skynet_handle_namehandle(uint32_t handle, const char *name) {
    rwlock_wlock(&H->lock);

    const char * ret = _insert_name(H, name, handle);

    rwlock_wunlock(&H->lock);

    return ret;
}

static const char *
_insert_name(struct handle_storage *s, const char * name, uint32_t handle) {
    int begin = 0;
    int end = s->name_count - 1;
    while (begin<=end) {
        int mid = (begin+end)/2;
        struct handle_name *n = &s->name[mid];
        int c = strcmp(n->name, name);
        if (c==0) {
            return NULL;
        }
        if (c<0) {
            begin = mid + 1;
        } else {
            end = mid - 1;
        }
    }
    char * result = skynet_strdup(name);

    _insert_name_before(s, result, handle, begin);

    return result;
}


在lua 中通过register name来注册服务的别名,只能注册本地的别名。多节点的全局别名不通过REG来注册。后面介绍。

//注册本服务的别名
function skynet.register(name)
    if not globalname(name) then
        c.command("REG", name)
    end
end
//注册handle服务的别名
function skynet.name(name, handle)
    if not globalname(name, handle) then
        c.command("NAME", name .. " " .. skynet.address(handle))
    end
end


struct message_queue * queue = ctx->queue = skynet_mq_create(ctx->handle);skynet_globalmq_push(queue); 创建一个服务的次级消息队列然后将次级消息队列push到全局消息队列。

消息队列数据结构:

//消息
struct skynet_message {
    uint32_t source;   //消息来源服务地址
    int session;       //序列号
    void * data;       //数据
    size_t sz;           //数据大小
};
//次级消息队列
struct message_queue {
    struct spinlock lock;
    uint32_t handle;    //服务handle
    int cap;            //消息
    int head;            //循环数组头
    int tail;            //循环数组尾
    int release;
    int in_global;        //是否在全局队列中
    int overload;
    int overload_threshold;
    struct skynet_message *queue;   //skynet_message数组,循环数据
    struct message_queue *next;        //用于串联全局消息队列的指针
};
//全局消息队列
struct global_queue {
    struct message_queue *head;
    struct message_queue *tail;
    struct spinlock lock;
};

向一个服务发送消息,其实就是定义skynet_message,然后将skynet_message push进次级消息队列中。如果次级消息队列没在主消息队列中则link主消息队列尾部。
代码:

void 
skynet_mq_push(struct message_queue *q, struct skynet_message *message) {
    assert(message);
    SPIN_LOCK(q)
    //push 消息到服务的次级消息队列
    q->queue[q->tail] = *message;
    if (++ q->tail >= q->cap) {
        q->tail = 0;
    }

    if (q->head == q->tail) {
        expand_queue(q);
    }
    //如果次级消息队列没在主消息队列中,则link 进主消息队列,添加到尾部。
    if (q->in_global == 0) {
        q->in_global = MQ_IN_GLOBAL;
        skynet_globalmq_push(q);
    }
    
    SPIN_UNLOCK(q)
}

module、skynet_context、message_queue、handle_storage、global_queue 构成了服务的基本数据结构。后面将介绍框架的调度。
    
 

发布了45 篇原创文章 · 获赞 4 · 访问量 4万+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章