用socket实现基于UDP的不可靠文件传输(客户端和服务器分开发送)

本篇博客记录本人的物联网实验的完成过程和代码,均是用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.结果比较

  1. UDP相较于MQTT发送信息的速度更快。UDP平均每秒能发20000个数据包。MQTT平均每秒能发18000个数据包。
  2. UDP相较于MQTT接收信息的速度更快。UDP平均每秒能接收18500个数据包。MQTT平均每秒能收18000个数据包。
  3. 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接口)。

2.实验结果

3.实验代码

  • 2
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值