本篇博客记录本人的物联网实验的完成过程和代码,均是用Java来模拟。
实验一
本实验旨在探究在校园网环境下,MQTT通信架构的性能表现,并与UDP协议进行比较。
1.实验要求
1、实现MQTT通信架构,搭建MQTT服务器,前端传感器发送数据。
2、测试MQTT和UDP收发的吞吐量、丢包率(校园网环境下)。
2.实验结果
1.搭建MQTT服务器
下载mosquitto:
能通过控制台收发消息:
2.搭建测试环境
我是通过2台电脑来进行模拟,因为我比较擅长Java所以使用的是IDEA这个软件,两台电脑同时连接校园网,将左边的大笔记本当作前端来发送数据,右边的小笔记本当作后端来接收数据。
3.吞吐量测试
下面是UDP发送方吞吐量测试,数据包发送方分别发送:10万条、50万条、100万条数据的吞吐量测试。每秒能发送的数据包大约在20000个左右。
下面是UDP接收方吞吐量测试,数据包接收方分别接收:10万条、50万条、100万条数据的吞吐量测试。每秒能接收的数据包在18500个左右。
下面是MQTT发送方吞吐量测试(下图“接收”实际上应该改为“发送”),数据包发送方分别接收:10万条、50万条、100万条数据的吞吐量测试。每秒能发送的数据包在18000个左右。
下面是MQTT接收方吞吐量测试,数据包接收方分别接收:10万条、50万条、100万条数据的吞吐量测试。每秒能接收的数据包在18000个左右。
4.丢包率测试
UDP进行了3次重复实验,每次发送100万个数据包。
第1次接收到984494个数据包,丢包率1.55%。
第2次接收到985153个数据包,丢包率1.48%。
第3次接收到994441个数据包,丢包率1.55%。
MQTT进行了3次重复实验,每次发送100万个数据包。
3次实验接收方均接收到了所有数据包,丢包率为0。
5.结果比较
- UDP相较于MQTT发送信息的速度更快。UDP平均每秒能发20000个数据包。MQTT平均每秒能发18000个数据包。
- UDP相较于MQTT接收信息的速度更快。UDP平均每秒能接收18500个数据包。MQTT平均每秒能收18000个数据包。
- MQTT相较于UDP丢包的概率更低。MQTT在100万次的收发强度下,丢包率为0%。UDP的丢包率为2%。
6.结论
整体而言,MQTT收发信息的效率比UDP约低10%,但丢包率基本为0%,而且MQTT协议基于TCP/IP协议,因此在一些对数据收发准确度比较高的场合建议使用MQTT。
3.实验代码
UDP接收
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class Receive {
public static void main(String[] args) {
DatagramSocket socket = null;
int cnt=1;
long startTime=0,endTime=0;
try {
socket = new DatagramSocket(4445);
byte[] buf = new byte[256];
DatagramPacket packet = new DatagramPacket(buf, buf.length);
socket.receive(packet);
String received = new String(packet.getData(), 0, packet.getLength());
if(received.equals("start")){
startTime = System.currentTimeMillis();
}
while (true) {
packet = new DatagramPacket(buf, buf.length);
socket.receive(packet);
received = new String(packet.getData(), 0, packet.getLength());
if(received!=null) cnt++;
if(cnt%10000==0) System.out.println(cnt);
if(received.equals("end")){
endTime = System.currentTimeMillis();
double seconds = (endTime-startTime)/1000.0;
double perSeconds = 1000000.0/seconds;
System.out.println("接收100万条数据耗时:"+seconds+" 秒");
System.out.println("接收100万条数据,平均每秒接收:"+perSeconds+" 条");
break;
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
System.out.println(cnt);
if (socket != null) {
socket.close();
}
}
}
}
UDP发送
import java.io.IOException;
import java.net.*;
import java.util.Random;
public class Send {
public static void main(String[] args) throws InterruptedException, SocketException {
try {
DatagramSocket socket = new DatagramSocket(null);
InetAddress address = InetAddress.getByName("10.30.83.89");
Random random = new Random();
long startTime=0,endTime=0;
startTime = System.currentTimeMillis();
String temperature = "start";
byte[] buf = temperature.getBytes();
DatagramPacket packet = new DatagramPacket(buf, buf.length, address, 4445);
socket.send(packet);
for(int i=1;i<=1000000;i++) {
int t = random.nextInt(60)-random.nextInt(30);// 模拟温度值
temperature = String.valueOf(t);
buf = temperature.getBytes();
packet = new DatagramPacket(buf, buf.length, address, 4445);
socket.send(packet);
if(i%10000==0) System.out.println(i);
}
temperature = "end";
buf = temperature.getBytes();
packet = new DatagramPacket(buf, buf.length, address, 4445);
socket.send(packet);
endTime = System.currentTimeMillis();
double seconds = (endTime-startTime)/1000.0;
double perSeconds = 1000000.0/seconds;
System.out.println("接收100万条数据耗时:"+seconds+" 秒");
System.out.println("接收100万条数据,平均每秒接收:"+perSeconds+" 条");
} catch (IOException e) {
e.printStackTrace();
}
}
}
MQTT接收
public class Receive {
public static void main(String[] args) throws MqttException {
Receive client = new Receive();
client.start();
}
//MQTT安装的服务器地址和端口号(本机的ip)
public static final String HOST = "tcp://10.30.83.89:1883";
//定义一个主题
public static final String TOPIC = "wtblnet/iotcloud/";
//定义MQTT的ID,可以在MQTT服务配置中指定
private static final String clientid = "client-2";
private MqttClient client;
private MqttConnectOptions options;
private String userName = "admin";
private String passWord = "123456";
private void start() {
try {
// host为主机名,clientid即连接MQTT的客户端ID,一般以唯一标识符表示,MemoryPersistence设置clientid的保存形式,默认为以内存保存
client = new MqttClient(HOST, clientid, new MemoryPersistence());
// MQTT的连接设置
options = new MqttConnectOptions();
options.setMaxInflight(10000);
// 设置是否清空session,这里如果设置为false表示服务器会保留客户端的连接记录,这里设置为true表示每次连接到服务器都以新的身份连接
options.setCleanSession(false);
// 设置连接的用户名
options.setUserName(userName);
// 设置连接的密码
options.setPassword(passWord.toCharArray());
// 设置超时时间 单位为秒
options.setConnectionTimeout(10);
// 设置会话心跳时间 单位为秒 服务器会每隔1.5*20秒的时间向客户端发送个消息判断客户端是否在线,但这个方法并没有重连的机制
options.setKeepAliveInterval(20);
// 设置回调,client.setCallback就可以调用PushCallback类中的messageArrived()方法
client.setCallback(new PushCallback());
MqttTopic topic = client.getTopic(TOPIC);
int qos = 2;
//setWill方法,如果项目中需要知道客户端是否掉线可以调用该方法。设置最终端口的通知消息
options.setWill(topic, "我未掉线".getBytes(), qos, true);
client.connect(options);
//订阅消息
int[] Qos = {qos};
String[] topic1 = {TOPIC};
client.subscribe(topic1, Qos);
} catch (Exception e) {
e.printStackTrace();
}
}
public class PushCallback implements MqttCallback {
long cnt = -2, end = 99999;
long start , endTime ;
@Override
public void connectionLost(Throwable throwable) {
}
public void messageArrived(String topic, MqttMessage message) throws Exception {
if (cnt == 0) {
start = System.currentTimeMillis();
} else if (cnt == end) {
endTime = System.currentTimeMillis();
double seconds = (endTime - start) / 1000.0;
double perSeconds = end / seconds;
System.out.println("接收" + end + "条数据耗时:" + seconds + " 秒");
System.out.println("接收" + end + "条数据,平均每秒接收:" + perSeconds + " 条");
}
if (cnt % 10000 == 0) {
System.out.println(cnt);
}else if(cnt>end-30000){
System.out.println(cnt);
}
cnt++;
}
@Override
public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {
}
}
}
MQTT发送
public class Send {
public static void main(String[] args) throws MqttException, InterruptedException {
Send server = new Send();
server.message = new MqttMessage();
server.message.setQos(0);
server.message.setRetained(true);
//server.message.setPayload("发送消息MQTT".getBytes());
long startTime=0,endTime=0;
startTime = System.currentTimeMillis();
for(int i=0;i<100000;i++){
server.publish(server.topic , server.message);
}
endTime = System.currentTimeMillis();
double seconds = (endTime-startTime)/1000.0;
double perSeconds = 100000.0/seconds;
System.out.println("接收10万条数据耗时:"+seconds+" 秒");
System.out.println("接收10万条数据,平均每秒接收:"+perSeconds+" 条");
}
public Send() throws MqttException {
// MemoryPersistence设置clientid的保存形式,默认为以内存保存
client = new MqttClient(HOST, clientid, new MemoryPersistence());
connect();
}
public static final String HOST = "tcp://10.30.83.89:1883"; //MQTT安装的服务器地址和端口号
public static final String TOPIC = "wtblnet/iotcloud/"; //定义一个主题
private static final String clientid = "client-1"; //定义MQTT的ID,可以在MQTT服务配置中指定
private MqttClient client;
private MqttTopic topic;
private String userName = "admin";
private String passWord = "123456";
private MqttMessage message;
private void connect() {
MqttConnectOptions options = new MqttConnectOptions();
options.setCleanSession(false);
options.setUserName(userName);
options.setMaxInflight(1000000);
options.setPassword(passWord.toCharArray());
options.setConnectionTimeout(10); // 设置超时时间
options.setKeepAliveInterval(20); // 设置会话心跳时间
try {
client.setCallback(new MqttCallback() {
public void connectionLost(Throwable cause) {
System.out.println("连接断开...(可以做重连)"); // 连接丢失后,一般在这里面进行重连
}
public void deliveryComplete(IMqttDeliveryToken token) {
}
public void messageArrived(String topic, MqttMessage message) throws Exception {
}
});
client.connect(options);
topic = client.getTopic(TOPIC);
} catch (Exception e) {
e.printStackTrace();
}
}
public void publish(MqttTopic topic , MqttMessage message) throws MqttPersistenceException, MqttException {
MqttDeliveryToken token = topic.publish(message);
}
}
实验二
1.实验要求
1.1 模拟虚拟传感器
用java、python或者其他的编程语言,写一个能够定时发送UDP报文的虚拟传感器(包括UDP报文的接收服务器)。可以模拟温度传感器,温湿度传感器,气象传感器,土壤传感器,也可以模拟随机的GPS位置变化。传感器的采样频率可以自行设定。传感器的类型没有限制,自由发挥。
1.2数据可视化
把收到的传感器的数据能够用简单的图表给展示出来(如echart),或者寻找相似的web3d组件开源的用于可视,使用的可视化工具不做限制。
(Ps:传感器的数据发送到服务器的数据接收以及可视化时,如果需要用到数据存储,可以采用小型数据库或者是文件存储,具体也不做限制。)
2.实验结果
实验1.1:
我是通过Java语言在IDEA上写了2个程序,包含一个Send程序和Receive程序,Send程序用于模拟UDP报文的发送端,Receive程序用于模拟传感器采样数据。
如下图所示是Send程序发送UDP报文的效果,源码见源文件:
如下图所示是Receive程序接收UDP报文的效果,源码见源文件:
实验1.2:
该实验整体的思路是:用实验2.1写的Send程序将数据发送至Receive程序,然后在Receive程序中调用文件写方法将List集合categories和values整合成的dataModel写入C盘的data/dist下的data.json文件中。
如下图就是data.json里写入的数据:
接下来我在index.html中写入了echart的代码用于显示图表,同时写入了异步加载的代码:
然后我输入http-server启动了服务器,打开浏览器,可以实时看到温度传感模拟器的数据在不断地改变,反映到图表上,图表也在不断改变如下图:
3.实验代码
UDP发送:
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Random;
public class Send {
public static void main(String[] args) throws InterruptedException {
DatagramSocket socket = null;
try {
socket = new DatagramSocket();
InetAddress address = InetAddress.getByName("localhost");
Random random = new Random();
while (true) {
int t = random.nextInt(60)-random.nextInt(30);
// 模拟温度值
String temperature = String.valueOf(t);
byte[] buf = temperature.getBytes();
DatagramPacket packet = new DatagramPacket(buf, buf.length, address, 4445);
socket.send(packet);
System.out.println("发送UDP报文: " + "温度: " + temperature+ " ℃");
// 设置发送频率,例如每5秒发送一次
Thread.sleep(3000);
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (socket != null) {
socket.close();
}
}
}
}
UDP接收:
import com.google.gson.Gson;
import java.io.FileWriter;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.util.ArrayList;
import java.util.List;
class DataModel {
private List<Integer> categories;
private List<String> values;
public DataModel(List<Integer> categories, List<String> values) {
this.categories = categories;
this.values = values;
}
// 省略getter和setter
}
public class Receive {
public static void main(String[] args) {
List<Integer> categories = new ArrayList<>();
List<String> values = new ArrayList<>();
DatagramSocket socket = null;
try {
socket = new DatagramSocket(4445);
byte[] buf = new byte[256];
int cnt=0;
while (true) {
DatagramPacket packet = new DatagramPacket(buf, buf.length);
socket.receive(packet);
String received = new String(packet.getData(), 0, packet.getLength());
categories.add(cnt);
values.add(received);
cnt++;
DataModel dataModel = new DataModel(categories,values);
System.out.println("接收UDP报文: " + "温度: " + received+ " ℃");
Gson gson = new Gson();
String json = gson.toJson(dataModel);
try (FileWriter file = new FileWriter("C:\\data\\dist\\data.json")) {
file.write(json);
file.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
} catch (IOException e) {
e.printStackTrace();
} finally {
if (socket != null) {
socket.close();
}
}
}
}
前端代码:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8" />
<title>ECharts</title>
<!-- 引入刚刚下载的 ECharts 文件 -->
<script>
// 设置延时函数来刷新页面
function autoRefreshPage() {
setTimeout(function() {
// 刷新页面
location.reload();
}, 5000); // 设置间隔时间为5000毫秒(5秒)
}
// 当文档加载完成时调用autoRefreshPage函数
window.onload = autoRefreshPage;
</script>
<script src="echarts.js"></script>
<script src="https://cdn.bootcss.com/jquery/1.12.4/jquery.min.js"></script>
</head>
<body>
<!-- 为 ECharts 准备一个定义了宽高的 DOM -->
<div id="main" style="width: 600px;height:400px;"></div>
<!-- 引入 jQuery 库 -->
<script>
var myChart = echarts.init(document.getElementById('main'));
// 显示标题,图例和空的坐标轴
myChart.setOption({
title: {
text: '温度传感器数据实时加载'
},
tooltip: {},
legend: {
data: ['温度']
},
xAxis: {
data: []
},
yAxis: {},
series: [
{
name: '温度',
type: 'bar',
data: []
}
]
});
// 异步加载数据
$.get('data.json?' + Math.random()).done(function (data) {
// 填入数据
myChart.setOption({
xAxis: {
data: data.categories
},
series: [
{
// 根据名字对应到相应的系列
name: '温度',
data: data.values
}
]
});
});
</script>
</body>
</html>
实验三
1.实验要求
延续前面实验的内容,在对比MQTT和UDP基础上:
1.实现 1台服务器接受3个以上时间序列传感器数据(频次相同),并可视化显示——单独显示趋势和曲线、组合显示(需要归一化处理)。
2.设置报警条件,单传感器数据超过异常告警范围的,需要页面提示报警。最终推送手机报警——采用钉钉的开放Webhook接口,需要能够在钉钉中可以查看传感器的数据和报警内容(钉钉机器人webhook接口)。