skynet源代码阅读(3)

snlua实例初始化
看看snlua的snlua_init函数

int
snlua_init(struct snlua *l, struct skynet_context *ctx, const char * args) {
    int sz = strlen(args);
    char * tmp = skynet_malloc(sz);
    memcpy(tmp, args, sz);
    skynet_callback(ctx, l , launch_cb);
    const char * self = skynet_command(ctx, "REG", NULL);
    uint32_t handle_id = strtoul(self+1, NULL, 16);

    printf("snlua_init %d\n", handle_id);
    // it must be first message
    //PTYPE_TAG_DONTCOPY
    skynet_send(ctx, 0, handle_id, PTYPE_TAG_DONTCOPY,0, tmp, sz);
    return 0;
}

函数skynet_callback
Skynet 的核心功能就是发送消息和处理消息。它体现在 skynet_send 和 skynet_callback 两个 api 上

void 
skynet_callback(struct skynet_context * context, void *ud, skynet_cb cb) {
    context->cb = cb;
    context->cb_ud = ud;
}

函数skynet_command
它接收一个字符串参数,返回一个字符串结果。你可以看成是一种文本协议。但 skynet_command 保证在调用过程中,不会切出当前的服务线程,导致状态改变的不可预知性。其每个功能的实现,其实也是内嵌在 skynet 的源代码中,相同上层服务,还是比较高效的。

void 
skynet_callback(struct skynet_context * context, void *ud, skynet_cb cb) {
    context->cb = cb;
    context->cb_ud = ud;
}

strtoul() 函数源自于“string to unsigned long”,用来将字符串转换成无符号长整型数(unsigned long),其原型为:
unsigned long strtoul (const char* str, char** endptr, int base);
str 为要转换的字符串,endstr 为第一个不能转换的字符的指针,base 为字符串 str 所采用的进制。

skynet_send(ctx, 0, handle_id, PTYPE_TAG_DONTCOPY,0, tmp, sz);

就是在snlua实例初始化的时候发送一个不需要复制的msg/sz的数据包给自己

int skynet_send(
  struct skynet_context * context, 
  uint32_t source, 
  uint32_t destination,
  int type,
  int session,
  void * msg, 
  size_t sz
);

typedef int (*skynet_cb)(
  struct skynet_context * context,
  void *ud, 
  int type, 
  int session, 
  uint32_t source ,
  const void * msg,
  size_t sz
);

source 和 destination 都是 32 位整数,表示地址。原则上不需要填写 source 地址,因为默认就是它自己。0 是系统保留的 handle ,可以指代自己。这里允许填写 source 值,是因为在某些特殊场合,需要伪造一个由别人发出的包。姑且可以理解 source 为 reply address 。

发送一个数据包,就是发送 msg/sz 对。我们可以在 type 里打上 dontcopy 的 tag (PTYPE_TAG_DONTCOPY) ,让框架不要复制 msg/sz 指代的数据包。否则 skynet 会用 malloc 分配一块内存,把数据复制进去。callback 函数在处理完这块数据后,会调用 free 释放内存。你可以通过让 callback 返回 1 ,阻止框架释放内存。这通常和在 send 时标记 dontcopy 标记配对使用.

在skynet_send函数中将消息加入destination对应的消息队列,skynet_harbor_message_isremote是重点

    if (skynet_harbor_message_isremote(destination)) {
        struct remote_message * rmsg = skynet_malloc(sizeof(*rmsg));
        rmsg->destination.handle = destination;
        rmsg->message = data;
        rmsg->sz = sz;
        skynet_harbor_send(rmsg, source, session);
    } else {
        struct skynet_message smsg;
        smsg.source = source;
        smsg.session = session;
        smsg.data = data;
        smsg.sz = sz;

        if (skynet_context_push(destination, &smsg)) {
            skynet_free(data);
            return -1;
        }
    }

在工作线程中thread_worker -> skynet_context_message_dispatch->dispatch_message->ctx的cb函数

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);
    }
    ++ctx->message_count;
    int reserve_msg;
    if (ctx->profile) {
        ctx->cpu_start = skynet_thread_time();
        reserve_msg = ctx->cb(ctx, ctx->cb_ud, type, msg->session, msg->source, msg->data, sz);
        uint64_t cost_time = skynet_thread_time() - ctx->cpu_start;
        ctx->cpu_cost += cost_time;
    } else {
        reserve_msg = ctx->cb(ctx, ctx->cb_ud, type, msg->session, msg->source, msg->data, sz);
    }
    if (!reserve_msg) {
        skynet_free(msg->data);
    }
    CHECKCALLING_END(ctx)
}

type 表示的是当前消息包的协议组别,而不是传统意义上的消息类别编号。协议组别类型并不会很多,所以,我限制了 type 的范围是 0 到 255 ,由一个字节标识。在实现时,skynet把 type 编码到了 size 参数的高 8 位。因为单个消息包限制长度在 16 M (24 bit)内,是个合理的限制。这样,为每个消息增加了 type 字段,并没有额外增加内存上的开销。

你可能感兴趣的:(skynet源代码阅读(3))