@wsd1
2017-01-19T08:00:55.000000Z
字数 5270
阅读 1776
skynet
http://cloudwu.github.io/lua53doc/manual.html#6.4.1
本篇分析UDP系列API中的客户端地址。
socket.udp()
function socket.udp(callback, host, port)local id = driver.udp(host, port) -- socketdriver节点建立udp,见下面的ludpcreate_udp_object(id, callback) --本地记录一下id和callback的关系return idend
lua-socket.c
static intludp(lua_State *L) {...const char * addr = lua_tolstring(L,1,&sz); //首先取出 地址字串“127.0.0.1”char tmp[sz];int port = 0;const char * host = NULL;if (addr) {host = address_port(L, tmp, addr, 2, &port); //从栈中或者从addr中取port,下面说明该函数}int id = skynet_socket_udp(ctx, host, port);...lua_pushinteger(L, id);return 1;}
address_port()函数不用分析下去了,这个函数功能很直接,就是尝试从lua栈中取port。
之所以独立成函数,是因为有 "127.0.0.1:8080"这样将端口写在addr中的写法。
这里面也会调整addr中的内容。
skynet_socket_udp()函数内部就 开始调用bind和socket这类的系统API,addr是字符串,port是数值,这里分析结束。
回到:
socket.lua
function socket.udp(callback, host, port)local id = driver.udp(host, port) -- 调用socketdriver的接口create_udp_object(id, callback)return idend
其中callback的接口如下:
function(str, from)
它被函数create_udp_object安装到一张表中:
socket.lua:
local function create_udp_object(id, cb)...socket_pool[id] = { --这是一个本地表 专用于记录fd和cb见的关系id = id,connected = true,protocol = "UDP",callback = cb,}end
通过表socket_pool[],我们找到 socket.lua安装的 “socket”协议的处理函数:
socket.lua:
-- SKYNET_SOCKET_TYPE_UDP = 6socket_message[6] = function(id, size, data, address)local s = socket_pool[id]...local str = skynet.tostring(data, size)...s.callback(str, address)end
可以看出,我们需要去 找找发送该消息的代码。下面的函数真正实现了将UDP数据到来事件中的数据和来源地址打包的过程。来源于 skynet_server.c
skynet_server.c:
static intforward_message_udp(struct socket_server *ss, struct socket *s, struct socket_message * result) {union sockaddr_all sa;socklen_t slen = sizeof(sa);int n = recvfrom(s->fd, ss->udpbuffer,MAX_UDP_PACKAGE,0,&sa.s,&slen);...uint8_t * data; //就是它。根据V4 V6来输出不同的长度,将其放置在数据块尾部if (slen == sizeof(sa.v4)) {...data = MALLOC(n + 1 + 2 + 4);gen_udp_address(PROTOCOL_UDP, &sa, data + n);} else {...data = MALLOC(n + 1 + 2 + 16);gen_udp_address(PROTOCOL_UDPv6, &sa, data + n);}memcpy(data, ss->udpbuffer, n); //<--- 这里就是将数据摆放到开头咯result->opaque = s->opaque;result->id = s->id;result->ud = n; //<---- 注意这里的长度 只是数据报文的长度 不包含地址信息的result->data = (char *)data;return SOCKET_UDP;}
gen_udp_address()函数更加清晰。
static intgen_udp_address(int protocol, union sockaddr_all *sa, uint8_t * udp_address) {int addrsz = 1;udp_address[0] = (uint8_t)protocol;if (protocol == PROTOCOL_UDP) {memcpy(udp_address+addrsz, &sa->v4.sin_port, sizeof(sa->v4.sin_port));addrsz += sizeof(sa->v4.sin_port);memcpy(udp_address+addrsz, &sa->v4.sin_addr, sizeof(sa->v4.sin_addr));addrsz += sizeof(sa->v4.sin_addr);} else {memcpy(udp_address+addrsz, &sa->v6.sin6_port, sizeof(sa->v6.sin6_port));addrsz += sizeof(sa->v6.sin6_port);memcpy(udp_address+addrsz, &sa->v6.sin6_addr, sizeof(sa->v6.sin6_addr));addrsz += sizeof(sa->v6.sin6_addr);}return addrsz;}
很清楚了,msg中依次摆放了 data、1字节类型,2字节port和 4或者16字节的addr。
然后到 skyne_socket.c中发送出来。
skynet_socket_poll() {...int type = socket_server_poll(ss, &result, &more); //这里拿到了所需信息switch (type) {...case SOCKET_UDP:forward_message(SKYNET_SOCKET_TYPE_UDP, false, &result); //这里发出来break;...}...}
forward_message()就不跟进去了,确定一点是这里发送的仅仅是msg和sz。
再回到socket.lua中:
skynet.register_protocol {name = "socket",id = skynet.PTYPE_SOCKET, -- PTYPE_SOCKET = 6unpack = driver.unpack, --这里将msg&size 转成 addrdispatch = function (_, _, t, ...)socket_message[t](...)end}
终于就要看到关键了,找到 lua-socket.c中的解包函数:
lua-socket.c:
static intlunpack(lua_State *L) {struct skynet_socket_message *message = lua_touserdata(L,1);...//上面各种push,可见就是为dispatch函数准备参数。if (message->type == SKYNET_SOCKET_TYPE_UDP) {int addrsz = 0;const char * addrstring = skynet_socket_udp_address(message, &addrsz);//这里是我们需要的addrif (addrstring) {lua_pushlstring(L, addrstring, addrsz);return 5;}}return 4;}const char *skynet_socket_udp_address(struct skynet_socket_message *msg, int *addrsz) {if (msg->type != SKYNET_SOCKET_TYPE_UDP) {return NULL;}struct socket_message sm;sm.id = msg->id;sm.opaque = 0;sm.ud = msg->ud;sm.data = msg->buffer;return (const char *)socket_server_udp_address(SOCKET_SERVER, &sm, addrsz);//这里从msg中解出addr}
socket_server_udp_address()函数及其简单,其根据V4、6,确定具体长度和位置。
const struct socket_udp_address *socket_server_udp_address(struct socket_server *ss, struct socket_message *msg, int *addrsz) {uint8_t * address = (uint8_t *)(msg->data + msg->ud);int type = address[0];switch(type) {case PROTOCOL_UDP:*addrsz = 1+2+4;break;case PROTOCOL_UDPv6:*addrsz = 1+2+16;break;default:return NULL;}return (const struct socket_udp_address *)address;}
再回到lua,看看示例代码 testudp.lua 片段:
host = socket.udp(function(str, from) --可以想见,这里的from是一个addr和port连续摆放的不可读数组。print("server recv", str, socket.udp_address(from))socket.sendto(host, from, "OK " .. str)end ,"127.0.0.1",8765)
可以看出:
1、socket.udp_address(from) 将不可读数组转换为可读字符串。
2、socket.sendto(host, from, "xx") 可以直接使用这种addr&port变量发送报文
在 socket.lua中,这两个定义其实是C扩展函数:
socket.sendto = assert(driver.udp_send)socket.udp_address = assert(driver.udp_address)
剩下的就没有什么了,这种序列化组织方式贯穿了c语言部分。
最后看一下lua-socket.c中的地址处理ludp_address():
static intludp_address(lua_State *L) {size_t sz = 0;const uint8_t * addr = (const uint8_t *)luaL_checklstring(L, 1, &sz);uint16_t port = 0;memcpy(&port, addr+1, sizeof(uint16_t));port = ntohs(port);const void * src = addr+3;char tmp[256];int family;if (sz == 1+2+4) {family = AF_INET;} else {if (sz != 1+2+16) {return luaL_error(L, "Invalid udp address");}family = AF_INET6;}if (inet_ntop(family, src, tmp, sizeof(tmp)) == NULL) {return luaL_error(L, "Invalid udp address");}lua_pushstring(L, tmp);lua_pushinteger(L, port);return 2;}
记住lua-socket.c 接收到UDP数据,序列化的摆放方式是:
data(n字节) + 类型(1字节) + port(2字节) + addr(4或者16字节)
lua环境下。收到的来源信息中,结构如下摆放:
类型(1字节) + port(2字节) + addr(4或者16字节)