創建一個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)
}