skynet源码分析之skynet_handle
skynet_handle是所有服务(ctx)的仓库(handle_storage),存储所有ctx。
struct handle_name { //ctx的handle与name对应关系 char * name; uint32_t handle; }; struct handle_storage { //管理所有ctx结构 struct rwlock lock; //读写锁 uint32_t harbor; //每个skynet节点可配置一个唯一harbor,用于与其他节点组网 uint32_t handle_index; //下次从handle_index位置开始查找空的位置 int slot_size; //已分配的ctx的容量 struct skynet_context ** slot; //slot_size容量的数组,每一项指向一个ctx int name_cap; //已分配ctx名字的容量 int name_count; //已用的名字 struct handle_name *name; //name_cap容量的数组,每一项是一个handle_name }; static struct handle_storage *H = NULL;
skynet启动时初始化handle_storage(S)
void skynet_handle_init(int harbor) { //初始化,harbor在配置文件中可配置 assert(H==NULL); struct handle_storage * s = skynet_malloc(sizeof(*H)); s->slot_size = DEFAULT_SLOT_SIZE; //给slot分配slot_size个ctx内存, s->slot = skynet_malloc(s->slot_size * sizeof(struct skynet_context *)); memset(s->slot, 0, s->slot_size * sizeof(struct skynet_context *)); rwlock_init(&s->lock); //读写锁 // reserve 0 for system // harbor取高8位,所以最多可组网2^8=255 skynet节点 s->harbor = (uint32_t) (harbor & 0xff) << HANDLE_REMOTE_SHIFT; s->handle_index = 1; s->name_cap = 2; s->name_count = 0; //给name分配name_cap个handle_name内存 s->name = skynet_malloc(s->name_cap * sizeof(struct handle_name)); H = s; // Don't need to free H }
为了效率,S->lock采用读写锁,因为获取一个ctx(读)的频率要远远大于创建/杀掉(写)一个ctx的频率,所以工作线程可以并发获取ctx。
S->harbor在skynet启动文件中配置,每个skynet节点可配置唯一的harbor,用于与其他skynet节点组网,harbor占8位,所以最高可组网2^8个。
S->slot存放所有ctx指针,是一个数组,容量为S->slot_size。当new一个ctx时,会给ctx注册一个对应的唯一的handle(skynet_handle_register)。handle从S->handle_index开始循环,通过handle&(S->slot_size-1)映射成一个hash值,如果在slot中未使用,则找到ctx对应的handle,在slot存放ctx指针,并设置ctx->handle=hande。当找不到空位置(数组满)时,重新申请之前2倍容量大小的内存空间,然后把数据迁移到新的空间里。这不就是c++里vector的实现方式嘛,skynet里有很多类似的实现机制,用数组+容量实现类似vector的功能。当kill一个ctx时会回收handle(skynet_handle_retire),置空ctx在slot里位置,供下一个ctx使用。skynet里每个ctx都有一个唯一的handle对应,向某个ctx发送消息时,都是通过handle找到对应的ctx,然后向ctx的消息队列里push消息。注:handle是uint32_t,其中高8位为harbor id,低24位为handle id,所以总共可创建2^24个ctx。
在上层逻辑很难记住每个handle具体代表哪个服务,通常会为handle注册name(不限一个),通过name找到对应的handle,通过S->name实现。S->name是一个数组,类似S->slot,动态分配内存,S->name_cap表示数组容量。S->name是按handle_name->name升序排序的,通过二分查找快速地查找name对应的handle(skynet_handle_findname)。给handle注册name时,也需保证注册完S->name有序(skynet_handle_namehandle)