skynet 服务的名字

skynet服务的名字我们在开发过程中会使用到,这篇文章试着讲解一下skynet服务名字的有关内容。

首先每个服务在初始化的时候都会调用skynet_command(ctx, "REG", ...)。例如snlua服务调用了skynet_command(ctx, "REG", NULL),logger服务调用skynet_command(ctx, "REG", ".logger"),最终会调用到cmd_reg这个函数,我们看下这个函数:

static const char *
cmd_reg(struct skynet_context * context, const char * param) {
    if (param == NULL || param[0] == '\0') {
        sprintf(context->result, ":%x", context->handle);
        return context->result;
    } else if (param[0] == '.') {
        return skynet_handle_namehandle(context->handle, param + 1);
    } else {
        skynet_error(context, "Can't register global name %s in C", param);
        return NULL;
    }
}

snlua服务初始化的时候参数是NULL,所以基本上没有做什么事,context的result其实是一个辅助性的缓存区,在后面没有实际用途。给服务注册名字的api是skynet.register,他在manager.lua中,最终也会调用到上面的函数。注册的函数必须是带点.,否则就是全局服务名,这个在后面的文章中会讲到。

调用skynet_handle_namehandle函数就会把服务的handle id和名字写在全局的handle_storage结构体中。他的算法是全局结构体中有一个保存名字和handle id结构体的数组,注册时就往数组里写入。这里写入用到了一个常用的简单算法--二分法,具体请看:

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;
}

static void _insert_name_before(struct handle_storage *s, char *name, uint32_t handle, int before) {
    if (s->name_count >= s->name_cap) {
        s->name_cap *= 2;
        assert(s->name_cap <= MAX_SLOT_SIZE);
        struct handle_name * n = skynet_malloc(s->name_cap * sizeof(struct handle_name));
        int i;
        for (i=0;i<before;i++) {
            n[i] = s->name[i];
        }
        for (i=before;i<s->name_count;i++) {
            n[i+1] = s->name[i];
        }
        skynet_free(s->name);
        s->name = n;
    } else {
        int i;
        for (i=s->name_count;i>before;i--) {
            s->name[i] = s->name[i-1];
        }
    }
    s->name[before].name = name;
    s->name[before].handle = handle;
    s->name_count ++;
}

代码很清晰,插入名字的时候会通过二分法找到要插入的位置(根据字符串ASCII顺序排位),然后写入插入的名字和handle id数组,累加数组大小。注意上面还有一个数组扩容的过程。

给服务命名是为了方便后面的调用。我们skynet.send或skynet.call的时候,可以使用服务的地址,但是更多的时候我们使用的是服务名,毕竟服务名方便记忆。在skynet.send或skynet.call函数的c底层实现中,首先会判断第一个参数是否为number,是的就调用skynet_send否则调用skynet_sendname。我们看看skynet_sendname的实现:

int skynet_sendname(struct skynet_context * context, uint32_t source, const char * addr , int type, int session, void * data, size_t sz) {
    if (source == 0) {
        source = context->handle;
    }
    uint32_t des = 0;
    if (addr[0] == ':') {
        des = strtoul(addr+1, NULL, 16);
    } else if (addr[0] == '.') {
        des = skynet_handle_findname(addr + 1);
        if (des == 0) {
            if (type & PTYPE_TAG_DONTCOPY) {
                skynet_free(data);
            }
            return -1;
        }
    } else {
        _filter_args(context, type, &session, (void **)&data, &sz);

        struct remote_message * rmsg = skynet_malloc(sizeof(*rmsg));
        copy_name(rmsg->destination.name, addr);
        rmsg->destination.handle = 0;
        rmsg->message = data;
        rmsg->sz = sz;

        skynet_harbor_send(rmsg, source, session);
        return session;
    }

    return skynet_send(context, source, des, type, session, data, sz);
}

可以看到如果名字带.则会调用skynet_handle_findname在全局handle_storage中查找,查找依然是用比较高效的二分查找法。对比数组中的名字,获取handle id,最终还是会调用skynet_send。如果查找不到名字就会走harbor服务进行远程调用,这个后面有机会再讲。我们还注意到,查找的名字也支持:1000001这样的格式。

服务查找自己服务名字的api是skynet.self(),在c底层实际上是调用我们上面的cmd_reg返回":id"的字符串,然后转换为number。

单纯的查找服务名的api是skynet.localname,在c底层会调用cmd_query,最终会调用skynet_handle_findname,返回":%0xid"类型的字符串,然后在skynet.localname中转化为number。

服务除了自己给自己注册名字之外,还可以给其他服务名命名,api是skynet.name('name', handleid) ,他首先也会判断名字是不是全局名,不是则会调用c层的cmd_name,最终也是调用skynet_handle_namehandle。

根据上面的命名算法,我们可以看出,可以为同一个服务命名多个名字。而把同一个名字给多个服务就会失败。

最后,skynet.adress会将一个number类型的地址转换为形如":%08x"的字符串。

以上讨论的都是单节点服务名,全局服务名的用法后面再讲解。

 

欢迎加入QQ群 858791125 讨论skynet,游戏后台开发,lua脚本语言等问题。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值