@wsd1
2017-01-19T08:00:55.000000Z
字数 5270
阅读 1608
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,见下面的ludp
create_udp_object(id, callback) --本地记录一下id和callback的关系
return id
end
lua-socket.c
static int
ludp(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 id
end
其中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 = 6
socket_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 int
forward_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 int
gen_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 = 6
unpack = driver.unpack, --这里将msg&size 转成 addr
dispatch = function (_, _, t, ...)
socket_message[t](...)
end
}
终于就要看到关键了,找到 lua-socket.c中的解包函数:
lua-socket.c:
static int
lunpack(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);
//这里是我们需要的addr
if (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 int
ludp_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字节)