文章目录
前言
我们已经搭建好了开发环境,并且已经设计好了UI界面,现在就要正式编写后端软件,来进行数据交互。先来介绍这些第三方依赖包。
KOA2框架
基于nodejs的web框架有很多,讨论最多的就是express和koa2,express生态最好,用的最多,而koa2是express的原班人马打造,相当于express的升级版。我个人的原则是:学就学最新的,除非学不会。所以我选用koa2框架。
推荐B站这个教程,koa教程,说实话因为之前没接触过web相关的知识,理解这些路由和中间件等概念,还是让我头痛了一阵,就算现在我也不能保证我理解的完全正确,但基本能实现我现在想要的功能,欢迎指正批评。而且想学好KOA和nodejs还是要花费很多功夫的,我目前这样的浅尝辄止的程度还是不够的,有机会继续深入研究。
在这篇文章中不系统总结(还没学到位),只解释我用到的一些东西。
koa2初步使用
我们现在项目目录中新建一个index.js文件,然后键入以下代码
//引入Koa
const koa=require('koa');
const app=new koa();
//配置中间件
app.use( async (ctx)=>{
ctx.body='hello koa2'
})
//监听端口
app.listen(3000);
//本地终端打印log
console.log('[demo] start-quick is starting at port 3000')
然后在浏览器中访问localhost:3000
或者127.0.0.1:3000
(一个意思),可以看到,字符串’hello koa2’就出现在网页中了。
PS :
- javascript的字符串可以用单引号也可以用双引号。
- javascript每条语句可以不加分号,只敲回车,也能正常运行,但最好加。因为不是不需要分好,而是JS剖析器会在执行期间自动帮你插入分号。
koa示例代码解释
引入模块用require,require是CommonJS规范引入模块的方法,而对于最新的ES6规范,引入模块的方法是import,关于这两种规范引入模块方法的错杂和历史以后我有机会再整理。我们只跟着npm官网,包的使用教程来使用,npm官网koa,官网怎么用,我们怎么用。引用模块后,再用new关键字,对模块进行实例化。
//配置中间件
app.use( async (ctx)=>{
ctx.body='hello koa2'
})
这里的中间件的概念,官方没给定义,koa中文网给的解释是
中间件就是匹配路由之前或者匹配路由完成做的一系列的操作,我们就可以把它叫做中间件
我的理解是,中间件就是一个插件,也可以理解为一个函数。当用户请求时,要经过中间件的处理,生成响应时也要经过中间件的处理,处理顺序符合洋葱模型详细解释。
和=>这种匿名函数的写法,都请移步到B站的视频学习koa教程。
最后使用app.listen方法,对3000端口进行监听。这个函数必须调用,否则浏览器访问网址时,发出的GET、POST请求都无法响应,那么我的网址也是无法访问的。
//监听端口
app.listen(3000);
要注意nodejs的编程,不同于C或其他的一些语言顺序性、过程性比较强,nodejs更偏向于事件驱动,函数调用也都是异步的,如果想同步,顺序的执行某些回调函数,就得用async和await的组合。
koa-static中间件
koa-static是一个用来加载静态资源的中间件,我们第一章写的index.html文件想要它通过访问网址,加载到网页上,就需要用koa-static这个中间件,首先我们去看npm官网的例子
const serve = require('koa-static');
const Koa = require('koa');
const app = new Koa();
// $ GET /package.json
app.use(serve('.'));
// $ GET /hello.txt
app.use(serve('test/fixtures'));
// or use absolute paths
app.use(serve(__dirname + '/test/fixtures'));
app.listen(3000);
console.log('listening on port 3000');
上面的官方例子代表了koa-static中间件访问静态资源的三种情况,第一种访问当前目录,在当前目录找index.html这个文件,然后显示到网页中去。第二种访问相对路径,test/fixtures文件夹下找index.html这个文件。第三种绝对路径,__dirname是nodejs全局对象,代表的是当前目录。
Aedes
Aedes是一个Stream-based MQTT broker,基于流的MQTT代理,所谓broker(代理),可以理解为服务器,所有的mqtt节点的话题发布和订阅都要经过broker。关于MQTT代理,还有一个包是mosca,也能起到相同的作用。mosca和Aedes是同一个人开发的,github网址moscajs,只不过mosca已经不维护了,Aedes是mosca的升级版本。
mqtt协议
MQTT(Message Queuing Telemetry Transport,消息队列遥测传输协议),是一种基于发布/订阅(publish/subscribe)模式的"轻量级"通讯协议,该协议构建于TCP/IP协议上,由IBM在1999年发布。MQTT最大优点在于,可以以极少的代码和有限的带宽,为连接远程设备提供实时可靠的消息服务。作为一种低开销、低带宽占用的即时通讯协议,使其在物联网、小型设备、移动应用等方面有较广泛的应用。
mqtt的协议的工作方式就如下图,broker类似于一个平台,任何机器都可以在平台上发布话题(Publish),任何机器也可以订阅想要看到的话题(Subscribe),这要就能使得每个支持mqtt协议的设备网络互联,实现所谓的万物互联。这里有一个Qos(服务质量等级)的概念,有Qos0、Qos1、Qos2三个等级。
质量等级 | 解释 |
---|---|
Qos0 | 发送者只发一次 |
Qos1 | 发送者至少发送一次,确保broker收到 |
Qos2 | 发送者至少发送一次,确保broker收到,且只收到一次 |
这里的Qos2只是理想,并没有broker支持。
用aedes创建broker并测试
参考github的aedes官方库的示例链接,我们直接在index.js敲以下代码,然后在终端启动,命令
node .\index.js
。
const aedes = require('aedes')()
const server = require('net').createServer(aedes.handle)
const port = 1883
server.listen(port, function () {
console.log('server started and listening on port ', port)
})
这里我们已经启动了一个broker,我们再利用工具来测试一下,windows10电脑端,在应用商店下载MQTTBox这个应用,点击creat mqtt client后,填入我们的Ip地址和端口号,协议选择mqtt/tcp。
保存设置后,来到下面这个页面,左边发布个mytopic话题,填入数据,右边我们订阅自己发布的话题。点击发布,就可以收到信息了。
当然,我们也可以用其他客户端进行订阅,在手机应用商店搜索mqtt,我用的是EasyMQTT这个APP,填入broker的ip和端口号,手机同样能收到MQTTBox发布的mytopic话题的信息。
MQTT server over WebSocket
我们已经搭建出了mqtt的broker,现在就要考虑怎么将mqtt的话题里的payload传递到web端。
http协议和websocket
web网页常用的通讯协议是HTTP和websocket。像我们前面使用koa中间件发送"hello koa2"字符串,虽然这里面经过了koa2的封装,我们看不到http协议的请求和响应,但这实际上就是一个基于http协议的通讯。
例如这个例子,我们用浏览器访问3000端口,发现我们的请求时GET,response是"hello koa2"。
response是"hello koa2"
ps: 这里的favicon.ico,是去请求小图标,小图标就是浏览器每个标签上左侧的小图标。
包括后面koa-static中间件,也是封装了http协议,去请求一个静态的页面。HTTP协议是一个单向协议,客户端放松请求(GET/POST),服务器发送响应,当连接的速度要求不高和只请求一次的场景下,使用HTTP协议。 当需要服务器主动向客户端推送消息,并且连接速度需要比较快,数据需要经常更新的场景下,我们使用的就应该是websocket协议了。
mqtt over websocket
对于我们这个场景,每个nodemcu都是一个mqtt客户端,web网页也应该是一个mqtt客户端,nodejs既是mqtt的broker(代理),也是web网页的服务器。用nodejs 的好处就凸显出来了,我们可以将mqtt的broker和web服务器,写在同一个nodejs脚本里。首先我们看官方例程。
例程中的ws.createServer({server:httpServer},aedes.handle)
,我在其git仓库中没有找到响应的解释,但通过试验,其功能应该是将mqtt协议的信息流传给websocket的信息流。
ps:var=require('模块')()
等价于先引用模块,再实例化。但并不是所有的模块都支持这种写法,应该与封装导出的方式相关。
//下面两行等效于
aedes=require('aedes')();
// const Aedes = require('aedes');
// var aedes=new Aedes();
后端
koa、koa-static、aedes、websocket-stream这三个依赖包都讲了下,可以开始写我们的nodejs程序了。
这里我们用到3个端口,1883端口用于mqtt broker,8888用于http模块创建的websocket,5000端口用于koa2创建的http服务器。其实应该可以直接用koa2框架里的工具来创建websocket的,但是我这里暂时是修改的官方例子,没有去探究怎么用koa2来实现websocket通讯。
其实这里面有两个http服务器,一个是8888端口,只用于websoket的。一个是5000端口,用于koa2加载静态资源的。有时间,会去把他们简化一些。
const Koa = require('koa');
const static = require('koa-static');
//const path = require('path');
//--------------------------------------------//
//下面两行等效于
aedes=require('aedes')();
// const Aedes = require('aedes');
// var aedes=new Aedes();
//--------------------------------------------//
//-----------------------MQTT broker creat-----------------------
const broker = require('net').createServer(aedes.handle);
const httpServer = require('http').createServer();
const ws = require('websocket-stream');
const port = 1883;
const wsPort = 8888;
broker.listen(port, function () {
console.log('broker listening on port', port)
});
ws.createServer({
server: httpServer
}, aedes.handle);
httpServer.listen(wsPort, function () {
console.log('websocket server listening on port', wsPort)
});
//身份验证
aedes.authenticate = function (client, username, password, callback) {
callback(null, (username === 'user' && password.toString() === '123456'));
};
// 客户端连接
aedes.on('client', function (client) {
console.log('Client Connected: \x1b[33m' + (client ? client.id : client) + '\x1b[0m', 'to broker', aedes.id);
});
// 客户端断开
aedes.on('clientDisconnect', function (client) {
console.log('Client Disconnected: \x1b[31m' + (client ? client.id : client) + '\x1b[0m', 'to broker', aedes.id);
});
//-----------------------MQTT broker creat-----------------------
var app = new Koa();
//const staticPath = './static';
//app.use(static(path.join(__dirname, staticPath)));
app.use(static('./static'));
app.listen(5000, () => { console.log('server start on port 5000'); });
至此,我们的后端和前端就都搭建好了,可以先脱离nodemcu硬件,用MQTTBox或者手机端的进行数据交互的测试。
前端脚本
第一章我们我们搭建好了UI,引入了两个脚本。其中mqtt.min.js是在npm库unpkg网站上下载的,当然我们也可以用下面注释的方式来引用它。这个脚本的作用,是让我们可以在前端的javascript脚本里使用mqtt协议。
<!-- <script src="https://unpkg.com/mqtt@4.2.8/dist/mqtt.min.js"></script> -->
<script src="./mqtt.min.js"></script>
<script src="./webclient.js" charset="utf-8"></script>
引用完mqtt.min.js的脚本后,我们就可以在javascript脚本里使用mqtt脚本的方法了。
注意,脚本第一行的mqtt.connect函数里填写的url可以是多种形式的,这里推荐ws://协议的形式,当然也可以是mqtt://192.168.3.113:8888,但如果是大家用的也是mqtt@4.2.8版本的,就会报错。这个错是官方的错,见该博客,其实修改成4.0.0版本的mqtt.min.js版本就不会有问题。其实不管什么url,连的其实还是websocket的端口。
webclient.js
const client = mqtt.connect('ws://192.168.3.113:8888', {
username: "user",
password: '123456'
});
// const client = mqtt.connect('mqtt://192.168.3.113:1883', {
// username: "user",
// password: '123456'
// });
client.subscribe("carinfo");
client.subscribe("steerbutton");
client.on("connect", function () {
console.log("服务器连接成功");
console.log(client.options.clientId);
client.subscribe("carinfo", { qos: 1 }); // 订阅text消息
});
client.on("message", function (top, message) {
console.log("当前topic:", top);
console.log(message.toString());
let messagestring=message.toString();
let stringafersplit=messagestring.split(",");
let throttle=stringafersplit[0];
let brake=stringafersplit[1];
let steer=stringafersplit[2];
let rotationspeed=stringafersplit[3]
let SOC=stringafersplit[4]
document.getElementById('throttle').innerHTML = throttle + '%';
document.getElementById('brake').innerHTML = brake + '%';
document.getElementById('steer').innerHTML = steer + "°";
document.getElementById('rotationspeed').innerHTML = rotationspeed + "rpm";
document.getElementById('SOC').innerHTML=SOC+'%';
});
// setInterval(() => {
// steer = steer + 1;
// document.getElementById('throttle').innerHTML = throttle + '%';
// document.getElementById('brake').innerHTML = brake + '%';
// document.getElementById('steer').innerHTML = steer + "°";
// }, 10)
源码如上,先连上broker,然后订阅carinfo这个话题,在message的事件中,我们利用DOM对html中的内容进行赋值DOM教程
测试
我们先用MQTTBox工具进行测试,首先在终端开启服务器。
可以看到,三个端口都在正常监听的状态。然后我们在浏览器中访问,192.168.3.113:5000这个地址,当然任何连如同一个网络的电脑和手机都可以访问。网页如下。
再打开MQTTBox。配置如下。
此时可以在服务器的终端看到,两个mqtt客户端已经连上了broker。
这里我们通过MQTTBox,发布carinfo话题,payload随便填一些信息,点击publish.
网页端可以看到,我们发送的信息已经在网页端更新了。
到此为止我们已经完成了后端和前端的代码,只剩下嵌入式设备的接入了。源码链接。