如何让客户端收发mqant服务器的消息,达到让客户端与mqant服务器双向通信的目的?这个问题可能目前困扰了很多同学,这里便做一下详细的讲解。
强烈建议查看mqant封装的JavaScript库,代码量很少 mqantlib.js
mqant通信基础
mqant使用的是目前非常流行的物联网通信协议mqtt协议,且只用到其中的数据收发功能,订阅功能没有用到。
通常mqtt客户端库都提供两个接口来实现消息的收发功能(mqantlib.js为例)
-
发送消息给服务端
this.client.send(topic,payload ,qos); topic 字符串 payload body []byte 字节流 qos 0
-
接收服务端发送来的消息
this.client.onMessageArrived = onMessageArrived; //注册消息接收处理事件 function onMessageArrived(message) { message.topic 字符串 message.payload 服务端发送过来的消息body []byte }
以上就是mqant所用到的mqtt通信基础功能,目前mqtt各种编程语言各种平台都有对应的客户端开发库,这些开发库都支持以上的这两种接口,只可能各种语言上字段名称不同。但最重要的概念 topic和payload是不变的。
mqant对mqtt消息的扩展
以上的接口只是与服务端建立了双向通信,且能发送任何的数据。但在实际开发过程中我们会发送各种类型的数据给服务器,同时服务器也需要根据不同的数据类型做不同的处理。如何来区分这些数据呢?
mqant 使用topic来区分不同的消息类型,例如:
客户端给服务器发送消息的topic
这里被makedown转义了,实际格式有点出入哈,没有那两个反斜杠
Shoot\@\Shoot001/HD_Enter
代表访问mqant服务器的Shoot模块Shoot001进程的HD_Enter函数。作用是进入房间
Shoot\@\Shoot001/HD_Fire
代表访问mqant服务器的Shoot模块Shoot001进程的HD_Fire函数。 作用是发射导弹
Shoot\@\Shoot001/HD_Move
代表访问mqant服务器的Shoot模块Shoot001进程的HD_Move函数。 作用是移动位置
发送这些消息给mqant服务器的前端代码表现为
this.client.send("Shoot\@\Shoot001/HD_Move ",“{X:0,Y:100}” ,0); //topic,payload
mqant服务器主动给客户端发送消息的topic
Shoot/OnMove 有玩家移动的事件
Shoot/OnFire 有玩家发射导弹的事件
Shoot/OnJoin 有玩家加入房间的事件
Shoot/OnSync 定时同步玩家状态的事件
发送这些消息在mqant服务器后端的代码表现为
/**
通知玩家移动
*/
func (self *Table)NotifyOnMove(player *objects.Player){
b, _ := json.Marshal(player.SerializableMap())
player.Session().Send("Shoot/OnMove", b) //topic payload
}
客户端如何分发这些mqant发送过来的消息呢(重点)
目前mqantlib.js是这样处理的:
-
创建一个存储 topic与消息处理函数的哈希表 (Map)
K:topic V:callback
-
当接收到mqant服务器发送过来的消息时通过topic查找callback 如果找到就调用这个callback,并将消息转发给这个callback
客户端收发消息的几种类型
- 发送消息—>需要等待mqant服务器对本次消息的处理结果
- 发送消息—>但不需要等待服务器的处理结果
- 接收mqant服务器主动下发给客户端的消息
mqant客户端中如何区分这些消息,并且正确分发给对应的callback呢?
mqant是这样处理的
针对以上几种消息收发情况,mqant对topic数据结构进行了约定和规范
客户端上行消息的topic格式
moduleType@moduleID/handler/msgid
moduleType
mqant后端模块名称 eg Chat Login
moduleID
后端模块的具体进程ID 因为一个模块可以被分配到多个进程运行,形成分布式集群,因此用moduleID来定位到具体的某一个进程中的模块
handle
实际的topic名称 例如:HD_Enter
msgid
标注这条消息的唯一性 这是一个可选项,但非常重要
通过以上的topic格式就可以定位到模块的具体一个进程的执行函数了
小贴示
你也可以不指定moduleID 当不指定moduleID时 如果某地的模块有多个进程的话,mqant允许你在后端按一定的规则去控制选择将消息投递到哪一个进程中。类似于负载均衡
eg.
Shoot/HD_Enter
如何发送消息并且等待服务器的处理结果
处理方式是 :topic加上msgid eg Shoot@Shoot001/HD_Enter/1200
其中1200 是客户端对消息的一个全局计数器,递增就行,保证唯一
当mqant服务器收到包含msgid格式的topic消息时它就知道这条消息需要返回处理结果。此时gate网关会将这条消息发送给后端模块后等待后端模块处理结果,当得到处理结果以后就会将结果发送给客户端。。
重点 mqant服务器返回的数据格式为 topic 保持与客户端上行的topic一致 也就是Shoot@Shoot001/HD_Enter/1200 payload 后端模块的处理结果
因此客户端收到这条消息的时候就可以通过 topic 确定是哪一次请求数据了,可以从本地的map中查找到对应的callback 然后调用。 一般情况下这种情况调用完成以后要讲callback 从map中删除,因为这是一次性的
如何只发送消息但是不需要服务器回复处理结果
解决方案: topic 不要msgid eg Shoot@Shoot001/HD_Enter
这种情况下gate模块是不会等待后端处理结果的,也不会回复客户端任何消息。
如何处理服务端主动下发的消息
一般情况下服务端主动给客户端发送的消息都是一些事件。例如用户加入房间,游戏开始,游戏暂停,游戏结束等。这些事件都可以定义成一个唯一的topic eg:
Shoot/OnMove 有玩家移动的事件
Shoot/OnFire 有玩家发射导弹的事件
Shoot/OnJoin 有玩家加入房间的事件
Shoot/OnSync 定时同步玩家状态的事件
客户端只需要在map中注册这些topic的callback就可以处理这些消息了。
最后给出我一个javascript的实际使用的代码段:
加入房间:
mqant.request(self.game.netServerId+"/HD_Enter",{
"BigRoomId": self.game.bigRoomId,
},function (data) {
var message=JSON.parse(data.payloadString);
if(message.Error!="") {
alert("加入房间失败,可能是该链接已失效");
location.href = "/qs/tacit";
return;
}
console.log(message);
},this);
玩家移动
var mqant=this.game.net.getNet();
mqant.requestNR(this.game.netServerId+"/HD_Move",{
Direction:direction
});
接收服务端主动发送的事件
mqant.on('Shoot/OnJoin', function(data) {
var message=JSON.parse(data.payloadString);
var SeatIndex=message.SeatIndex; //2 Active
if(this.SeatIndex!=SeatIndex){
alert("有玩家加入了游戏,可以点击开始按钮开始游戏了!")
}
},this);
mqant.on('Shoot/OnSync', function(data) {
},this);
mqant.on('Shoot/OnStop', function(data) {
this.OnStop(data);
},this);
mqant.on('Shoot/OnPause', function(data) {
this.OnPause(data);
this.game.paused = true;
},this);