数据包一般是在一个服务内打包生成的,Skynet 并不关心数据包是怎样被打包的,它甚至不要求这个数据包内的数据是连续的(虽然这样很危险,在后面会谈及的跨机通信中会出错,除非你保证你的数据包绝对不被传递出当前所在的进程)。它仅仅是把数据包的指针,以及你声称的数据包长度(并不必定是真实长度)传递出去。因为服务都是在同一个进程内,接收方取得这个指针后,就能够直接处理其引用的数据了。api
这个机制能够在必要时,保证绝对的零拷贝,几乎等价于在同一线程内作一次函数调用的开销。session
但,这只是 Skynet 提供的性能上的可能性。它推荐的是一种更可靠,性能略低的方案:它约定,每一个服务发送出去的包都是复制到用 malloc 分配出来的连续内存。接收方在处理完这个数据块(在处理的 callback 函数调用完毕)后,会默认调用 free 函数释放掉所占的内存。即,发送方申请内存,接收方释放。多线程
咱们来看看 skynet_send
和 callback 函数的定义:框架
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 ); //发送一个数据包,就是发送 msg/sz 对。 //咱们能够在 type 里打上 dontcopy 的 tag (PTYPE_TAG_DONTCOPY) ,让框架不要复制 msg/sz 指代的数据包。不然 skynet 会用 malloc 分配一块内存,把数据复制进去。callback 函数在处理完这块数据后,会调用 free 释放内存。你能够经过让 callback 返回 1 ,阻止框架释放内存。这一般和在 send 时标记 dontcopy 标记配对使用。
skynet 核心并不解决进程间通信的问题。数据交换方式并不是相似 TCP 的数据流,因此,没有必要把服务间的通信形式强行统一为单个数据块。最合适作进程内通信的方式就是 C 结构。消息发送方和接收方都处于同一个进程内时,它们必定能够识别同一个 C 结构映射的内存块,没必要考虑内存布局,字节序等问题。在这个层面上使用这种更高效的数据交换方式,能够极大的提高性能。tcp
session 是什么?因为每一个服务仅有一个 callback 函数,比如在 ip 协议中去掉了端口的设定,全部发送到一个 ip 地址上的 ip 包就没法被分发到不一样的进程了。这时,咱们就须要有另外一个东西来区分这个包。这就是 session 的做用。使用 skynet_send
发送一个包的时候,你能够在 type 里设上 alloc session 的 tag (PTYPE_TAG_ALLOCSESSION
)。send api 就会忽略掉传入的 session 参数,而会分配出一个当前服务历来没有使用过的 session 号,发送出去。同时约定,接收方在处理完这个消息后,把这个 session 原样发送回来。这样,编写服务的人只须要在 callback 函数里记录下全部待返回的 session 表,就能够在收到每一个消息后,正确的调用对应的处理函数。函数
type 的做用?type 表示的是当前消息包的协议组别,而不是传统意义上的消息类别编号。协议组别类型并不会不少,因此,我限制了 type 的范围是 0 到 255 ,由一个字节标识。在实现时,我把 type 编码到了 size 参数的高 8 位。由于单个消息包限制长度在 16 M (24 bit)内,是个合理的限制。这样,为每一个消息增长了 type 字段,并无额外增长内存上的开销。布局
为何整个系统不统一使用一种消息编码协议?这样,全部服务间都不会有沟通障碍。这样,甚至 session 参数也能够编码在数据包中。那仅仅是一丁点效率问题。可是,在真正的项目开发中,这其实作起来很难。尤为在使用第三方库的时候,你须要作不少低效的封装工做,才能够把交互协议都统一块儿来。并且,这个一致的编码协议也就成了系统底层约定的一部分,成为全部开发人员都须要了解的知识。性能
handle 也就是每一个服务的地址,在接口上看用的是一个 32 位整数。但实际上单个服务中 handle 的最终限制在 24bit 内,也就是 16M 个。高 8 位是保留给集群间通信用的。咱们最终容许 255 个 skynet 节点部署在不一样的机器上协做。每一个 skynet 节点有不一样的 id 。这里被称为 harbor id 。这个是独立指定,人为管理分配的(也能够写一个中央服务协调分配)。每一个消息包产生的时候,skynet 框架会把本身的 harbor id 编码到源地址的高 8 位。这样,系统内全部的服务模块(用handle来标识,高8位是 harbor id ,低24为本节点中的服务标识符),都有不一样的地址了。从数字地址,能够轻易识别出,这个消息是远程消息,仍是本地消息。ui
集群间的通信,是由一个独立的 harbor 服务来完成的。全部的消息包在发送时,skynet 识别出这是一个远程消息包时,都会把它转发到 harbor 服务内。harbor 服务会创建 tcp 链接到全部它认识的其它 skynet 节点内的 harbor 服务上。编码
全局名字服务:
skynet 目前支持一个全局名字服务,能够把一个消息包发送到特定名字的服务上。这个服务没必要存在于当前 skynet 节点中。这样,咱们就须要一个机构可以同步这些全局名字。为此,我实现了一个叫作 master 的服务。它的做用就是广播同步全部的全局名字,以及加入进来的 skynet 节点的地址。本质上,这些地址也是一种名字。一样能够用 key-value 的形式储存。即,每一个 skynet 节点号对应一个字符串的地址。
组播