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萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章