MsgServer 请求回应模式

来源:互联网 发布:网络打字员工作 编辑:程序博客网 时间:2024/06/11 23:27

snax.msgserver 是一个基于消息请求和回应模式的网关服务器模板.它基于snax.gateserver定制,可以接收客户端发起的请求数据包,并给出对应的回应.


和service/gate.lua不同,用户在使用它的时候,一个用户的业务处理不基于连接.即,它不把连接建立作为用户登录,不在连接断开时让用户登出.用户必须显式的登出系统,或是业务逻辑设计的超时机制导致登出.


和传统的http服务不同.在同一个连接上,用户可以发起任意多个请求,并接收多次回应,请求和回应次序不必保证一致,所以内部用session来关联请求和回应.回应不保证和请求次序相同,也方便在同一个tcp连接上发起一些服务器需要很长时间的请求,而不会阻塞后续的快速响应.


在请求回应模式下,服务器无法单向向客户端推送消息,但可以很容易模拟这种业务.你只需要再登入系统后,立刻发起一个应用层协议要求服务器返回给你最近需要推送给比的消息,如果服务器暂时没有消息推送给客户端,那么只需要挂起这个session,待有消息推送时,度武器通过这个session返回即可.


和一般的长连接服务也不同.当客户端和服务器失去连接后,只需要简单的重新接入,就可以继续工作,在snax.magserver的实现中,我们要求客户端和服务器同时只保证一条有效的通讯连接,但你可以轻易扩展成多条(但意义不大,因为session可以保证慢请求不会阻塞快请求的回应)并发.


架构


消息服务器snax.msgserver(M)必须和LoginServer(L)一起使用.用户C的业务流程通常是这样的:

1.C向L表明想登陆M.

2.L验证C的身份,交换secrst,并转告M说C想登陆.

3.M确认C的secret,做好登陆准备,生成一个subid(subid用于多重登陆)

4.L构造当次登陆用的用户名(用uid和subid联合生成)返回给C向C确认可以登陆.

5.C以这个用户名加上一个自增序列号(用于断线重连),利用secret签名和M握手接入.

6.M检查签名是否正确,以及序列号是否使用过(一次有效),然后等待M的请求,并在当前连接上回应.


如果C和M断开连接,并不意味着用户从系统登出,这时C可以自增序列号,重新和M握手.这称为修复连接,C不需要重复进行和L的登陆认证流程.


在前次连接上,C向M发送的请求,M会缓存一部分,如果连接修复后,C向M发过去用过的session号的请求,M将之前缓存的回应包直接发回,而不去重新做一次业务处理,但是,在同一连接上发起的相同的session,后一次会覆盖前一次,请求都会交给业务层处理.业务层收到相同的session,可以认为是前一次的session的回应已经被客户端收到,所以复用了这个session,因为在同一条tcp连接上,不会发生数据丢失的情况,一旦有回应包没有发到,只可能是连接断开,新的连接可以重新发起相同的请求.


注:这里cache最近的一部分请求的回应可以满足大部分需求.同时也可以约定session的自增性,发现请求过去的session而没有缓存时,强制将用户登出.


C也可以重新去L处交换新的用户名和serect.这种情况下,L会通知M将登陆状态中的C登出,更新新版的serect,重置所有之前的请求session和回应缓存,最后让C重新登陆.


使用


和GateServer和LoginServer一样,snax.msgserver只是一个模板,你还需要自定义一些业务相关的代码,才是一个完整的服务.

local msgserver = require "snax.msgserver"
你需要注册这样一些handle来处理一些事件.


function server.login_handler(uid, secret)

当一个用户登陆后,登陆服务器会转交给你这个用户的uid和serect,最终会触发login_handle方法,在这个函数里,你需要做的是判定这个用户是否真的可以登陆.(一般是可以的,如果想阻止用户多重登陆,在邓丽服务器里就完成了)


然后为用户生成一个subid,如果你的用户同时只允许一个实例存在于系统中,你可以生成固定的subid(但建议还是生成不同的).使用msgserver.username(uid,subid,servername)可以得到这个用户这次的登录名,这里servername是当前登录点的名字.


