[关闭]
@wsd1 2017-01-19T08:00:55.000000Z 字数 5270 阅读 1169

【专题6】skynet的socketAPI中client的网络地址

skynet


自用笔记,仅供参考

参考文档

http://cloudwu.github.io/lua53doc/manual.html#6.4.1

背景

本篇分析UDP系列API中的客户端地址。

IP地址从lua传达到C

socket.udp()

  1. function socket.udp(callback, host, port)
  2. local id = driver.udp(host, port) -- socketdriver节点建立udp,见下面的ludp
  3. create_udp_object(id, callback) --本地记录一下idcallback的关系
  4. return id
  5. end

lua-socket.c

  1. static int
  2. ludp(lua_State *L) {
  3. ...
  4. const char * addr = lua_tolstring(L,1,&sz); //首先取出 地址字串“127.0.0.1”
  5. char tmp[sz];
  6. int port = 0;
  7. const char * host = NULL;
  8. if (addr) {
  9. host = address_port(L, tmp, addr, 2, &port); //从栈中或者从addr中取port,下面说明该函数
  10. }
  11. int id = skynet_socket_udp(ctx, host, port);
  12. ...
  13. lua_pushinteger(L, id);
  14. return 1;
  15. }
address_port()函数不用分析下去了,这个函数功能很直接,就是尝试从lua栈中取port。
之所以独立成函数,是因为有 "127.0.0.1:8080"这样将端口写在addr中的写法。
这里面也会调整addr中的内容。

skynet_socket_udp()函数内部就 开始调用bind和socket这类的系统API,addr是字符串,port是数值,这里分析结束。

IP地址从C传递到lua

回到:
socket.lua

  1. function socket.udp(callback, host, port)
  2. local id = driver.udp(host, port) -- 调用socketdriver的接口
  3. create_udp_object(id, callback)
  4. return id
  5. end

其中callback的接口如下:

function(str, from)

它被函数create_udp_object安装到一张表中:

socket.lua:

  1. local function create_udp_object(id, cb)
  2. ...
  3. socket_pool[id] = { --这是一个本地表 专用于记录fdcb见的关系
  4. id = id,
  5. connected = true,
  6. protocol = "UDP",
  7. callback = cb,
  8. }
  9. end

通过表socket_pool[],我们找到 socket.lua安装的 “socket”协议的处理函数:

socket.lua:

  1. -- SKYNET_SOCKET_TYPE_UDP = 6
  2. socket_message[6] = function(id, size, data, address)
  3. local s = socket_pool[id]
  4. ...
  5. local str = skynet.tostring(data, size)
  6. ...
  7. s.callback(str, address)
  8. end

可以看出,我们需要去 找找发送该消息的代码。下面的函数真正实现了将UDP数据到来事件中的数据和来源地址打包的过程。来源于 skynet_server.c

skynet_server.c:

  1. static int
  2. forward_message_udp(struct socket_server *ss, struct socket *s, struct socket_message * result) {
  3. union sockaddr_all sa;
  4. socklen_t slen = sizeof(sa);
  5. int n = recvfrom(s->fd, ss->udpbuffer,MAX_UDP_PACKAGE,0,&sa.s,&slen);
  6. ...
  7. uint8_t * data; //就是它。根据V4 V6来输出不同的长度,将其放置在数据块尾部
  8. if (slen == sizeof(sa.v4)) {
  9. ...
  10. data = MALLOC(n + 1 + 2 + 4);
  11. gen_udp_address(PROTOCOL_UDP, &sa, data + n);
  12. } else {
  13. ...
  14. data = MALLOC(n + 1 + 2 + 16);
  15. gen_udp_address(PROTOCOL_UDPv6, &sa, data + n);
  16. }
  17. memcpy(data, ss->udpbuffer, n); //<--- 这里就是将数据摆放到开头咯
  18. result->opaque = s->opaque;
  19. result->id = s->id;
  20. result->ud = n; //<---- 注意这里的长度 只是数据报文的长度 不包含地址信息的
  21. result->data = (char *)data;
  22. return SOCKET_UDP;
  23. }

gen_udp_address()函数更加清晰。

  1. static int
  2. gen_udp_address(int protocol, union sockaddr_all *sa, uint8_t * udp_address) {
  3. int addrsz = 1;
  4. udp_address[0] = (uint8_t)protocol;
  5. if (protocol == PROTOCOL_UDP) {
  6. memcpy(udp_address+addrsz, &sa->v4.sin_port, sizeof(sa->v4.sin_port));
  7. addrsz += sizeof(sa->v4.sin_port);
  8. memcpy(udp_address+addrsz, &sa->v4.sin_addr, sizeof(sa->v4.sin_addr));
  9. addrsz += sizeof(sa->v4.sin_addr);
  10. } else {
  11. memcpy(udp_address+addrsz, &sa->v6.sin6_port, sizeof(sa->v6.sin6_port));
  12. addrsz += sizeof(sa->v6.sin6_port);
  13. memcpy(udp_address+addrsz, &sa->v6.sin6_addr, sizeof(sa->v6.sin6_addr));
  14. addrsz += sizeof(sa->v6.sin6_addr);
  15. }
  16. return addrsz;
  17. }

