Featured image of post Skynet源码阅读笔记(三)-SkynetetHandle

Skynet源码阅读笔记(三)-SkynetetHandle

Skynet源码阅读笔记-SkynetetHandle

在一个skyent进程中,handle的作用是用来通过它查找对应注册的ctx。

e而在 skynet_context_new 创建一个新的ctx的时候, 会通过 skynet_handle_register 来获取 ctx 对应的 handle。下面看看具体是怎么玩的。

1
2
3
4
5
6
7
struct skynet_context * 
skynet_context_new(const char * name, const char *param) {
	...
	ctx->handle = 0;	
	ctx->handle = skynet_handle_register(ctx); 
	...
}

handle_storage

先看一下 handle_storage 这个结构

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21

struct handle_name {
	char * name;
	uint32_t handle;
};

struct handle_storage {
	struct rwlock lock; // 读写锁

	uint32_t harbor;  // 本skynet节点的harbor

	uint32_t handle_index;  // 下一个handle 插入时查找的位置
	int slot_size;  // slot 的长度
	struct skynet_context ** slot; // skynet_context 数组 
	
	int name_cap;  // 当前数组的长度
	int name_count;  // 当前使用个数
	struct handle_name *name;   // 名字映射handle的数组
};

static struct handle_storage *H = NULL;

handle_storage结构很简单,就是一个 skynet_context 的数组,只是因为它是一个全局的对象,所以需要一个读写锁来控制访问。

skynet_handle_register

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
uint32_t
skynet_handle_register(struct skynet_context *ctx) {
	struct handle_storage *s = H;

	rwlock_wlock(&s->lock);
	
	for (;;) {
		int i;
		uint32_t handle = s->handle_index;

		// 从handle_index 位置开始找一个可以插入的位置
		for (i=0;i<s->slot_size;i++,handle++) { 
			if (handle > HANDLE_MASK) {
				// 0 is reserved
				handle = 1;
			}
			int hash = handle & (s->slot_size-1);
			if (s->slot[hash] == NULL) {
				// 找到空位就插入返回
				s->slot[hash] = ctx;
				s->handle_index = handle + 1;

				rwlock_wunlock(&s->lock);

				handle |= s->harbor;
				return handle;
			}
		}
		assert((s->slot_size*2 - 1) <= HANDLE_MASK);
		// 需要扩容了
		struct skynet_context ** new_slot = skynet_malloc(s->slot_size * 2 * sizeof(struct skynet_context *));
		memset(new_slot, 0, s->slot_size * 2 * sizeof(struct skynet_context *));
		for (i=0;i<s->slot_size;i++) {
			if (s->slot[i]) {
				int hash = skynet_context_handle(s->slot[i]) & (s->slot_size * 2 - 1);
				assert(new_slot[hash] == NULL);
				new_slot[hash] = s->slot[i];
			}
		}
		skynet_free(s->slot);
		s->slot = new_slot;
		s->slot_size *= 2;
		// 扩容完成后再插入一遍
	}
}

skynet_handle_register 需要做的事情就是将 ctx 插入到一个当前空着的 slot 中。

因为是修改数组,所以进入函数的时候就获取写锁,这边从 handle_index 开始遍历数组,转一圈如果没找到空位就将数组扩充成原来的2倍,然后再执行一个插入。找到空位后将数组下标返回,这个数组下标就是 skynet_context 的handle。

稍微值得注意的是,因为返回的是下标,所以扩容的时候原来位置对应的元素还是应该一样的。

skynet_handle_namehandle

这个接口是用名字和handle进行把绑定

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
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;
}

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

在 _insert_name 中可以很明显的看出来, handle_storage 的 name 数组是一个有序的数组,插入的时候通过二分来找到插入的位置,然后调用 _insert_name_before 完成插入, _insert_name_before 中包含了对该数组的扩容以及元素移动等行为。

skynet_handle_retire

看一下删除操作

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
int
skynet_handle_retire(uint32_t handle) {
	int ret = 0;
	struct handle_storage *s = H;

	rwlock_wlock(&s->lock);

	uint32_t hash = handle & (s->slot_size-1);
	struct skynet_context * ctx = s->slot[hash];

	if (ctx != NULL && skynet_context_handle(ctx) == handle) {
		s->slot[hash] = NULL;
		ret = 1;
		int i;
		int j=0, n=s->name_count;
		// 开始删除name中的对应关系
		for (i=0; i<n; ++i) {
			if (s->name[i].handle == handle) {
				skynet_free(s->name[i].name);
				continue;
			} else if (i!=j) {
				s->name[j] = s->name[i];
			}
			++j;
		}
		s->name_count = j;
	} else {
		ctx = NULL;
	}

	rwlock_wunlock(&s->lock);

	if (ctx) {
		// release ctx may call skynet_handle_* , so wunlock first.
		skynet_context_release(ctx);
	}

	return ret;
}

因为handle 本身就是数组下标,所以对于删除 slot 来说很简单,相对麻烦的事情是删name里面的对应,需要遍历和移动整个数组。

skynet_handle_init

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
void 
skynet_handle_init(int harbor) {
	assert(H==NULL);
	struct handle_storage * s = skynet_malloc(sizeof(*H));
	s->slot_size = DEFAULT_SLOT_SIZE;
	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
	s->harbor = (uint32_t) (harbor & 0xff) << HANDLE_REMOTE_SHIFT;
	s->handle_index = 1;
	s->name_cap = 2;
	s->name_count = 0;
	s->name = skynet_malloc(s->name_cap * sizeof(struct handle_name));

	H = s;

	// Don't need to free H
}

整个结构初始化的行为 在 skynet_start 的时候调用 skynet_handle_init,这边简单的为几个数组长度进行了初始化。稍微有点点疑惑的是 特别注释了一个 不用free H。 那H的内存什么时候释放呢?

Licensed under CC BY-NC-SA 4.0
Last updated on 2023-11-21 00:00 UTC
Built with Hugo
Theme Stack designed by Jimmy