有两种
①是游戏客互端使用 TCP 连接接入 skynet 节点。如果你用 skynet 实现一个 web 服务器的话,游戏客户端就可以等价于一个浏览器请求。
称为:gate 服务
特征:监听一个 TCP 端口,接受连入的 TCP 连接,并把连接上获得的数据转发到 skynet 内部。
介绍:Gate 可以用来消除外部数据包和 skynet 内部消息包的不一致性。外部 TCP 流的分包问题,是 Gate实现上的约定。Gate 会接受外部连接,并把连接相关信息转发给另一个服务去处理。它自己不做数据处理是因为我们需要保持 gate实现的简洁高效。
外部信息:
①连接本身的接入和断开消息(控制信息包),
②连接上的数据包
gate三种工作模式:
①watchdog 模式 ,由gate 加上包头,同时处理控制信息和数据信息的所有数据;
②agent 模式 ,让每个 agent 处理独立连接;
③broker模式 ,由一个 broker 服务处理不同连接上的所有数据包。
①一开始,Gate无条件转发这两类消息到同一个处理服务。但对于连接数据包,添加一个包头无疑有性能上的开销。
②所以 Gate 还接收另一种工作模式:把每个不同连接上的数据包转发给不同的独立服务上。每个独立服务处理单一连接上的数据包。
③或者,我们也可以选择把不同连接上的数据包从控制信息包(建立/断开连接)中分离开,但不区分不同连接而转发给同一数据处理服务(对数据来源不敏感,只对数据内容敏感的场合)。
无论是哪种模式,控制信息都是交给 watchdog 去处理的,而数据包如果不发给 watchdog 而是发送给 agent 或 broker 的话,则不会有额外的数据头(也减少了数据拷贝)。识别这些包是从外部发送进来的方法是检查消息包的类型是否为 PTYPE_CLIENT。当然,你也可以自己定制消息类型让 gate 通知你。
注意:Gate只负责读取外部数据,但不负责回写。也就是说,向这些连接发送数据不是它的职责范畴。
②是第三方的服务,比如数据库服务,它接受一个或多个 TCP 连接。你需要从 skynet 内部建立一个 TCP 连接出去使用。
网关服务 (GateSever) 是游戏的接入层, 基本功能是管理客户端的连接, 分割完整的数据包, 转发给逻辑处理服务.
skynet 提供了一个通用模板 lualib/snax/gateserver.lua. 同时基于 gateserver.lua, 实现了一个网关服务 gate.lua.
TCP 是面向字节流的协议,我们需要把字节流流切割成数据包, 具体的方式见分包.
skynet网络服务器一般是用 gate / watchdog / agent 三剑客
为什么有了socket还要用这个?这一点还没完全搞明白,留作以后解决。
其中 watchdog.lua在文件夹 skynet/examples 下 gate.lua 在 skynet/service 文件夹下,gateserver.lua 在 skynet/lualib/snax 文件夹下。
我们写网关服务的时候,gate直接用example里面的,因为它已经被标准化了。
watchdog也直接用,不用修改。
只有agent服务需要自己写。
首先一个连接进来,先到gate,gate会给watchdog发一个请求。watchdog就会启动一个agent。agent启动以后会给gate发个请求forward,gate就会给连接加上agent属性。当这个连接再有数据进来的时候,还是经过gate,但是gate检查到这个连接已经有agent属性以后,数据就直接发给agent了,不会再发给watchdog。
skynet 提供了一个通用模板 lualib/snax/gateserver.lua 来启动一个网关服务器,通过 TCP 连接和客户端交换数据。
TCP 基于数据流,但一般我们需要以带长度信息的数据包的结构来做数据交换。gateserver做的就是这个工作,把数据流切割成包的形式转发到可以处理它的地址。
gate服务基础
local gateserver = require "snax.gateserver"
local handler = {} --必须提供一张表,表里面定义connect、message等相关回调函数
-- 注册各种handler函数接口,比如:handler.connect、handler.disconnect、handler.message...
gateserver.start(handler) --网关服务的入口函数
gate服务实例
local skynet = require "skynet"
local gateserver = require "snax.gateserver"
local handler = {}
--当一个客户端链接进来,gateserver自动处理链接,并且调用该函数,必须要有
function handler.connect(fd, ipaddr)
skynet.error("ipaddr:",ipaddr,"fd:",fd,"connect")
gateserver.openclient(fd) --链接成功不代表马上可以读到数据,需要打开这个套接字,允许fd接收数据
end
--当一个客户端断开链接后调用该函数,必须要有
function handler.disconnect(fd)
skynet.error("fd:", fd, "disconnect")
end
--当fd有数据到达了,会调用这个函数,前提是fd需要调用gateserver.openclient打开
function handler.message(fd, msg, sz)
skynet.error("recv message from fd:", fd)
end
gateserver.start(handler)
启动gate服务 并 需要告诉它监听端口
local gateserver = skynet.newservice("mygateserver")
skynet.call(gateserver, "lua", "open", { --需要给网关服务发送open消息,来启动监听
port = 8002, --监听的端口
maxclient = 64, --客户端最大连接数
nodelay = true, --是否延迟TCP
})
关闭gate服务
local skynet = require "skynet"
local gateserver = ...
skynet.start(function()
skynet.call(gateserver, "lua", "close")
skynet.exit()
end)
客户端tcp连接gate监听端口
注意两点:
①snax.gateserver 基于TCP协议包装了一个两字节数据长度协议
前两个字节表示数据包的长度len(不计算这两个表示长度的字节),高字节在前低字节在后(大端序),后面紧跟len字节数的数据。
也就说,你给gate服务发消息,消息格式必须符合该协议,不然不会识别该消息!
消息打包方法:
local netpack = require "skynet.netpack" --使用netpack
--(发送端)打包数据str,返回一个C指针msg,sz,申请内存
netpack.pack(str)
--(接收端)解包数据,返回一个lua的字符串,会释放内存
netpack.tostring(msg, sz)
客户端实例
local skynet = require "skynet"
local socket = require "skynet.socket"
local netpack = require "skynet.netpack"
skynet.start(function()
local addr = "127.0.0.1:8002"
skynet.error("connect ".. addr)
local id = socket.open(addr)
assert(id)
socket.write(id, netpack.pack("Hello world"))
end)
详细代码参考
--当一个新客户端被accept后,connect 方法会被回调。 fd 是socket句柄 (不是系统fd). ipaddr是客户端地址, 例如 "127.0.0.1:8000".
function handler.connect(fd, ipaddr)
--当一个客户端断开链接后调用该函数,必须要有
function handler.disconnect(fd)
--当fd有数据到达了,会调用这个函数,前提是fd需要调用gateserver.openclient打开
function handler.message(fd, msg, sz)
--如果你希望在监听端口打开的时候,做一些初始化操作,可以提供 open 这个方法。
--source 是请求来源地址,conf 是开启 gate 服务的参数表(端口,连接数,是否延迟)。
function handler.open(source, conf)
--当一个连接异常(通常意味着断开),error 被调用,除了 fd ,还会拿到错误信息 msg(通常用于 log 输出)。
function handler.error(fd, msg)
--当 fd 上待发送的数据累积超过 1M 字节后,将回调这个方法。你也可以忽略这个消息。
function handler.warning(fd, size)
--gateserver除了能接收socket消息以为,当然也是可以接受skynet的lua消息,并且gateserver还对lua消息注册函数进行了封装,只需提供handler.command回调函数就能处理lua消息,不需要我们自己调用skynet.dispatch来注册。
function CMD.kick(source, fd)
function handler.command(cmd, source, ...)
local f = assert(CMD[cmd])
return f(source, ...)
end
具体的实现参考
其中 watchdog.lua 在文件夹 skynet/examples 下 gate.lua 在
skynet/service文件夹下,gateserver.lua 在 skynet/lualib/snax 文件夹下。
gate服务写法
--注册client消息专门用来将接收到的网络数据转发给agent,不需要解包,也不需要打包
skynet.register_protocol {
name = "client",
id = skynet.PTYPE_CLIENT,
}
function handler.connect(fd, ipaddr)
gateserver.openclient(fd)
local agent = skynet.newservice("myagent", fd) --连接成功就启动一个agent来代理
agents[fd] = agent
end
function handler.disconnect(fd) --断开连接后,agent服务退出
local agent = agents[fd]
if(agent) then
--通过发送消息的方式来退出不要使用skynet.kill(agent)
skynet.send(agent, "lua", "quit")
agents[fd] = nil
end
end
function handler.message(fd, msg, sz)
local agent = agents[fd]
skynet.redirect(agent, 0, "client", 0, msg, sz) --收到消息就转发给agent
end
agent写法
skynet.start(function()
--注册client消息专门用来接收网络数据
skynet.dispatch("client", function(_,_, msg)
func1(msg)
end)
skynet.dispatch("lua", function(_,_, cmd) --注册lua消息,来退出服务
if cmd == "quit" then
skynet.error(fd,"agent quit")
skynet.exit()
end
end)
end)
个人看法:skynet网关服务这一块主要是为了解决网络通信的问题,处理网络请求,目前了解的还不够深,如果想更好的理解,应该仔细研究
注: 这个模板不可以和 Socket 库一起使用。因为这个模板接管了 socket 类的消息。
watchdog.lua 在文件夹 skynet/examples 下 |
gate.lua 在 skynet/service 文件夹下 |
gateserver.lua 在 skynet/lualib/snax 文件夹下 |
本篇博客参考:
https://blog.csdn.net/hp_cpp/article/details/107364207
https://blog.csdn.net/qq769651718/article/details/79435075
https://github.com/cloudwu/skynet/wiki/GateServer