在之前的两篇文章中分解介绍了如何搭建MQTT服务器和MQTT桌面客户端,为了更好的体现MQTT多平台适应性,本篇文章将来说明如何使用paho-mqtt.js和JQuery.js来创建一个Web版的MQTT客户端。
在具体的需求上,仍与上篇中的WPF版MQTT客户端的需求保持一致。下面将直接说明实现过程。
MQTT学习(二)–使用MQTTNet在WPF框架下搭建MQTT客户端
目前常见的关于MQTT协议的JavaScript库有MQTT.js、paho-mqtt.js等,其中MQTT.js是需要Node.js的相关环境的,因为本人计算机上暂时没有Node.js环境,所以这个示例选用paho-mqtt.js来实现。paho-mqtt.js的源码可以在GitHub上找到。
paho.mqtt.javascript
paho-mqtt.js的引入可以使用网络地址引入
<script src="https://cdnjs.cloudflare.com/ajax/libs/paho-mqtt/1.0.1/mqttws31.js" type="text/javascript"></script>
考虑到有时需要在非互联网情况下开发测试,所以也可以将文件下载下来从本地引入。paho-mqtt.js可以从GitHub库里下载到本地。
JQuery.js是经常用到的库了。如何引入和使用方法就不再多做介绍了。对于这两个库我都是下载到本地项目引入的。
页面布局和上一篇中的WPF版客户端基本保持一致,代码如下:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<title>title>
<meta charset="utf-8" />
<script src="Scripts/jquery.min.js">script>
<script src="Scripts/paho-mqtt.js">script>
head>
<body>
<table>
<tr>
<td>td>
<td><input id="txtIp" type="text" value="127.0.0.1"/>td>
<td><input id="txtPort" type="text" value="61623"/>td>
<td><input id="btnConnect" type="button" value="连接"/>td>
<td><input id="btnDisconnect" type="button" value="断开" disabled="disabled"/>td>
<td>td>
tr>
<tr>
<td>td>
<td colspan="3">
<div id="subTopics">
div>
td>
<td><input id="btnSubscribe" type="button" value="订阅" disabled="disabled"/>td>
<td>td>
tr>
<tr>
<td>td>
<td colspan="4">
<ul id="logResult">ul>
td>
<td>td>
tr>
<tr>
<td>td>
<td>
<select id="pubTopics">
select>
td>
<td colspan="2"><input id="txtContent" type="text"/>td>
<td><input id="btnPublish" type="button" value="发布" disabled="disabled"/>td>
<td>td>
tr>
table>
body>
html>
同之前的桌面版客户端一样,我在JS脚本中定义了几个简单的数据结构。
//所有主题
var allTopics = [
{ "Topic": "/data/alarm", "Describe": "报警" },
{ "Topic": "/data/message", "Describe": "消息" },
{ "Topic": "/data/notify", "Describe": "通知" }
];
//选中订阅主题
var selectedTopics = [];
//选中发布主题
var currentTopic;
//客户端选项
var option = {
"ServerUri": "127.0.0.1",
"ServerPort": 61623,
"UserName": "admin",
"Password": "password",
"ClientId": "",
"TimeOut": 5,
"KeepAlive": 100,
"CleanSession": false,
"SSL":false
}
//客户端
var client;
数据初始化包括了订阅主题和发布主题的绑定,以及选择订阅主题和选择发布主题的事件。
$(function () {
BindSubTopics(allTopics);
BindPubTopics(allTopics);
//订阅主题选中事件
$("#subTopics input[type=checkbox]").on("click", function () {
var t = $(this).val();
var topic;
for(var i in allTopics){
var tmp = allTopics[i];
if (tmp.Topic == t) {
topic = tmp;
}
}
if ($(this).is(":checked")) {//选中
selectedTopics.push(topic);
}
else {//取消选择
if(selectedTopics.length>0){
for(var i in selectedTopics){
var tmp = selectedTopics[i];
if (tmp.Topic == t) {
selectedTopics.splice(i, 1);
}
}
}
}
});
//发布主题选中事件
$("#pubTopics").on("change", function () {
var d = $("#pubTopics option:selected").text();
var t = $("#pubTopics").val();
currentTopic = { "Topic": t, "Describe": d }
console.log(currentTopic);
});
//订阅按钮点击事件
$("#btnSubscribe").on("click", function () {
if (!client) {
alert("请连接服务端");
return;
}
if(selectedTopics.length==0){
alert("请选择要订阅的主题!");
return;
}
var msg = "";
for(var i in selectedTopics){
var t = selectedTopics[i];
client.subscribe(t.Topic);
msg+=t.Topic+";"
}
WriteToStatus("成功订阅主题:" + msg);
});
//发布按钮点击事件
$("#btnPublish").on("click", function () {
if(!client){
alert("请连接服务端");
return;
}
if (!currentTopic) {
alert("请选择要发布的主题!");
return;
}
if($("#txtContent").val()==""){
alert("请输入要发布的内容");
return;
}
var message = new Paho.Message($("#txtContent").val());
message.destinationName = currentTopic.Topic;
client.send(message);
WriteToStatus("发布了主题为" + currentTopic.Topic + "的消息:" + $("#txtContent").val())
});
});
//绑定订阅主题
function BindSubTopics(topics) {
var html = "";
for (var i = 0; i < topics.length;i++){
var topic = topics[i];
html += topic.Describe;
html += '+topic.Topic+'"/>';
}
$("#subTopics").html(html);
}
//绑定发布主题
function BindPubTopics(topics) {
var html = "";
for (var i = 0; i < topics.length; i++) {
var topic = topics[i];
html += ' topic.Topic + '">' + topic.Describe + '';
}
$("#pubTopics").html(html);
}
$(function () {
//连接按钮点击事件
$("#btnConnect").on("click", function () {
if ($("#txtIp").val()!="") {
option.ServerUri = $("#txtIp").val();
}
else {
alert("请输入服务端IP!");
return;
}
if($("#txtPort").val()!=""){
option.ServerPort = Number($("#txtPort").val());
}
else {
alert("请输入端口号!");
return;
}
//设置客户端标识
option.ClientId = guid();
//客户端实例化
client = new Paho.Client(option.ServerUri, option.ServerPort, option.ClientId)
client.onConnectionLost = onConnectionLost;//绑定连接断开事件
client.onMessageArrived = onMessageArrived;//绑定接收消息事件
//连接服务端
client.connect({
invocationContext: {
host: option.ServerUri,//IP地址
port: option.ServerPort,//端口号
path: client.path,
clientId: option.ClientId//标识
},
timeout: option.TimeOut,//连接超时时间
keepAliveInterval: option.KeepAlive,//心跳间隔
cleanSession: option.CleanSession,//是否清理Session
useSSL: option.SSL,//是否启用SSL
userName: option.UserName, //用户名
password: option.Password, //密码
onSuccess: onConnect,//连接成功回调事件
onFailure: onError//连接失败回调事件
});
});
//断开按钮点击事件
$("#btnDisconnect").on("click", function () {
client = null;
enable($("#btnConnect"), true);
enable($("#btnDisconnect"), false);
enable($("#btnPublish"), false);
enable($("#btnSubscribe"), false);
});
});
//连接成功回调事件
function onConnect() {
WriteToStatus("连接成功!")
enable($("#btnConnect"), false);
enable($("#btnDisconnect"), true);
enable($("#btnPublish"), true);
enable($("#btnSubscribe"), true);
}
//连接失败回调事件
function onError(e) {
WriteToStatus("连接失败:" + e)
enable($("#btnConnect"), true);
enable($("#btnDisconnect"), false);
enable($("#btnPublish"), false);
enable($("#btnSubscribe"), false);
}
//连接断开事件
function onConnectionLost(e) {
if (e.errorCode !== 0) {
WriteToStatus("连接异常断开:" + e.errorMessage);
enable($("#btnConnect"), true);
enable($("#btnDisconnect"), false);
enable($("#btnPublish"), false);
enable($("#btnSubscribe"), false);
}
}
//接收消息事件
function onMessageArrived(data) {
WriteToStatus("收到消息:" + data.payloadString);
}
//状态输出
function WriteToStatus(data) {
var now = new Date();
var message = '[' + now.toLocaleTimeString() + ']' + data;
console.log(message);
$("#logResult").append('' + message + '');
}
//生成GUID
function guid() {
function S4() {
return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1);
}
return (S4() + S4() + "-" + S4() + "-" + S4() + "-" + S4() + "-" + S4() + S4() + S4());
}
//切换按钮状态
function enable(button,enabled) {
if (enabled) {
button.removeAttr("disabled");
}
else {
button.attr("disabled", "disabled");
}
}
代码编写完成后,启动运行来测试一下实际效果。启动Apollo并打开管理界面,分别打开三个Web客户端页面,并输入IP地址和端口,然后连接服务端。
注意:paho-mqtt.js默认使用的是WebSocket连接,所以端口号应使用配置中ws对应的端口号,而非tcp对应的端口号。
在服务端的管理界面,可以看到Connectors选项卡中的ws分组下有三个连接。说明连接成功,客户端运行正常。选择主题订阅、发布,数据的收发也都正常。
接下来再打开一个WPF版的客户端,连接服务端,订阅、收发消息也都正常。
经过上面的测试说明桌面客户端和Web客户端可以无缝连接,相互通信了。后续将考虑采用更多不同形式的终端来实现信息互通。
实践出真知。
源代码地址