很清楚了,msg中依次摆放了 data、1字节类型,2字节port和 4或者16字节的addr。

然后到 skyne_socket.c中发送出来。

  1. skynet_socket_poll() {
  2. ...
  3. int type = socket_server_poll(ss, &result, &more); //这里拿到了所需信息
  4. switch (type) {
  5. ...
  6. case SOCKET_UDP:
  7. forward_message(SKYNET_SOCKET_TYPE_UDP, false, &result); //这里发出来
  8. break;
  9. ...
  10. }
  11. ...
  12. }

forward_message()就不跟进去了,确定一点是这里发送的仅仅是msg和sz。

再回到socket.lua中:

  1. skynet.register_protocol {
  2. name = "socket",
  3. id = skynet.PTYPE_SOCKET, -- PTYPE_SOCKET = 6
  4. unpack = driver.unpack, --这里将msg&size 转成 addr
  5. dispatch = function (_, _, t, ...)
  6. socket_message[t](...)
  7. end
  8. }

终于就要看到关键了,找到 lua-socket.c中的解包函数:
lua-socket.c:

  1. static int
  2. lunpack(lua_State *L) {
  3. struct skynet_socket_message *message = lua_touserdata(L,1);
  4. ...
  5. //上面各种push,可见就是为dispatch函数准备参数。
  6. if (message->type == SKYNET_SOCKET_TYPE_UDP) {
  7. int addrsz = 0;
  8. const char * addrstring = skynet_socket_udp_address(message, &addrsz);
  9. //这里是我们需要的addr
  10. if (addrstring) {
  11. lua_pushlstring(L, addrstring, addrsz);
  12. return 5;
  13. }
  14. }
  15. return 4;
  16. }
  17. const char *
  18. skynet_socket_udp_address(struct skynet_socket_message *msg, int *addrsz) {
  19. if (msg->type != SKYNET_SOCKET_TYPE_UDP) {
  20. return NULL;
  21. }
  22. struct socket_message sm;
  23. sm.id = msg->id;
  24. sm.opaque = 0;
  25. sm.ud = msg->ud;
  26. sm.data = msg->buffer;
  27. return (const char *)socket_server_udp_address(SOCKET_SERVER, &sm, addrsz);
  28. //这里从msg中解出addr
  29. }

socket_server_udp_address()函数及其简单,其根据V4、6,确定具体长度和位置。

  1. const struct socket_udp_address *
  2. socket_server_udp_address(struct socket_server *ss, struct socket_message *msg, int *addrsz) {
  3. uint8_t * address = (uint8_t *)(msg->data + msg->ud);
  4. int type = address[0];
  5. switch(type) {
  6. case PROTOCOL_UDP:
  7. *addrsz = 1+2+4;
  8. break;
  9. case PROTOCOL_UDPv6:
  10. *addrsz = 1+2+16;
  11. break;
  12. default:
  13. return NULL;
  14. }
  15. return (const struct socket_udp_address *)address;
  16. }

再回到lua,看看示例代码 testudp.lua 片段:

  1. host = socket.udp(
  2. function(str, from) --可以想见,这里的from是一个addrport连续摆放的不可读数组。
  3. print("server recv", str, socket.udp_address(from))
  4. socket.sendto(host, from, "OK " .. str)
  5. end ,
  6. "127.0.0.1",
  7. 8765)

可以看出:
1、socket.udp_address(from) 将不可读数组转换为可读字符串。
2、socket.sendto(host, from, "xx") 可以直接使用这种addr&port变量发送报文

在 socket.lua中,这两个定义其实是C扩展函数:

  1. socket.sendto = assert(driver.udp_send)
  2. socket.udp_address = assert(driver.udp_address)

剩下的就没有什么了,这种序列化组织方式贯穿了c语言部分。

最后看一下lua-socket.c中的地址处理ludp_address():

  1. static int
  2. ludp_address(lua_State *L) {
  3. size_t sz = 0;
  4. const uint8_t * addr = (const uint8_t *)luaL_checklstring(L, 1, &sz);
  5. uint16_t port = 0;
  6. memcpy(&port, addr+1, sizeof(uint16_t));
  7. port = ntohs(port);
  8. const void * src = addr+3;
  9. char tmp[256];
  10. int family;
  11. if (sz == 1+2+4) {
  12. family = AF_INET;
  13. } else {
  14. if (sz != 1+2+16) {
  15. return luaL_error(L, "Invalid udp address");
  16. }
  17. family = AF_INET6;
  18. }
  19. if (inet_ntop(family, src, tmp, sizeof(tmp)) == NULL) {
  20. return luaL_error(L, "Invalid udp address");
  21. }
  22. lua_pushstring(L, tmp);
  23. lua_pushinteger(L, port);
  24. return 2;
  25. }

总结

记住lua-socket.c 接收到UDP数据,序列化的摆放方式是:

data(n字节) + 类型(1字节) + port(2字节) + addr(4或者16字节)

lua环境下。收到的来源信息中,结构如下摆放:

类型(1字节) + port(2字节) + addr(4或者16字节)
添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注