写在前面:
前两篇文字<<基于MQTT协议谈谈物联网开发-华佗写代码>>,<<基于MQTT协议实现Broker-华佗写代码>>主要叙述了MQTT协议的编解码以及基于MQTT协议的一些常见应用场景,并以一个简单的消息推送系统作为例子具体阐述了Mqtt Broker部分的实现,之前主要以原生android或者iOS或者服务端代理作为例子,考虑到在移动端开发时,选择的技术栈有所不同,有的选择web前端开发.作为例子,这里以之前的消息推送系统为例基于web前端开发,继续叙述基于WebSocket协议实现Broker.
1.WebSocket协议主要特点:
(1)基于http协议握手建立tcp长连接;
(2)相比http,WebSocket协议交换最小化,降低网络流量;
(3)双向通信,服务器可以主动推送数据给客户端;
2.Mqtt Broker具体实现(WebSocket部分):
2.1Mqtt Broker架构草图:
2.2Mqtt Broker实现细节:
(1)新增实现websocket server,监听不同端口;
(2)每个websocket连接,实例化一个mqttclient负责其协议解析,消息发布和订阅等;
(3)复用之前Mqtt Broker与RabbitMQ通信部分,具体参考上一篇文字;
(4)其他...
2.3Mqtt Broker代码实现(WebSocket部分):
defer mb.wg.Done()
http.HandleFunc("/", mb.webHandler)
webserver := &http.Server{Addr: mb.webaddr, Handler: nil}
var listener net.Listener
var err error
listener, err = net.Listen("tcp", mb.webaddr)
if err != nil {
return
}
U.GetLog().Printf("listen and serve web broker on %s", mb.webaddr)
err = webserver.Serve(tcpKeepAliveListener{listener.(*net.TCPListener)})
}
//每一个Websocket连接,实例化一个MqttClient负责其协议解析,以及与rabbitmq的通信
2.4Mqtt Client代码实现(WebSocket部分):
//定义WebSocket通信消息格式
//Action选项有publish,subscribe,unsubscribe
type WebMessage struct {
Action string
Topic string
Payload string
}
type MqttClient struct {
wg *sync.WaitGroup
broker *MqttBroker
tcpconn net.Conn
wconn *websocket.Conn
...
needDisConn bool
}
//通过WebMessage.Action区分消息指令类型
func (mc *MqttClient) ServeWeb() {
defer mc.wg.Done()
defer mc.commonDefer()
if mc.wconn == nil {
return
}
for {
if mc.needDisConn {
break
}
_, message, err := mc.wconn.ReadMessage()
if err != nil {
U.GetLog().Printf("handle message error:%v", err)
mc.needDisConn = true
continue
}
wm := WebMessage{}
err = json.Unmarshal(message, &wm)
if err != nil {
U.GetLog().Printf("json.Unmarshal(message, &wm) error:%v", err)
continue
}
switch wm.Action {
case "subscribe":
err = mc.handleWebSubscibe(wm.Topic)
case "publish":
err = mc.handleWebPublish(wm.Topic, wm.Payload)
case "unsubscribe":
err = mc.handleWebUnSubscribe(wm.Topic)
case "ping":
mc.lastheartbeat = 0
default:
U.GetLog().Printf("unexpected WebMessage Action:%s", wm.Action)
continue
}
if err != nil {
U.GetLog().Printf("handle message error:%v", err)
}
mc.lastheartbeat = 0
}
}
3.WebSocket Client端实现:
3.1实现细节:
(1)建立与WebSocket Server的连接;
(2)初始化WebSocket,注册相关回调函数;
(3)实现WebSocket断线重连机制;
(4)封装类似mqtt基于topic的发布订阅等接口;
(5)Nodejs端需要browserify相关js文件,Javascript端可以直接调用WebSocket;
(6)其他...
3.2具体代码实现:
var WebSocket = require('ws');
var WEBSOCKET_MQTT_BROKER = 'ws://your_server_ip/ws/';
var ping = {
Action: "ping"
};
var _listeners = {};
var _websocket = null;
var _connected = false;
_access = function () {
console.log('try mqtt.connect');
_connect_websocket();
setInterval(function () {
_reconnect_websocket();
if (_websocket != null && _connected) {
_websocket.send(JSON.stringify(ping));
}
}, 3000);
};
//websocket初始化,并实现相关回调函数
_init_websocket = function () {
if (_websocket == null) {
return;
}
_websocket.onopen = function () {
_connected = true;
console.log("Connected to WebSocket server.");
for (var topic in _listeners) {
var sub = {
Action: "subscribe",
Topic: topic,
Payload: ""
};
_websocket.send(JSON.stringify(sub));
}
};
_websocket.onclose = function () {
_connected = false;
_websocket = null;
console.log("Disconnected");
};
_websocket.onmessage = function (evt) {
console.log('recv data from server: ' + evt.data);
var dataObj = JSON.parse(evt.data);
_listeners[dataObj.Topic] && _listeners[dataObj.Topic](dataObj.Payload);
};
_websocket.onerror = function (evt) {
_connected = false;
_websocket = null;
console.log('Error occured: ' + evt);
};
};
_connect_websocket = function () {
if (_connected) {
return;
}
_websocket = new WebSocket(WEBSOCKET_MQTT_BROKER);
_init_websocket();
};
//断线重连,通过定时器实现每三秒断线重连
_reconnect_websocket = function () {
if (_connected) {
return;
}
_websocket = new WebSocket(WEBSOCKET_MQTT_BROKER);
_init_websocket();
};
//模拟mqtt发布消息
sendMessage = function (topic, data) {
if (!_websocket || !_connected) {
var err = new Error('iot client not ready.');
console.warn(err);
return;
}
var send_data = JSON.stringify(data);
var pub = {
Action: "publish",
Topic: topic,
Payload: send_data
};
_websocket.send(JSON.stringify(pub));
};
//模拟mqtt订阅消息,并根据topic注册回调函数
onMessage = function (topic, callback) {
_listeners[topic] = callback;
if (!_websocket || !_connected) {
console.warn('onMessage, but iot client not ready.');
return;
}
var sub = {
Action: "subscribe",
Topic: topic,
Payload: ""
};
_websocket.send(JSON.stringify(sub));
};
//模拟mqtt取消订阅,并根据topic删除对应回调函数
stopReceiveMessage = function (topic) {
delete _listeners[topic];
if (!_websocket || !_connected) {
console.warn('stopReceiveMessage, but iot client not ready.');
return;
}
var unsub = {
Action: "unsubscribe",
Topic: topic,
Payload: ""
};
_websocket.send(JSON.stringify(unsub));
};
_access();
4.WebSocket相关nginx配置:
server {
listen 80;
server_name your_server_name;
...
location /ws/ {
proxy_redirect off;
add_header Access-Control-Allow-Origin *;
add_header Access-Control-Allow-Methods 'GET, POST, OPTIONS';
add_header Access-Control-Allow-Headers 'DNT,X-Mx-ReqToken,Keep-Alive,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Authorization';
proxy_pass http://127.0.0.1:2884/;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
出于篇幅考虑,之前两篇文字叙述过的内容,比如Mqtt Broker其他实现部分以及与RabbitMQ通信部分,都是复用之前的代码逻辑,这里不再赘述,Mqtt Broker中WebSocket部分相当于使用WebSocket协议做了MQTT协议的翻译转换,也有一些成员变量,用到了也不一一具体注释了,主要通过代码关键路径叙述实现的一些细节,如有错误,恳请指出,转载也请注明出处!!!
未完待续...