继上一篇介绍了skynet的网络部分之后,这一篇以网关gate.lua为例,简单分析下其串接和处理流程。

在官方给出的范例中,是以examples/main.lua作为启动脚本的,在此过程中会创建watchdog服务:

     local watchdog = skynet.newservice("watchdog")
skynet.call(watchdog, "lua", "start", {
port = ,
maxclient = max_client,
nodelay = true,
})

首先加载watchdog.lua脚本。而在watchdog.lua的加载过程中,创建了gate服务。加载gate.lua过程中,调用gateserver.start(gate),gateserver会向skynet注册socket协议的处理:

     skynet.register_protocol {
name = "socket",
id = skynet.PTYPE_SOCKET, -- PTYPE_SOCKET = 6
unpack = function ( msg, sz )
return netpack.filter( queue, msg, sz)
end,
dispatch = function (_, _, q, type, ...)
queue = q
if type then
MSG[type](...)
end
end
}

另外gateserver也会注册lua协议的处理,这里就不展开了。gateserver中会拦截skynet_socket_message;也会拦截部分lua消息(一般是由watchdog转发而来),并调用gate注册进来的回调。注意gateserver才是skynet消息的入口,gate只不过是个回调而已。至此,gate服务加载完毕。

watchdog服务加载完毕后,main.lua中接着调用watchdog的start方法,其参数分别指定了侦听的端口、最大客户端连接数、是否延迟等。看下watchdog的start方法:

 function CMD.start(conf)
skynet.call(gate, "lua", "open" , conf)
end

其紧接着调用gate的open方法,而这个方法在gateserver中被拦截了:

     function CMD.open( source, conf )
assert(not socket)
local address = conf.address or "0.0.0.0"
local port = assert(conf.port)
maxclient = conf.maxclient or
nodelay = conf.nodelay
skynet.error(string.format("Listen on %s:%d", address, port))
socket = socketdriver.listen(address, port)
socketdriver.start(socket)
if handler.open then
return handler.open(source, conf)
end
end

可以看到,在open方法中创建了socket并开始了侦听过程。回忆上篇,socket操作的LuaAPI作为socketdriver被实现在lua-socket.c文件中,看一眼这里的listen是如何交互的:

 static int
llisten(lua_State *L) {
const char * host = luaL_checkstring(L,);
int port = luaL_checkinteger(L,);
int backlog = luaL_optinteger(L,,BACKLOG);
struct skynet_context * ctx = lua_touserdata(L, lua_upvalueindex());
int id = skynet_socket_listen(ctx, host,port,backlog);
if (id < ) {
return luaL_error(L, "Listen error");
} lua_pushinteger(L,id);
return ;
}

析取参数,获取关联的skynet-context之后,调用skynet_socket.c的skynet_socket_listen:

 int
skynet_socket_listen(struct skynet_context *ctx, const char *host, int port, int backlog) {
uint32_t source = skynet_context_handle(ctx);
return socket_server_listen(SOCKET_SERVER, source, host, port, backlog);
}

拿到context-handle,这个handle在后续创建socket时会被关联起来。handle作为参数opaque传递入socket_server.c中的socket_server_listen方法中:

 int
socket_server_listen(struct socket_server *ss, uintptr_t opaque, const char * addr, int port, int backlog) {
int fd = do_listen(addr, port, backlog);
if (fd < ) {
return -;
}
struct request_package request;
int id = reserve_id(ss);
if (id < ) {
close(fd);
return id;
}
request.u.listen.opaque = opaque;
request.u.listen.id = id;
request.u.listen.fd = fd;
send_request(ss, &request, 'L', sizeof(request.u.listen));
return id;
}

在作了bind和listen之后,将此socket描述符打包为request写入socket_server的读管道,由socket_server_poll轮循处理。至此,gate服务中listen的流程已经非常清晰了。

再回到gateserver.CMD.open方法中,在socketdriver.listen之后,紧接着调用socketdriver.start开始网络事件的处理(具体细节请按上述流程参照源码),最后调用gate.lua的回调handler.open。在这个过程中,gate服务的skynet-context-handle已经与对应的socket绑定了,后续关于此socket的SOCKET消息都会转发到gate服务中来,具体到代码则是由gateserver接收过滤后,再做进一步的分发处理。

那么一个新的客户端连接是如何被接收创建的呢?

