创建一个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)
}