skynet流程分析二

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执行文本指令,最后返回执行结果给客户端。消息返回完毕。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值