【Skynet】Sproto初学与应用
来源:互联网 发布:沙发品牌 知乎 编辑:程序博客网 时间:2024/05/21 11:34
参考自:Skynet基础入门例子详解(5)(魔王魂影)
Sproto是云风专门为Skynet开发的轻量协议。
借鉴官方例子,我们可以在 ./examples/proto.lua 中添加自己的一个 “say” 协议
local sprotoparser = require "sprotoparser"local proto = {}proto.c2s = sprotoparser.parse [[.package {type 0 : integersession 1 : integer}handshake 1 {response {msg 0 : string}}get 2 {request {what 0 : string}response {result 0 : string}}set 3 {request {what 0 : stringvalue 1 : string}}quit 4 {}say 5 {request {name 0 : stringmsg 1 : string}}]]proto.s2c = sprotoparser.parse [[.package {type 0 : integersession 1 : integer}heartbeat 1 {}]]return proto
然后编写自己的客户端 ./examples/client2.lua
package.cpath = "luaclib/?.so"package.path = "lualib/?.lua;examples/?.lua"if _VERSION ~= "Lua 5.3" then error "Use lua 5.3"endlocal socket = require "clientsocket"-- 通信协议local proto = require "proto"local sproto = require "sproto"local host = sproto.new(proto.s2c):host "package"local request = host:attach(sproto.new(proto.c2s))local fd = assert(socket.connect("127.0.0.1", 8888))local session = 0local function send_request(name, args) session = session + 1 local str = request(name, args, session) -- 解包测试--[=[ local host2 = sproto.new(proto.c2s):host "package" local req_type, name, arg, func = host2:dispatch(str) print(req_type)print(name)if type(arg) == "table" thenprint(arg.name)print(arg.msg)endprint(func)--]=] socket.send(fd, str) print("Request:", session)endsend_request("handshake")--此处如果多次发送数据,可能造成收端粘包,致使数据无法正常解析while true do -- 接收服务器返回消息 local str = socket.recv(fd) -- print(str) if str~=nil and str~="" then print("server says: "..str) -- socket.close(fd) -- break; end -- 读取用户输入消息 local readstr = socket.readstdin() if readstr then if readstr == "quit" then send_request("quit") socket.close(fd) break else -- 把用户输入消息发送给服务器 send_request("say", { name = "mick", msg = readstr }) end else socket.usleep(100) endend
我们知道
local host = sproto.new(proto.s2c):host "package"local request = host:attach(sproto.new(proto.c2s))host 和request 分别是一对打包和解包协议,可以在本地检测。发现解包函数返回了四个参数:请求类型,请求名,请求参数,回应函数。其中,请求名和请求参数正好与打包函数里的入参 name 及 args 对应。
最后是服务器 ./examples/socket2.lua
local skynet = require "skynet"local socket = require "socket"local proto = require "proto"local sproto = require "sproto"local hostlocal REQUEST = {}function REQUEST:say() print("say", self.name, self.msg)endfunction REQUEST:handshake() print("handshake")endfunction REQUEST:quit() print("quit")endlocal function request(name, args, response) local f = assert(REQUEST[name]) local r = f(args)do return name end if response then -- 生成回应包(response是一个用于生成回应包的函数。) -- 处理session对应问题 -- return response(r) endendlocal function send_package(fd,pack) -- 协议与客户端对应(两字节长度包头+内容) local package = string.pack(">s2", pack) socket.write(fd, package)endlocal function accept(id) -- 每当 accept 函数获得一个新的 socket id 后,并不会立即收到这个 socket 上的数据。这是因为,我们有时会 -- 希望把这个 socket 的操作权转让给别的服务去处理。 -- 任何一个服务只有在调用 socket.start(id) 之后,才可以收到这个 socket 上的数据。 socket.start(id) host = sproto.new(proto.c2s):host "package" -- request = host:attach(sproto.new(proto.c2s)) while true do local str = socket.read(id) if str then local type,str2,str3,str4 = host:dispatch(str) if type=="REQUEST" then -- REQUEST : 第一个返回值为 "REQUEST" 时,表示这是一个远程请求。如果请求包中没有 session -- 字段,表示该请求不需要回应。这时,第 2 和第 3 个返回值分别为消息类型名(即在 sproto 定义 -- 中提到的某个以 . 开头的类型名),以及消息内容(通常是一个 table );如果请求包中有 -- session 字段,那么还会有第 4 个返回值:一个用于生成回应包的函数。 local ok, result = pcall(request, str2,str3,str4)print("ok result", ok, result) if ok then if result then socket.write(id, "收到了" .. result) -- 暂时不使用回应包回应 -- print("response:"..result) -- send_package(id,result) end else skynet.error(result) end endif str2 == "quit" thensocket.close(id)returnend if type=="RESPONSE" then -- RESPONSE :第一个返回值为 "RESPONSE" 时,第 2 和 第 3 个返回值分别为 session 和 -- 消息内容。消息内容通常是一个 table ,但也可能不存在内容(仅仅是一个回应确认)。 -- 暂时不处理客户端的回应 print("client response") end else socket.close(id) return end endendskynet.start(function() print("==========Socket Start=========") local id = socket.listen("127.0.0.1", 8888) print("Listen socket :", "127.0.0.1", 8888) socket.start(id , function(id, addr) -- 接收到客户端连接或发送消息() print("connect from " .. addr .. " " .. id) -- 处理接收到的消息 accept(id) end)end)
最后别忘了在 ./examples/main.lua 中起一个 socket2.lua 服务。
这个例子让我清晰了不少:
1、打包与解包;
2、服务器中,pcall() 函数有两个返回值,第一个返回值应该在程序正常运行完成后返回 true,后面的返回值来自 pcall 调用的函数的返回值。
3、鉴于TCP的流服务,我们在客户端代码中,多次数据的发送过程代码尽量用 readstdin 分离开,否则所有数据可能一同到达服务器,而服务器并没有针对TCP数据流的粘包和拼包处理。这里可以参见 ./examples/client.lua 中的处理。
于是有了下面改进过的服务器代码:
local skynet = require "skynet"local socket = require "socket"local proto = require "proto"local sproto = require "sproto"local hostlocal last = ""local REQUEST = {}function REQUEST:say() print("say", self.name, self.msg)endfunction REQUEST:handshake() print("handshake")endfunction REQUEST:quit() print("quit")endlocal function request(name, args, response) local f = assert(REQUEST[name]) local r = f(args)do return name end if response then -- 生成回应包(response是一个用于生成回应包的函数。) -- 处理session对应问题 -- return response(r) endendlocal function unpack_package(text)local size = #textif size < 2 thenprint("size: ", size)return nil, textendlocal s = text:byte(1) * 256 + text:byte(2)if size < s+2 thenreturn nil, textendreturn text:sub(3,2+s), text:sub(3+s)endlocal function recv_package(last, fd)local resultresult, last = unpack_package(last)if result thenreturn result, lastendlocal r = socket.read(fd)if r thenreturn nil, last .. relsereturn nil, nilendendlocal function accept(id) socket.start(id)last = "" host = sproto.new(proto.c2s):host "package" -- request = host:attach(sproto.new(proto.c2s)) while true dolocal strstr, last = recv_package(last, id)if str thenlocal type,str2,str3,str4 = host:dispatch(str)if type=="REQUEST" thenlocal ok, result = pcall(request, str2,str3,str4)print("ok result", ok, result)if ok thenif result thensocket.write(id, "收到了" .. result .. '\n')-- 暂时不使用回应包回应-- print("response:"..result)-- send_package(id,result)endelseskynet.error(result)endendif str2 == "quit" thenprint("quit!")socket.close(id)returnendif type=="RESPONSE" then-- 暂时不处理客户端的回应print("client response")end elseif not last thenprint("disconnected!")socket.close(id)returnend endendskynet.start(function() print("==========Socket Start=========") local id = socket.listen("127.0.0.1", 8888) print("Listen socket :", "127.0.0.1", 8888) socket.start(id , function(id, addr) -- 接收到客户端连接或发送消息() print("connect from " .. addr .. " " .. id) -- 处理接收到的消息 accept(id) end)end)
此外,客户端也得修改,需要添加打包代码:
package.cpath = "luaclib/?.so"package.path = "lualib/?.lua;examples/?.lua"if _VERSION ~= "Lua 5.3" then error "Use lua 5.3"endlocal socket = require "clientsocket"-- 通信协议local proto = require "proto"local sproto = require "sproto"local host = sproto.new(proto.s2c):host "package"local request = host:attach(sproto.new(proto.c2s))local fd = assert(socket.connect("127.0.0.1", 8888))local function send_package(fd,pack) -- 协议与客户端对应(两字节长度包头+内容) local package = string.pack(">s2", pack) socket.send(fd, package)endlocal session = 0local function send_request(name, args) session = session + 1 local str = request(name, args, session) send_package(fd, str) print("Request:", session)endsend_request("handshake")send_request("say", { name = "mick", msg = "hello world" })while true do -- 接收服务器返回消息 local str = socket.recv(fd) -- print(str) if str~=nil and str~="" then print("server says: "..str) -- socket.close(fd) -- break; end -- 读取用户输入消息 local readstr = socket.readstdin() if readstr then if readstr == "quit" then send_request("quit") socket.close(fd) break else -- 把用户输入消息发送给服务器 send_request("say", { name = "mick", msg = readstr }) end else socket.usleep(100) endend
这样就为client ----> server 的传输过程添加了打包解包过程,client端在起始处就可以多次发送 send_request() 而不用担心粘包问题。上面的版本对于 server ---> client 的传输过程并未添加打包解包过程,我们可以进一步仿照上面的版本添加,同时还可以尝试服务器在收到 request 进行回应,由于目前我还不清楚 response() 函数的具体工作内容,但该函数内部进行了打包处理,我们可以尝试用一下。当然,如果是服务端单独发送消息给客户端(如心跳包),这样就需要和客户端一样调用sproto进行封包。(具体可参考examples/agent)
要添加回应,还需修改 sproto,修改如下:
say 5 {request {name 0 : stringmsg 1 : string} response {name 0 : stringmsg 1 : string}}
local skynet = require "skynet"local socket = require "socket"local proto = require "proto"local sproto = require "sproto"local hostlocal last = ""local REQUEST = {}function REQUEST:say() print("say", self.name, self.msg)return {name = "cxl", msg = "hello"}#对应 response ,即响应。改值会调用 response 打包endfunction REQUEST:handshake() print("handshake")endfunction REQUEST:quit() print("quit")endlocal function request(name, args, response) local f = assert(REQUEST[name]) local r = f(args) if response and r then -- 生成回应包(response是一个用于生成回应包的函数。) -- 处理session对应问题 return response(r) endendlocal function unpack_package(text)local size = #textif size < 2 thenreturn nil, textendlocal s = text:byte(1) * 256 + text:byte(2)if size < s+2 thenreturn nil, textendreturn text:sub(3,2+s), text:sub(3+s)endlocal function recv_package(last, fd)local resultresult, last = unpack_package(last)if result thenreturn result, lastendlocal r = socket.read(fd)if r thenreturn nil, last .. relsereturn nil, nilendendlocal function send_package(id, pack)local package = string.pack(">s2", pack)socket.write(id, package) endlocal function accept(id) socket.start(id)last = "" host = sproto.new(proto.c2s):host "package" host2 = sproto.new(proto.s2c):host "package" -- request = host:attach(sproto.new(proto.c2s)) while true dolocal strstr, last = recv_package(last, id)if str thenlocal type,str2,str3,str4 = host:dispatch(str)if type=="REQUEST" thenlocal ok, result = pcall(request, str2,str3,str4)if ok thenif result thensend_package(id,result)endelseskynet.error(result)endendif str2 == "quit" thenprint("quit!")socket.close(id)returnendif type=="RESPONSE" then-- 暂时不处理客户端的回应print("client response")end elseif not last thenprint("disconnected!")socket.close(id)returnend endendskynet.start(function() print("==========Socket Start=========") local id = socket.listen("127.0.0.1", 8888) print("Listen socket :", "127.0.0.1", 8888) socket.start(id , function(id, addr) -- 接收到客户端连接或发送消息() print("connect from " .. addr .. " " .. id) -- 处理接收到的消息 accept(id) end)end)
结果如下:
客户端从终端接收输入:
服务器:
可以发现两边的打包解包全都正确,而且服务器对客户端的每次请求都有响应,目前响应都是 {name = "cxl", msg = "hello"}。当初想在服务器端直接对 response 的包用 dispatch() 解包,直接报错。这是因为对于 response 类型的解包,dispatch() 知道是回应,就会知道是自己启动的该次服务,就回去查找对应 session id。但其实,我们属于提前解包,并未在客户端解包,服务器端是没有该 session id的,导致报错。
阅读全文
0 0
- 【Skynet】Sproto初学与应用
- unity-与skynet通信二三事(sproto,crypt)
- skynet的sproto模块
- skynet sproto 阅读笔记之一 协议的生成
- skynet sproto 阅读笔记之二 协议的构造
- sproto
- Sproto(与客户端通信协议)
- Doxygen初学与简单应用
- Doxygen初学与简单应用
- Doxygen初学与简单应用
- Doxygen初学与简单应用
- Doxygen初学与简单应用
- Doxygen初学与简单应用
- skynet
- Skynet
- skynet
- 【Skynet】socket与多服务
- 初试 sproto
- scala implicit关键字详解(隐式转换函数、隐式类、隐式参数、隐式值)
- 【数位DP】C~K的幸运数字
- msfvenom生成shellcode
- Python: sorted,operator.itemgetter的用法
- Scala知识点整理
- 【Skynet】Sproto初学与应用
- [简单逻辑学]逻辑学的基本原理——万物终有其根源
- Pairs Forming LCM LightOJ
- HDU 6206 Apple(高精度C++)
- 微信公众号开发常见问题整理
- MapKit/地图定位导航(第一篇:基本配置)
- Linux免密ssh登录
- Java编辑练习
- 黑裙安装-yellowcong