skynet源码分析之网络层——网关服务器(转)
在上一篇文章里介绍Lua层通过lualib/skynet/socket.lua这个库与网络底层交互(http://www.cnblogs.com/RainRill/p/8707328.html)。除此之外,skynet还提供一个通用模板lualib/snax/gateserver来启动一个网关服务器,通过TCP连接和客户端交换数据,这个库不能与socket.lua共用,因为这个库接管了底层传来的socket类消息,具体用法参考官方wiki https://github.com/cloudwu/skynet/wiki/GateServer。
1. 概述
gateserver注册接收网络底层传过来的socket消息,通过netpack.filter解析消息包(第6行),稍后会着重分析如何解析,解析完返回的type有6中类型,每种类型指定特定的回调函数。注:当一个包不完整时,type为nil,这种情况不需要处理。
"open":新连接建立;"close":关闭连接;"warning":当fd上待发送的数据累积超过1M时,会收到这个消息;’"error":发生错误,关闭fd;“data”:表示收到一个完整的tcp包,回调函数把这个包传给逻辑层去处理;
1 -- lualib/snax/gateserver.lua
2 skynet.register_protocol {
3 name = "socket",
4 id = skynet.PTYPE_SOCKET, -- PTYPE_SOCKET = 6
5 unpack = function ( msg, sz )
6 return netpack.filter( queue, msg, sz)
7 end,
8 dispatch = function (_, _, q, type, ...)
9 queue = q
10 if type then
11 MSG[type](...)
12 end
13 end
14 }
“more”:表示收到的数据不止一个tcp包,netpack.filter会把包依次放到队列里,然后回调函数一个个从队列中pop出来(第11行)
1 -- lualib/snax/gateserver.lua 2 local function dispatch_queue() 3 local fd, msg, sz = netpack.pop(queue) 4 if fd then 5 -- may dispatch even the handler.message blocked 6 -- If the handler.message never block, the queue should be -- 7 -- empty, so only fork once and then exit. 8 skynet.fork(dispatch_queue) 9 dispatch_msg(fd, msg, sz) 10 11 for fd, msg, sz in netpack.pop, queue do 12 dispatch_msg(fd, msg, sz) 13 end 14 end 15 end 16 17 MSG.more = dispatch_queue
2. 如何解析TCP数据包
为了说明如何解析TCP数据包,先了解下网络底层是采用什么策略接收数据的。单个socket每次从内核尝试读取的数据字节数为sz(第6行),这个值保存在s->p.size中,初始是MIN_READ_BUFFER(64b),当实际读到的数据等于sz时,sz扩大一倍(8-9行);如果小于sz的一半,则设置sz为原来的一半(10-11行)。
比如,客户端发了一个1kb的数据,socket线程会从内核里依次读取64b,128b,256b,512b,64b数据,总共需读取5次,即会向gateserver服务发5条消息,一个TCP包被切割成5个数据块。第5次尝试读取1024b数据,所以可能会读到其他TCP包的数据(只要客户端有发送其他数据)。接下来,客户端再发一个1kb的数据,socket线程只需从内核读取一次即可。
1 // skynet-src/socket_server.c
2 static int
3 forward_message_tcp(struct socket_server *ss, struct socket *s, struct socket_lock *l, struct socket_message * result) {
4 int sz = s->p.size;
5 char * buffer = MALLOC(sz);
6 int n = (int)read(s->fd, buffer, sz);
7 ...
8 if (n == sz) {
9 s->p.size *= 2;
10 } else if (sz > MIN_READ_BUFFER && n*2 < sz) {
11 s->p.size /= 2;
12 }
13 }
netpack做的工作就是把这些数据块组装成一个完整的TCP包,再交给gateserver去处理。注:netpack约定,tcp包头两字节(大端方式)表示数据包长度。如果采用sproto打包方式,需附加4字节(32位)的session值。所以客户端传过来1kb数据,实际数据只有1024-2-4=1018字节。
数据结构:
16-19行,用数组实现的队列。当客户端连续发了几个小的tcp包,gateserver收到的一条消息包可能包含多个tcp包,存放到这个队列里
第20行,存放不完整的tcp包的指针数组,每一项是指向一个链表,fd hash值相同的组成一个链表。
1 // lualib-src/lua-netpack.c
2 struct netpack {
3 int id; //socket id
4 int size; //数据块长度
5 void * buffer; //数据块
6 };
7
8 struct uncomplete { //不完整tcp包结构
9 struct netpack pack; //数据块信息
10 struct uncomplete * next; //链表,指向下一个
11 int read; //已读的字节数
12 int header; //第一个字节(代表数据长度的高8位)
13 };
14
15 struct queue {
16 int cap;
17 int head;
18 int tail;
19 struct netpack queue[QUEUESIZE]; //一次从内核读取多个tcp包时放入该队列里
20 struct uncomplete * hash[HASHSIZE]; //指针数组,数组里每个位置指向一个不完整的tcp包链表,fd hash值相同的组成一个链表
21 };
解析流程:
最终会调用filter_data_这个接口解析,下面着重介绍:
参数:fd socket; buffer从内核中读到的数据块;size数据块大小
先看后半段46-82行,当queue里并没有该socket剩余的数据块,执行46行分支。
46-51行,是一个不完整的tcp包,只有一个字节数据,说明表示长度的头部两字节数据都还差一个字节,构造一个uncomplete结构(简称uc),然后存在queue->hash里。uc->read设置为-1,uc->header存放这一个字节。返回给Lua层的type是nil,Lua层不需要处理。
52-54行,通过头部两字节计算tcp包的长度read_size,接下来比较收到的数据size与真正需要的数据pack_size。
56-63行,size<pack_size,说明tcp包还有未读到的数据,将已读到的数据构造一个uc结构,保存在queue->hash里,返回给Lua层的type是nil,Lua层不需要处理。uc->read已读到字节,uc->pack.size目标字节数
64-73行,size=pack_size,说明是一个完整的tcp包,大部分是这种情况,把tcp包返回给Lua层即可,此时返回的type是“data”(第66行)。
74-82行,size>pack_size,说明不止一个tcp包的数据,则先通过push_data保存第一个完整的tcp包(76行),接着通过push_more处理余下的数据(79行)。返回的type是"more"。
push_data做的工作是将tcp包保存在队列里,供Lua层pop出使用。
push_more是一个递归操作,流程跟上面一样,对比读到的数据和需要的数据做对应的处理。
接着看6-44行,之前收到了tcp包的部分数据块。
8-18行,说明之前只读到一个字节,加上该数据块的第一个字节,组成两个字节计算出整个包的长度(12行)
第19行,目标字节-已读字节=需要的字节need。
20-27行,如果size<need,说明仍然还差数据块没收到,此时将数据附加到之前的uc->pack.buffer里。
28-44行,其他两种情况跟上面处理流程一样。
1 // lualib-src/lua-netpack.c
2 static int
3 filter_data_(lua_State *L, int fd, uint8_t * buffer, int size) {
4 struct queue *q = lua_touserdata(L,1);
5 struct uncomplete * uc = find_uncomplete(q, fd);
6 if (uc) { //之前收到该包的部分数据块,
7 // fill uncomplete
8 if (uc->read < 0) {//之前只收到一个字节,加上该数据块的第一个字节,表示整个包的长度
9 // read size
10 assert(uc->read == -1);
11 int pack_size = *buffer;
12 pack_size |= uc->header << 8 ;
13 ++buffer;
14 --size;
15 uc->pack.size = pack_size;
16 uc->pack.buffer = skynet_malloc(pack_size);
17 uc->read = 0;
18 }
19 int need = uc->pack.size - uc->read;//包还差多少字节
20 if (size < need) {
21 memcpy(uc->pack.buffer + uc->read, buffer, size);
22 uc->read += size;
23 int h = hash_fd(fd);
24 uc->next = q->hash[h];
25 q->hash[h] = uc;
26 return 1;
27 }
28 memcpy(uc->pack.buffer + uc->read, buffer, need);
29 buffer += need;
30 size -= need;
31 if (size == 0) {
32 lua_pushvalue(L, lua_upvalueindex(TYPE_DATA));
33 lua_pushinteger(L, fd);
34 lua_pushlightuserdata(L, uc->pack.buffer);
35 lua_pushinteger(L, uc->pack.size);
36 skynet_free(uc);
37 return 5;
38 }
39 // more data
40 push_data(L, fd, uc->pack.buffer, uc->pack.size, 0);
41 skynet_free(uc);
42 push_more(L, fd, buffer, size);
43 lua_pushvalue(L, lua_upvalueindex(TYPE_MORE));
44 return 2;
45 } else {
46 if (size == 1) {
47 struct uncomplete * uc = save_uncomplete(L, fd);
48 uc->read = -1;
49 uc->header = *buffer;
50 return 1;
51 }
52 int pack_size = read_size(buffer); //需要数据包的字节数
53 buffer+=2;
54 size-=2;
55
56 if (size < pack_size) { //说明还有未获得的数据包
57 struct uncomplete * uc = save_uncomplete(L, fd); //保存这个数据包
58 uc->read = size;
59 uc->pack.size = pack_size;
60 uc->pack.buffer = skynet_malloc(pack_size);
61 memcpy(uc->pack.buffer, buffer, size);
62 return 1;
63 }
64 if (size == pack_size) { //说明是一个完整包,把包返回给Lua层即可
65 // just one package
66 lua_pushvalue(L, lua_upvalueindex(TYPE_DATA));
67 lua_pushinteger(L, fd);
68 void * result = skynet_malloc(pack_size);
69 memcpy(result, buffer, size);
70 lua_pushlightuserdata(L, result);
71 lua_pushinteger(L, size);
72 return 5;
73 }
74 // more data
75 // 说明不止同一个数据包,还有额外的
76 push_data(L, fd, buffer, pack_size, 1); //保存第一个包到q->queue中
77 buffer += pack_size;
78 size -= pack_size;
79 push_more(L, fd, buffer, size); //处理余下的包
80 lua_pushvalue(L, lua_upvalueindex(TYPE_MORE));
81 return 2;
82 }
83 }
举例,客户端发了一个1kb的数据,socket线程会从内核里依次读取64b,128b,256b,512b,64b数据。gateserver会执行5次filter_data_,分支依次是56行,20行,20行,20行,31行。

浙公网安备 33010602011771号