接着你应该做好用户进入的准备,常规做法是启动一个agent服务,然后命令它从数据库加载这个用户的数据,如果启动agent需要消耗大量的CPU时间,你也可以预先启动好多分agent放在一个池中,这里只需要的取出一个可用的空agent即可.


当一切准备好后,把subid返回.


在这个过程中,如果你发现一些意外情况,不希望用户进入,只需要用error抛出异常.


function server.logout_handler(uid, subid)
当一个用户想登出时,这个函数会被调用,你可以在里面做一些状态清除的工作.这个事件通常是由agent的消息触发的.

function server.kick_handler(uid, subid)
当外界(通常是登陆服务器)希望让一个用户登出时,会触发这个事件.通常你需要再里面通知agent将用户数据写数据库,并且让它在善后工作完成后,发起一个logout消息(最终会触发logout_handler)


function server.disconnect_handler(username)
当用户的通讯连接断开后,会触发这个事件,你可以不关心这个事件,也可以利用这个事件做超时管理.(比如断开连接后一定事件不重新连回来就主动登出.

function server.request_handler(username, msg, sz)
如果用户提起了一个请求,就会被这个request_handle捕获到.这里隐藏了session信息,因为你可以在这个函数中调用skynet.call等RPC调用,但一般的做法是简单的把msg,sz转发给agent即可.这里使用的是client协议通道,agent只需要处理这个协议通道,具体的业务层通讯协议并无限制.


等请求处理完后,只需要返回一个字符串,这个字符串会回到框架,加上session回应客户端.这个函数中允许抛出异常,框架会正确的捕获这个异常,并通过协议通知客户端.

function server.register_handler(name)
因为snax.msgserver实际上是snax.gateserver的一个特殊实现,同样有打开监听端口的指令,在打开端口时,会触发这个register_handler name 是在配置信息中配置的当前登录点的名字,你需要把这个名字注册到登录服务器.登录服务器就可以按约定,在用户登陆你的时候把消息转发给你.


api


一下api可以在实现上述的handler时,在恰当的时机使用:

msgserver.userid(username) : uid,subid,server 把一个登录名转换为uid,subid,sercername 三元组

msgserver.username(uid,subid,server) : username 把 uid,subid,servernamev  三元组构造成一个登陆名

msgserver.login(username,serect) : 你需要再login_handler中调用它,注册一个登陆名对应的swrect

msgserver.logout(username) : 让一个登陆名失效(登出),通常在logout_handler里调用.

msgserver.ip(username) : 查询一个登陆名对应的连接的ip地址,如果没有关联的连接,会返回nil

msgserver.start(conf) : 启动一个msgserver.conf是配置表.配置表和GateServer相同,但增加一项servername,你需要配置这个登陆点的名字.


范例


你可以在examples/login/gates.lua 看到一个实现的范例,它可以和examples/login/logind.lua协同工作.


客户端的示范在examples/login/client.lua


你可以先启动


./skynet examples/config.login
然后在同一台机器上启动

lua examples/login/client.lua
wire protocol

基础封包协议遵循的GateServer,即一个2字节的包头加内容,一下说明的是内容的编码.

当一个连接接入后,第一个包是握手包,握手首先由客户端发起:

base64(uid)@base64(server)#base64(subid):index:base64(hmac)
index至少是1,每次连接都需要比之前的大,这样可以保证握手包不会被人恶意截获复用,hmac是在登陆环节获得的共识serect,对前面一串数据做的挑战确认,具体算法可见范例代码.


服务器会针对这次握手做一个回应,回应信息是一行文本:

404 User Not Foundh

403 Index Expired

401 Unauthorized 

400 Bad Request

200 OK


如果握手成功,后续的包就是按照请求回应模式进行:


客户端发起请求:


byte 任意内容 dword session


session 原则上以Big-Endian 方式编码一个数字,但实际上session也可以看成是一个四字节的token,并无强制要求客户端如何生存和编码.


服务器回应:


bytes 内容(或异常信息) byte 标记,1表示正常返回,0表示异常返回.dword session


session 用于匹配客户端提起的请求.

0 0
原创粉丝点击