clinet对server的通信流程
首先从skynet_start.c文件开始看起。
作者在函数_start中,启动了多个工作线程(_worker),一个moniter线程(_moniter),一个定时器线程(_timer),一个网络线程(_socket)。我们来看看这些线程都在做什么?
worker线程(执行函数thread_worker()):
从全局消息队列中取一条消息队列,而后找到该消息队列的处理器(handle),通过处理器找到对应节点的上下文(skynet_context),而后,更新处理器的状态值,确定其不是无穷执行的状态。接下来执行消息请求。即接口(_dispatch_message),在该接口中会判定是否是广播消息,若不是,则执行节点的回调接口_cb(callback)。处理完毕之后,查看是否需要返回数据,若需要,则将处理结果压到目标节点的消息队列中。
timer线程:
定时器线程。
moniter线程:
我觉得这个线程,只是判定节点是否出错了,如果出错,进行修复一下。
socket线程(执行函数thread_socket()):
该线程主要是从epoll_wait的结果中读取消息,若有需要处理的消息则进行相关的处理。这里面的消息目前要说明的有三个:第一个是管道,作者把管道的读端放到了epoll中进行管理,也就是说,每次向管道中写数据,都是socket线程读取并处理的;第二个是gate产生的监听端口,也是由epoll管理,并且一旦产生了数据也是由socket处理;第三个是accept客户端的socket后,客户端发送到服务端的数据,此数据也由socket线程处理。
socket线程:
首先从socket线程开始。(ps:其中的socket_server应该就是整个socket的管理者)
客户端发起connect请求,socket线程进入到socket_server_poll()函数的case SOCKET_TYPE_LISTEN:中产生该客户端对应的skynet_socket然后返回上一层SOCKET_ACCEPT。然后经由上一层调用forward_message()向消息队列中压入该条消息(ps:需要注意的是在该函数内会将消息拷贝出来而不是使用指针或引用)。然后转由worker线程进行处理。
int
socket_server_poll(struct socket_server *ss, struct socket_message * result, int * more) {
for (;;) {
if (ss->checkctrl) {
if (has_cmd(ss)) { //通过select模式查看是否有cmd到来
int type = ctrl_cmd(ss, result);
if (type != -1) {
clear_closed_event(ss, result, type);
return type;
} else
continue;
} else {
ss->checkctrl = 0;
}
}
//如果已遍历完所有的socket事件
if (ss->event_index == ss->event_n) {
ss->event_n = sp_wait(ss->event_fd, ss->ev, MAX_EVENT); //重新获取socket事件
ss->checkctrl = 1;
if (more) {
*more = 0;
}
ss->event_index = 0;
if (ss->event_n <= 0) {
ss->event_n = 0;
return -1;
}
}
struct event *e = &ss->ev[ss->event_index++];
struct socket *s = e->s;
if (s == NULL) {
// dispatch pipe message at beginning
continue;
}
switch (s->type) {
case SOCKET_TYPE_CONNECTING:
return report_connect(ss, s, result);
case SOCKET_TYPE_LISTEN:
if (report_accept(ss, s, result)) { //accept产生新的套接字,但是现在还没有把它放到epoll中,而是做了一次记录。标志了一下哪个槽被引用了。并且,该消息由哪个服务处理(handle)。
return SOCKET_ACCEPT;
}
break;
case SOCKET_TYPE_INVALID:
fprintf(stderr, "socket-server: invalid socket\n");
break;
default:
if (e->read) {
int type;
if (s->protocol == PROTOCOL_TCP) {
type = forward_message_tcp(ss, s, result);//向消息队列中加入该条消息
} else {
type = forward_message_udp(ss, s, result);
if (type == SOCKET_UDP) {
// try read again
--ss->event_index;
return SOCKET_UDP;
}
}
if (e->write) {
// Try to dispatch write message next step if write flag set.
e->read = false;
--ss->event_index;
}
if (type == -1)
break;
clear_closed_event(ss, result, type);
return type;
}
if (e->write) {
int type = send_buffer(ss, s, result);
if (type == -1)
break;
clear_closed_event(ss, result, type);
return type;
}
break;
}
}
}
worker线程:
有消息传来其中的一个worker线程被唤醒,在skynet_context_message_dispatch()中遍历所有消息队列。然后调入到dispatch_message()内。
static void
dispatch_message(struct skynet_context *ctx, struct skynet_message *msg) {
assert(ctx->init);
CHECKCALLING_BEGIN(ctx)
pthread_setspecific(G_NODE.handle_key, (void *)(uintptr_t)(ctx->handle)); //设置线程自有变量
int type = msg->sz >> MESSAGE_TYPE_SHIFT;
size_t sz = msg->sz & MESSAGE_TYPE_MASK;
if (ctx->logfile) {
skynet_log_output(ctx->logfile, msg->source, type, msg->session, msg->data, sz);
}
if (!ctx->cb(ctx, ctx->cb_ud, type, msg->session, msg->source, msg->data, sz)) {
skynet_free(msg->data); //释放掉msg内malloc出来的内存
}
CHECKCALLING_END(ctx)
}
对于本条消息其ctx为gate服务,此处调用gate的_cb接口来处理数据。接下来我们看看service_gate.c是如何处理该消息的,gate模块发现消息是类型是PTYPE_SOCKET,于是乎直接走了接口dispatch_socket_message,在该接口中,继续执行逻辑,发现message->type为SKYNET_SOCKET_TYPE_ACCEPT,如果没有逻辑错误,而后,向现有的管道中写入一个start_socket的消息请求。
static void
dispatch_socket_message(struct gate *g, const struct skynet_socket_message * message, int sz) {
struct skynet_context * ctx = g->ctx;
switch(message->type) {
case SKYNET_SOCKET_TYPE_DATA: {
int id = hashid_lookup(&g->hash, message->id);
if (id>=0) {
struct connection *c = &g->conn[id];
dispatch_message(g, c, message->id, message->buffer, message->ud);
} else {
skynet_error(ctx, "Drop unknown connection %d message", message->id);
skynet_socket_close(ctx, message->id);
skynet_free(message->buffer);
}
break;
}
case SKYNET_SOCKET_TYPE_CONNECT: {
if (message->id == g->listen_id) {
// start listening
break;
}
int id = hashid_lookup(&g->hash, message->id);
if (id>=0) {
struct connection *c = &g->conn[id];
_report(g, "%d open %d %s:0",message->id,message->id,c->remote_name);
} else {
skynet_error(ctx, "Close unknown connection %d", message->id);
skynet_socket_close(ctx, message->id);
}
break;
}
case SKYNET_SOCKET_TYPE_CLOSE:
case SKYNET_SOCKET_TYPE_ERROR: {
int id = hashid_remove(&g->hash, message->id);
if (id>=0) {
struct connection *c = &g->conn[id];
databuffer_clear(&c->buffer,&g->mp);
memset(c, 0, sizeof(*c));
c->id = -1;
_report(g, "%d close", message->id);
}
break;
}
case SKYNET_SOCKET_TYPE_ACCEPT:
// report accept, then it will be get a SKYNET_SOCKET_TYPE_CONNECT message
assert(g->listen_id == message->id);
if (hashid_full(&g->hash)) {
skynet_socket_close(ctx, message->ud);
} else {
struct connection *c = &g->conn[hashid_insert(&g->hash, message->ud)]; //根据hash算法获得其对应的连接实例
if (sz >= sizeof(c->remote_name)) {
sz = sizeof(c->remote_name) - 1;
}
c->id = message->ud; //socket_server的socket列表中的槽位ID
memcpy(c->remote_name, message+1, sz);
c->remote_name[sz] = '\0';
skynet_socket_start(ctx, message->ud); //开始监听该socket,并与SOCKET_SERVER关联,向现有的管道中写入一个start_socket的消息请求
}
break;
case SKYNET_SOCKET_TYPE_WARNING:
skynet_error(ctx, "fd (%d) send buffer (%d)K", message->id, message->ud);
break;
}
}
socket线程:
epoll在监听到该请求后,通过了socket_server_poll()内的if (has_cmd(ss))检测,执行ctrl_cmd接口根据传入的type为’S’转入到start_socket()函数。在里面将新建立的socket的accept端放到epoll中,并返回一个消息类型位SOCKET_OPEN,而后返回给skynet_socket_poll接口,该接口捕捉到返回类型后,向gate节点发送了一个socket消息类型为SKYNET_SOCKET_TYPE_CONNECT的消息,socket线程处理完毕。
workder线程:
在gate中执行_cb接口回调,发现有消息类型为PTYPE_SOCKET的消息,同时其数据中存放的是skynet_socket_message并且其类型为SKYNET_SOCKET_TYPE_CONNECT,在gate模块中的dispatch_socket_message接口中,捕捉到该类型的消息时,用_report接口来处理消息。该接口则是向watchdog节点发送了一条文本消息,该文本消息则执行watchdog中的command:open接口,这时候,为用户载入agent节点,和client节点。同时,向gate模块发送一条文本消息告诉gate将两者连接起来。worker处理完毕。
workder线程:
在gate中执行_cb接口回调,发现有消息类型为PTYPE_SOCKET的消息,同时其数据中存放的是skynet_socket_message并且其类型为SKYNET_SOCKET_TYPE_CONNECT,在gate模块中的dispatch_socket_message接口中,捕捉到该类型的消息时,用_report接口来处理消息。该接口则是向watchdog节点发送了一条文本消息,该文本消息则执行watchdog中的command:open接口,这时候,为用户载入agent节点,和client节点。同时,向gate模块发送一条文本消息告诉gate将两者连接起来。worker处理完毕。
worker线程:
由于此时已经不是向管道中写消息,所以,继续worker线程处理逻辑。worker线程抓取到gate的消息组,执行相关的逻辑,发现一条文本消息,中间含有forward命令,于是拆开参数,将agent和client连接起来,并且保存在该gate节点中。
经过以上步骤,客户端与服务端建立连接完毕。等待两者互相发送消息。
假设服务端需要向客户端发送消息:
agent向client节点发送一个服务请求,比如skynet.send(client,”text”,”Welcome to skynet”)
agent向服务节点client一条文本消息,”Welcome to skynet”,这是在脚本中执行的。首先调用lua-skynet模块的_command接口向节点client压一条服务消息,在worker线程处理到client节点时,取出消息,并调用client的_cb(callback)接口处理该消息,而后client节点向管道写入消息。在下次socket线程从管道中取出数据时,发消息类型是发送消息时会调用send_socket接口,向套接字中写入消息。消息发送完毕。
假设客户端向服务端发送消息:
客户端向socket中发送消息。socket线程首先捕捉到消息。取出消息,而后将该消息压到gate节点的消息组中。socket线程处理完毕。worker线程执行消息回调的时候,发现消息类型是PTYPE_SOCKET,其来源于socket。而后,检查该消息的数据部分,发现数据类型是SKYNET_SOCKET_TYPE_DATA,于是调用dispatch_message接口继续执行,当数据没有超出16M时,将数据传入_forward接口,在_forward接口中,用skynet_send接口发送消息到agent,agent在收到消息之后,发现消息类型是”client”。调用agent.lua中的dispatch接口处理。
agent节点中,dispatch接口首先将文本消息发送给log节点,而后调用simpledb执行文本指令,最后返回执行结果给客户端。消息返回完毕。