在socket_server_poll过程中,会检查是否有网络事件产生(以下是简化的代码):

 int socket_server_poll(struct socket_server *ss, struct socket_message * result, int * more) {
for (;;) {
// 管道select
...... // socket event check
...... // s: socket
switch (s->type) {
case SOCKET_TYPE_CONNECTING:
return report_connect(ss, s, result);
case SOCKET_TYPE_LISTEN: {
int ok = report_accept(ss, s, result);
if (ok > ) { return SOCKET_ACCEPT; }
17 if (ok < ) { return SOCKET_ERROR; }
// when ok == 0, retry
break;
}
23 // other
24 ......25 }
26 }

对于正在listen的socket(SOCKET_TYPE_LISTEN),当发生事件(即侦测到有新的连接)时,会在report_accept中accept得连接描述符fd并创建socket结构,然后返回SOCKET_ACCEPT交由上层的skynet_socket.c:skynet_socket_poll处理,后者会封装类型为SKYNET_SOCKET_TYPE_ACCEPT的skynet-message并推入到gate服务的队列中去,最终转发到gateserver中所注册的SOCKET协议入口。

回到gateserver中来(见上述gateserver摘录的代码),在接收到网络消息时,先是在unpack中通过netpack(见lua-netpack.c)合并过滤消息,比如TCP消息粘包等(注意skynet_socket_message如果其padding为true,则表示非数据的命令,比如SKYNET_SOCKET_TYPE_ACCEPT)。对于SKYNET_SOCKET_TYPE_ACCEPT命令,netpack会解析并转换为open命令,最后gateserver会调用到MSG.open:

     function MSG.open(fd, msg)
if client_number >= maxclient then
socketdriver.close(fd)
return
end
if nodelay then
socketdriver.nodelay(fd)
end
connection[fd] = true
client_number = client_number +
handler.connect(fd, msg)
end

回调到gate.lua中的connect:

 function handler.connect(fd, addr)
local c = {
fd = fd,
ip = addr,
}
connection[fd] = c
skynet.send(watchdog, "lua", "socket", "open", fd, addr)
end

watchdog.lua中的SOCKET.open:

 function SOCKET.open(fd, addr)
skynet.error("New client from : " .. addr)
agent[fd] = skynet.newservice("agent")
skynet.call(agent[fd], "lua", "start", { gate = gate, client = fd, watchdog = skynet.self() })
end

此时,会创建玩家agent服务,并调用start方法:

 function CMD.start(conf)
local fd = conf.client
local gate = conf.gate
WATCHDOG = conf.watchdog
-- slot 1,2 set at main.lua
host = sprotoloader.load():host "package"
send_request = host:attach(sprotoloader.load())
skynet.fork(function()
while true do
send_package(send_request "heartbeat")
skynet.sleep()
end
end) client_fd = fd
skynet.call(gate, "lua", "forward", fd)
end

agent拿到gate服务的标识后,调用forward将自己的标识注册到gate服务中来:

 function CMD.forward(source, fd, client, address)
local c = assert(connection[fd])
unforward(c)
c.client = client or
c.agent = address or source
forwarding[c.agent] = c
gateserver.openclient(fd)
end

gateserver.openclient开始侦听此socket的事件:

function gateserver.openclient(fd)
if connection[fd] then
socketdriver.start(fd)
end
end

至此,一个新连接的建立流程就结束了。那么连接建立后网络数据又是如何转发进来的呢?流程依然是一致的,socket_server_poll侦测到READ后读取数据并转发到gateserver中来,后者调用netpack对数据粘包,此时会调用MSG.more或MSG.data将数据转交给gate.message:

 function handler.message(fd, msg, sz)
-- recv a package, forward it
local c = connection[fd]
local agent = c.agent
if agent then
skynet.redirect(agent, c.client, "client", , msg, sz)
else
skynet.send(watchdog, "lua", "socket", "data", fd, netpack.tostring(msg, sz))
end
end

直接将数据转发给之前通过forward注册进来的agent,采用的协议是"client"。agent中需注册此协议的处理,拿到字节流并根据上层的业务协议对数据转码,做进一步的处理。这样,数据接收和转发的流程就结束了。其它方面,比如数据发送,关闭socket等等,流程上都是一致的,具体细节不再详述。

skynet源码阅读<3>--网关分析的更多相关文章

  1. skynet源码阅读<1>--lua与c的基本交互

    阅读skynet的lua-c交互部分代码时,可以看到如下处理: struct skynet_context * context = lua_touserdata(L, lua_upvalueindex ...

  2. skynet源码阅读<5>--协程调度模型

    注:为方便理解,本文贴出的代码部分经过了缩减或展开,与实际skynet代码可能会有所出入.    作为一个skynet actor,在启动脚本被加载的过程中,总是要调用skynet.start和sky ...

  3. skynet源码阅读<6>--线程调度

    相比于上节我们提到的协程调度,skynet的线程调度从逻辑流程上来看要简单很多.下面我们就来具体做一分析.首先自然是以skynet_start.c为入口: static void start(int ...

  4. skynet源码阅读<4>--定时器实现

    昨天和三石公聊天,他提到timer的实现原理,我当时迟疑了一下,心想timer不是系统底层时钟中断驱动上层进程/线程,累积计时实现的么?他简述了timer的实现,什么堆排序,优先级队列等,与我想象的不 ...

  5. skynet 源码阅读笔记 bootstrap.lua

    最近几周粗略看了 skynet 代码的 C 部分.遇到很多知识点以前只是知道,但并不十分了解,所以这是一个学习的过程. 从 main 函数开始,闷头一阵看下来,着实蛋疼. 当看了 skynet_mq. ...

  6. skynet源码阅读<7>--死循环检测

    在使用skynet开发时,你也许会碰到类似这样的警告:A message from [ :0100000f ] to [ :0100000a ] maybe in an endless loop (v ...

  7. skynet源码阅读<2>--网络部分

    先来看下socket_server的数据结构,这里简称为ss: struct socket_server { int recvctrl_fd; int sendctrl_fd; int checkct ...

  8. HTTP请求库——axios源码阅读与分析

    概述 在前端开发过程中,我们经常会遇到需要发送异步请求的情况.而使用一个功能齐全,接口完善的HTTP请求库,能够在很大程度上减少我们的开发成本,提高我们的开发效率. axios是一个在近些年来非常火的 ...

  9. JDK源码阅读(一):Object源码分析

    最近经过某大佬的建议准备阅读一下JDK的源码来提升一下自己 所以开始写JDK源码分析的文章 阅读JDK版本为1.8 目录 Object结构图 构造器 equals 方法 getClass 方法 has ...

随机推荐

  1. hdu1251 字典树trie 模板题

    //字典树模板题.题意:给一个库,每次查询,求以之为前缀的单词数量. #include<iostream> #include<string> #include<vecto ...

  2. GitHub中watch、star、fork的作用

    star 的作用是收藏,目的是方便以后查找. watch 的作用是关注,目的是等作者更新的时候,你可以收到通知. fork 的作用是参与,目的是你增加新的内容,然后 Pull Request,把你的修 ...

  3. windows下模拟linux命令的工具 xshell

    windows下模拟linux命令的工具 xshell

  4. shell高级-----初识sed和gawk

    sed编辑器 sed说明 sed是Linux下一款功能强大的非交互流式文本编辑器,可以对文本文件进行增.删.改.查等操作,支持按行.按字段.按正则匹配文本内容,灵活方便,特别适合于大文件的编辑. 替换 ...

  5. 【Java TCP/IP Socket】深入剖析socket——数据传输的底层实现

    底层数据结构 如果不理解套接字的具体实现所关联的数据结构和底层协议的工作细节,就很难抓住网络编程的精妙之处,对于TCP套接字来说,更是如此.套接字所关联的底层的数据结构集包含了特定Socket实例所关 ...

  6. Spring基于构造函数的依赖注入(DI)

    以下内容引用自http://wiki.jikexueyuan.com/project/spring/dependency-injection/spring-constructor-based-depe ...

  7. (CF)Codeforces445A DZY Loves Chessboard(纯实现题)

    转载请注明出处:http://blog.csdn.net/u012860063? viewmode=contents 题目链接:http://codeforces.com/problemset/pro ...

  8. Redhat7/centOs7 安装配置python3.6.5

    centos默认安装python2,设置py2和py3并存的方法如下: sudo yum install openssl-devel -y sudo yum install zlib-devel -y ...

  9. js正則表達式:验证邮箱格式、password复杂度、手机号码、QQ号码

    $(function () { $("input[name='sub']").on("click", function () { if (!isEmail($( ...

  10. CSS样式布局入门介绍,非常详尽

    转载自:http://wenboxz.com/archives/try-css-layout.html/