[关闭]
@wsd1 2017-01-19T07:57:00.000000Z 字数 3712 阅读 2664

【专题2】skynet之C版本的gate_service分析

skynet


参考文档

http://cloudwu.github.io/lua53doc/manual.html#6.4.1

https://github.com/cloudwu/skynet/wiki

从 Commits on Apr 13, 2014 开始,skynet开始添加了 lua版本的gate实现,版本号是 8e92930413e8e18f55f389e93d522614c88eb4c6。
如果想要看到早期无干扰版本,可以提取前一个commit:
https://github.com/cloudwu/skynet/tree/8e92930413e8e18f55f389e93d522614c88eb4c6

文件 见:service-src/service_gate.c

综述

关于gate的说明,细节可见:
http://blog.codingnow.com/2012/09/the_design_of_skynet.html

云风设计思路:

无论是哪种模式,控制信息都是交给 watchdog 去处理的,而数据包如果不发给 watchdog 而是发送给 agent 或 broker的话,则不会有额外的数据头(也减少了数据拷贝)。
识别这些包是从外部发送进来的方法是检查消息包的类型是否为 PTYPE_CLIENT 。

代码分析

C版本的service总是被编译成.so文件。需要时被加载,通过create函数被创建,通过init函数被初始化(安装callback),通过release函数被释放。

service_gate.c中,三个函数分别是gate_create(), gate_init(), gate_release()。

gate_init()分析

代码框架如下:

int
gate_init(struct gate *g , struct skynet_context * ctx, char * parm) {
    ...
    int n = sscanf(parm, "%c %s %s %d %d", &header, watchdog, binding, &client_tag, &max);
    //这里通过参数获取配置信息
    ...
    //下面是一个skynet address(id)到connect的hash映射
    hashid_init(&g->hash, max);
    g->conn = skynet_malloc(max * sizeof(struct connection));
    ...
    //安装回调函数
    skynet_callback(ctx,g,_cb);
    //listen
    return start_listen(g,binding);
}

处理事件的回调函数_cb() 分析

static int
_cb(struct skynet_context * ctx, void * ud, int type, int session, uint32_t source, const void * msg, size_t sz) {
    struct gate *g = ud;
    switch(type) {
    case PTYPE_TEXT:
        _ctrl(g , msg , (int)sz);
        break;
    case PTYPE_CLIENT: {
        ...
        uint32_t uid = idbuf[0] | idbuf[1] << 8 | idbuf[2] << 16 | idbuf[3] << 24;
        int id = hashid_lookup(&g->hash, uid);
        if (id>=0) {
            // don't send id (last 4 bytes)
            skynet_socket_send(ctx, uid, (void*)msg, sz-4);
            return 1;   // return 1 means don't free msg
        }
        ...
    }
    case PTYPE_SOCKET:
        // recv socket message from skynet_socket
        dispatch_socket_message(g, msg, (int)(sz-sizeof(struct skynet_socket_message)));
        break;
    }
    return 0;
}

可见其处理三种消息,text、client和socket类型。一一分析:
“PTYPE_TEXT”:主要用于接收本地指令。

static void
_ctrl(struct gate * g, const void * msg, int sz) {
    struct skynet_context * ctx = g->ctx;
    ...
    if (memcmp(command,"kick",i)==0) {
        ...
    }
    if (memcmp(command,"forward",i)==0) {
        ...
        _forward_agent(g, id, agent_handle, client_handle);
        return;
    }
    if (memcmp(command,"broker",i)==0) {
        ...
        g->broker = skynet_queryname(ctx, command);
        return;
    }
    if (memcmp(command,"start",i) == 0) {
        ...
        skynet_socket_start(ctx, uid);
        return;
    }
    if (memcmp(command, "close", i) == 0) {
        ...
        skynet_socket_close(ctx, g->listen_id);
        ...
    }
    skynet_error(ctx, "[gate] Unkown command : %s", command);
}

"kick"、“start”,"close"这类的指令功能明确毋庸质疑。我们主要看一下forward指令的用法。
详见:"gate_server的forward原理分析" 章节

gate_server的forward原理分析

开头已经说明agent模式中,gate会将数据直接发给agent,这里的主要设置这个关系。在lua中对应的调用类如:

\service\watchdog.lua,line14:

if agent then
    agent_all[self] = { agent , client }
    skynet.send(gate, "text", "forward" , self, skynet.address(agent) , skynet.address(client))
end

//上述代码判断是否有agent这个参数,若是有则关联agent。

注意:后期的skynet版本都开始使用lua版本的gate_server,所以找不到使用场景,只要利用下面的指令回归一下老版本:

git checkout 52cf86403735d4f64ab2ff5458d9a2c743388ae5

可以回溯到早期版本, “service_gate.c”这个文件刚刚加入,可以在watchdog.lua中看到使用方式。

可见:

lua代码:
skynet.send(gate, "text", "forward" , self, skynet.address(agent) , skynet.address(client))

对应

c代码:

static void
_forward_agent(struct gate * g, int id, uint32_t agentaddr, uint32_t clientaddr) {
    struct connection * agent = lookup_id(g,id);
    if (agent) {
        agent->agent = agentaddr;
        agent->client = clientaddr;
    }
}

代码很简单,通过connection_id找到connection结构,并赋值agent和client。之后的事情就是在“socket”事件处理函数中发生的,其依次调用: dispatch_message() -> _forward()

_forward()中:
...
if (c->agent) {
    void * temp = malloc(c->header);
    read_data(g,c,temp, c->header);
    skynet_send(ctx, c->client, c->agent, g->client_tag | PTYPE_TAG_DONTCOPY, 0 , temp, c->header); 
    //注意上面用clinet的addr来设置source字段,就是为了假装是client发出的。
    //另外,发出的msg类型也是client类型
}
else if (g->watchdog) {
    char * tmp = malloc(c->header + 32);
    int n = snprintf(tmp,32,"%d data ",c->id);
    read_data(g,c,tmp+n,c->header);
    skynet_send(ctx, 0, g->watchdog, PTYPE_TEXT | PTYPE_TAG_DONTCOPY, 0, tmp, c->header + n);
    //这个就是用9表示source,类型也是TEXT了。
}
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注