现在来编写我们的第二个服务——登录服务,在编写此服务时,建议大家对照着如下所示的流程图来看,知晓各个方法的作用,写起来会简单许多。
1、登录协议
定义如下图所示的登录协议:
客户端需要发送玩家账号和密码,服务端收到登录协议后,会做出回应,中间参数0代表登录成功,若为1则代表登录失败,第二个参数代表(失败的)原因,比如“账号或密码错误”“其他玩家正在尝试登录该账号,请稍后再试”。
2、客户端消息分发
gateway会将客户端协议以client消息的形式转发给login服务。由于客户端会发送很多协议,虽然可以在login服务的s.resp.client方法中编写多个if来判断,但消息一多,s.resp.client可能会变得混乱,因此最好是再做一次消息分发,根据不同的协议名指定不同的处理方法。
下面编写如下所示代码的client远程调用,它实现两个功能。
- 根据协议名(cmd)找到s.client.XXX方法,并调用它。
- 鉴于服务端几乎都要给客户端回应消息,因此给出一个简便处理方式。将s.client.XXX的返回值发回给客户端(经由gateway转发)。
local skynet = require "skynet"
local s = require "service"
s.client = {}
s.resp.client = function(source, fd, cmd, msg)
if s.client[cmd] then
local ret_msg = s.client[cmd]( fd, msg, source)
skynet.send(source, "lua", "send_by_fd", fd, ret_msg)
else
skynet.error("s.resp.client fail", cmd)
end
end
s.start(...)
变量的含义如下:
- s.client:定义一个空表,用于存放客户端消息处理方法。
- 参数source:消息发送方,比如某个gateway。
- 参数fd:客户端连接的标识,由gateway发送过来。
- 参数cmd和msg:协议名和协议对象。
3、登录流程处理
登录服务会先校验客户端发来的用户名密码,再作为gateway和agentmgr的中介,等待agentmgr创建代理服务。代码如下所示:
s.client.login = function(fd, msg, source)
local playerid = tonumber(msg[2])
local pw = tonumber(msg[3])
local gate = source
node = skynet.getenv("node")
--校验用户名密码
if pw ~= 123 then
return {"login", 1, "密码错误"}
end
--发给agentmgr
local isok, agent = skynet.call("agentmgr", "lua", "reqlogin", playerid, node, gate)
if not isok then
return {"login", 1, "请求mgr失败"}
end
--回应gate
local isok = skynet.call(gate, "lua", "sure_agent", fd, playerid, agent)
if not isok then
return {"login", 1, "gate注册失败"}
end
skynet.error("login succ "..playerid)
return {"login", 0, "登陆成功"}
end
1)校验用户名密码:为减少代码量,这里只是简单判断密码是否为“123”,读者可以根据自己的需要,在此处查询数据库或者平台SDK。
- playerid:玩家id;
- pw:密码;
- gate:转发消息的gateway服务;
- node:本节点名称。
2)请求登录:给agentmgr发送reqlogin请求登录。reqlogin会回应两个值,第一个值isok代表是否成功,agent代表已创建的代理服务id。对应流程图中的阶段③。
3)给gate发送sure_agent,对应流程图中的阶段⑧。
4)如果全部过程成功执行,login服务会打印“login succ”,并给客户端回应成功信息(根据消息分发过程,login服务会把s.client.XX的返回值发回给客户端)。
完整代码地址:https://gitee.com/frank-yangyu/ball-server