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行。

posted on 2022-02-28 13:58  &大飞  阅读(40)  评论(0编辑  收藏  举报

导航