skynet范例研究--客户端

早就听说云风大大的skynet框架,一直找不到很好的时间研究,昨天下了代码和范例,一遍编译跑通了,几乎没遇到什么问题,不愧是我看好的男人。-0- 下面分析一下范例代码,由于本人之前对lua并不是很了解,几乎是现学现卖的,所以文章会有些啰嗦,不过这样也许也更适合和我一样的初学者呢?
范例github地址:https://github.com/cloudwu/skynet_sample

socket链接封装


文件:simplesocket.lua

声明定义

1
2
3
4
5
6
local lsocket = require "lsocket"       -- 以 lsocket 为基础进行封装.
local socket = {} -- 声明一个table用来保存的封装函数
local fd -- lsocket 保存连接对象
local message -- 接收消息寸在这个变量里
socket.error = setmetatable({}, { __tostring = function() return "\[socket error\]" end } )
-- 设置默认连接出错时调用的函数,错误以__tostring字符串形式显示

连接函数

1
2
3
4
5
6
7
8
9
10
11
12
13
function socket.connect(addr, port)
assert(fd == nil) -- 保证链接对象仅生成一次,只链接一次
fd = lsocket.connect(addr, port) -- 开始连接服务器
if fd == nil then -- 如果创建连接出错则报错
error(socket.error)
end
lsocket.select(nil, {fd}) -- 获取连接状态
local ok, errmsg = fd:status()
if not ok then -- 若连接不正常出错则报错
error(socket.error)
end
message = ""
end

链接状态函数

1
2
3
4
5
-- 此处为判断是否链接中
function socket.isconnect(ti)
local rd, wt = lsocket.select(nil, { fd }, ti)
return next(wt) ~= nil
end

关闭链接函数

1
2
3
4
5
function socket.close()
fd:close()
fd = nil
message = nil
end

读取链接数据函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
-- 死循环不断读取连接数据
function socket.read(ti)
while true do
local ok, msg, n = pcall(string.unpack, ">s2", message) -- 从链接读取到的数据为二进制数据,这里调用string.unpack将消息解包,解包后的数据保存在msg变量里
if not ok then -- 若解包不成功/未结束,则到连接里继续读取数据
local rd = lsocket.select({fd}, ti) -- 让 lsocket 选择我们建立的链接
if not rd then
return nil
end
if next(rd) == nil then
return nil
end
local p = fd:recv() -- 读取链接数据
if not p then -- 读取失败
error(socket.error)
end
message = message .. p -- 将读取到的数据加到message里
else
message = message:sub(n) -- 链接数据读取结束并返回解包后的数据
return msg
end
end
end

发送数据函数

1
2
3
4
5
6
7
8
9
10
function socket.write(msg)
local pack = string.pack(">s2", msg) -- 将要发送的数据打包为二进制数据
repeat
local bytes = fd:send(pack) -- 调用链接对象发送数据,返回值为发送了多少个数据
if not bytes then -- 若发送失败则报错
error(socket.error)
end
pack = pack:sub(bytes+1) -- 由于网络数据不一定一次能发送完全,所以分次发送,并删除已经发送的数据
until pack == "" -- 全部发送完毕后结束循环
end

消息处理


文件:simplemessage.lua

声明定义

1
2
3
4
5
6
7
8
local socket = require "simplesocket" -- 封装好的链接
local sproto = require "sproto" -- sproto协议
local message = {} -- 声明一个table用保存消息处理函数
local var = { -- 保存通信信息
session_id = 0 ,
session = {},
object = {},
}

注册sproto协议

1
2
3
4
5
6
7
8
9
10
11
-- sproto 协议定义在项目根目录proto文件夹中,可以文本文件打开
function message.register(name)
local f = assert(io.open(name .. ".s2c.sproto")) -- 读取客户端协议定义文件
local t = f:read "a"
f:close()
var.host = sproto.parse(t):host "package" -- 加载到sproto进行解析
local f = assert(io.open(name .. ".c2s.sproto")) -- 读取服务端协议定义文件
local t = f:read "a"
f:close()
var.request = var.host:attach(sproto.parse(t)) -- 加载服务器sproto协议
end

设置连接地址(服务器地址和端口)

1
2
3
4
function message.peer(addr, port)
var.addr = addr
var.port = port
end

连接服务器

1
2
3
4
function message.connect()
socket.connect(var.addr, var.port)
socket.isconnect()
end

绑定请求回调函数

1
2
3
function message.bind(obj, handler)
var.object\[obj\] = handler -- 将所有回调函数保存在object这个table中
end

发送请求

1
2
3
4
5
6
function message.request(name, args)
var.session\_id = var.session\_id + 1 -- 每个请求保存一个sessionID
var.session\[var.session_id\] = { name = name, req = args } -- 并将请求信息保存,请求消息类型名称和请求内容
socket.write(var.request(name , args, var.session_id)) -- 发送请求,此处调用了sproto将请求数据进行打包,再发送给服务器
return var.session_id
end

接收消息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
function message.update(ti)
local msg = socket.read(ti) -- 读取服务器发来的数据
if not msg then
return false
end
local t, session_id, resp, err = var.host:dispatch(msg) -- 通过sproto解包数据,返回值分别为
-- t:消息类型RESPONSE/REQUEST
-- session_id:消息ID,如果消息类型是REQUEST则该ID为消息名称,否则为在sproto定义文件中定义的数字ID
-- resp:消息内容
-- err:错误信息
if t == "REQUEST" then -- 如果是服务器主动发来的消息
for obj, handler in pairs(var.object) do -- 从回调函数中寻找消息对应的处理函数
local f = handler\[session\_id\] -- session\_id is request type
if f then
local ok, err_msg = pcall(f, obj, resp) -- resp is content of push
if not ok then
print(string.format("push %s for \[%s\] error : %s", session\_id, tostring(obj), err\_msg))
end
end
end
else
local session = var.session\[session\_id\] -- 通过session\_id获得消息类型名称
var.session\[session_id\] = nil -- 清除已经处理的请求
for obj, handler in pairs(var.object) do -- 从回调函数中寻找函数处理消息
if err then -- 若发生错误则进行错误处理,此处打印错误信息
local f = handler.__error
if f then
local ok, err\_msg = pcall(f, obj, session.name, err, session.req, session\_id)
if not ok then
print(string.format("session %s\[%d\] error(%s) for \[%s\] error : %s", session.name, session\_id, err, tostring(obj), err\_msg))
end
end
else
local f = handler\[session.name\] -- 获取回调函数
if f then
local ok, err\_msg = pcall(f, obj, session.req, resp, session\_id) -- 调用回调函数,并传入消息内容
if not ok then
print(string.format("session %s\[%d\] for \[%s\] error : %s", session.name, session\_id, tostring(obj), err\_msg))
end
end
end
end
end
return true
end

客户端入口


文件:simpleclient.lua

声明定义

1
2
3
4
5
6
7
8
9
10
11
local PATH,IP = ...
IP = IP or "127.0.0.1" -- 服务器IP地址
package.path = string.format("%s/client/?.lua;%s/skynet/lualib/?.lua", PATH, PATH) -- 加载lua基础包
package.cpath = string.format("%s/skynet/luaclib/?.so;%s/lsocket/?.so", PATH, PATH)
local socket = require "simplesocket" -- 加载封装好的链接包
local message = require "simplemessage" -- 加载消息处理包
message.register(string.format("%s/proto/%s", PATH, "proto")) -- 注册sproto协议定义数据
message.peer(IP, 5678) -- 设置服务器地址和端口
message.connect() -- 连接服务器
local event = {} -- 消息回调函数保存在这个table中
message.bind({}, event) -- 绑定消息回调函数

错误处理

1
2
3
4
-- 默认的错误处理函数
function event:__error(what, err, req, session)
print("error", what, err)
end

Ping

1
2
3
4
-- 心跳?
function event:ping()
print("ping")
end

登陆回调

1
2
3
4
5
6
7
8
9
10
function event:signin(req, resp)
print("signin", req.userid, resp.ok)
if resp.ok then -- 若登陆成功,则请求login
message.request "ping" -- should error before login
message.request "login" -- 请求login
else
-- signin failed, signup
message.request("signup", { userid = "alice" }) -- 若登录失败则发送注册请求
end
end

注册回调函数

1
2
3
4
5
6
7
8
function event:signup(req, resp)
print("signup", resp.ok)
if resp.ok then
message.request("signin", { userid = req.userid }) -- 若注册成功则发送登陆请求
else
error "Can't signup"
end
end

Login回调

1
2
3
4
5
6
7
8
function event:login(_, resp)
print("login", resp.ok)
if resp.ok then
message.request "ping" -- 登陆成功则发送 ping
else
error "Can't login"
end
end

服务器推送消息回调

1
2
3
function event:push(args)
print("server push", args.text)
end

登录逻辑

1
2
3
4
5
6
-- 启动客户端后发送登陆请求
message.request("signin", { userid = "alice" })
-- 接收服务器消息
while true do
message.update()
end

以上就是范例中客户端的全部内容,本人半天lua水平,有什么不对的地方欢迎指出哈,感谢阅读~

  • 本文作者: Tshine Zheng
  • 本文链接: 387.html
  • 版权声明: 本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明出处!