计网 | 应用层协议仿真与验证实验-分析详解【附工程代码】

一、实验目的

软件开发工具实现应用层协议功能或设计一个简单的应用层协议,并利用仿真工具和协议分析器验证网络协议,并分析验证结果。


二、实验要求


三、实验内容

1.MQTT协议流程图

2.MQTT协议格式

每条MQTT命令消息的消息头都包含一个固定的报头,有些消息会携带一个可变报文头和一个负荷。消息格式如下:

①固定报文头(Fixed Header)

MQTT固定报文头最少有两个字节,第一字节包含消息类型(Message Type)和QoS级别等标志位。第二字节开始是剩余长度字段,该长度是后面的可变报文头加消息负载的总长度,该字段最多允许四个字节。

剩余长度字段单个字节最大值为二进制0b0111 1111,16进制0x7F。也就是说,单个字节可以描述的最大长度是127字节。为什么不是256字节呢?因为MQTT协议规定,单个字节第八位(最高位)若为1,则表示后续还有字节存在,第八位起“延续位”的作用。

例如,数字64,编码为一个字节,十进制表示为64,十六进制表示为0×40。数字321(65+2*128)编码为两个字节,重要性最低的放在前面,第一个字节为65+128=193(0xC1),第二个字节是2(0x02),表示2×128。

由于MQTT协议最多只允许使用四个字节表示剩余长度(如表1),并且最后一字节最大值只能是0x7F不能是0xFF,所以能发送的最大消息长度是256MB,而不是512MB。

②可变报文头(Variable Header)

可变报文头主要包含协议名、协议版本、连接标志、心跳间隔时间、连接返回码、主题名等,后面会针对主要部分进行讲解。

③有效负荷(Payload)

Payload直译为负荷,可能让人摸不着头脑,实际上可以理解为消息主体(body)。

当MQTT发送的消息类型是CONNECT(连接)、PUBLISH(发布)、SUBSCRIBE(订阅)、SUBACK(订阅确认)、UNSUBSCRIBE(取消订阅)时,则会带有负荷。

3.运行代码及截图

代码如下:

package srx.awesome.code.mqtt.client;
import org.eclipse.paho.client.mqttv3.*;
public class PahoTest {
    //关注的主题
    private static String topic        = "MQTT Examples";//
    //发送的内容
    private static String content      = "Hello MQTT!!!!!";
    //质量等级
    private static int qos             = 2;
    //MQTT服务地址
    private static String broker       = "tcp://iot.eclipse.org:1883";
    //客户端ID
    private static String clientId     = "JavaSample";
    //用户名
    private static String userName     = "admin";
    //密码
    private static String passWord     = "password";
    @SuppressWarnings("finally")
    public static void main(String[] args) {
        try {
            //创建客户端
            MqttClient sampleClient = new MqttClient(broker, clientId, null);
            //配置回调函数
            sampleClient.setCallback(new MyMqttCallback());
            //创建连接选择
            MqttConnectOptions connOpts = getMqttConnectOptions(userName, passWord);
            System.out.println("Connecting to broker: "+broker);
            //创建服务连接
            sampleClient.connect(connOpts);
            System.out.println("Connected");
            //关注主题,质量等级为2
            sampleClient.subscribe(topic, qos);
            //在另一个线程中发送消息
            Thread thread = new Thread(() -> {
                try {
                    publishMsg(topic, content, qos, sampleClient);
                } catch (MqttException e) {
                    e.printStackTrace();
                }
            });
            thread.start();
            thread.join();

            //断开服务连接
            sampleClient.disconnect();
            System.out.println("Disconnected");

        } catch(MqttException me) {
            System.out.println("reason "+me.getReasonCode());
            System.out.println("msg "+me.getMessage());
            System.out.println("loc "+me.getLocalizedMessage());
            System.out.println("cause "+me.getCause());
            System.out.println("excep "+me);
            me.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.exit(0);
        }
    }

    private static void publishMsg(String topic, String content, int qos, MqttClient sampleClient) throws MqttException {
        //循环发送10次消息
        for (int times =0 ;times<10; times++) {
            System.out.println(String.format("%d time Publishing message: %s", times, content));
            //创建消息内容
            MqttMessage message = new MqttMessage(content.getBytes());
            //设置质量级别
            message.setQos(qos);
            //发送消息
            sampleClient.publish(topic, message);
            //System.out.println("Message published");
        }
    }

    private static MqttConnectOptions getMqttConnectOptions(String userName, String passWord) {
        MqttConnectOptions connOpts = new MqttConnectOptions();
        
        //是否清除Session,如果否,重新连接之后会自动关注之前关注的主题
        connOpts.setCleanSession(true);
        connOpts.setUserName(userName);
        connOpts.setPassword(passWord.toCharArray());
        connOpts.setAutomaticReconnect(true);

        // 设置连接超时时间, 单位为秒,默认30
        connOpts.setConnectionTimeout(30);

        // 设置会话心跳时间,单位为秒,默认20
        connOpts.setKeepAliveInterval(20);
        return connOpts;
    }
}


//设置的代理回调器,将通信和业务处理分离开:
package srx.awesome.code.mqtt.client;
import org.eclipse.paho.client.mqttv3.IMqttDeliveryToken;
import org.eclipse.paho.client.mqttv3.MqttCallback;
import org.eclipse.paho.client.mqttv3.MqttException;
import org.eclipse.paho.client.mqttv3.MqttMessage;
class MyMqttCallback implements MqttCallback {
    //端看连接之后被调用
    @Override
    public void connectionLost(Throwable arg0) {
        System.out.println("Connection Lost:"+arg0.getMessage());

    }
    //收到消息后被发送
    @Override
    public void messageArrived(String s, MqttMessage mqttMessage) throws MqttException {
        System.out.println(String.format("get Msg: %s from Topic: %s", mqttMessage, s));
    }
    //消息被送到之后被调用
    @Override
    public void deliveryComplete(IMqttDeliveryToken iMqttDeliveryToken) {
            if(iMqttDeliveryToken.isComplete()){
            System.out.println(String.format("Delivery a Msg to Topic: %s",iMqttDeliveryToken.getTopics()[0]));
        }
    }}

截图如下:

如图显示10次消息发送全部成功,客户端也成功收到自己发送的消息。由于设置QoS=2,需要服务器和客户端之间多次通信,耗费了时间,往往是消息已经被发到了,客户端才确定消息真的被发出了。

4.Wireshark抓包截图+分析

①连接服务端

连接标志 (Connect Flag):

连接标志字节包含一些用于指定MQTT连接行为的参数。它还指出有效载荷中的字段是否存在。

用户名密码标志位(User Name Flag/Password FLag):

顾名思义,设置了就必须包含对应字段;

遗嘱保留(Will Retain):

如果遗嘱消息被发布时需要保留,需要指定这一位的值。

遗嘱标志位设置为0:遗嘱标志位为0,遗嘱保留必须为0;

遗嘱标志位设置为1:

遗嘱保留为0:服务端必须将遗嘱消息当中非保留消息发布;

遗嘱保留为1:服务端必须将遗嘱消息当中保留消息发布

遗嘱QoS(QoS Level):

指定发布遗嘱消息时使用的服务质量等级。

设置为00:遗嘱标志位为0,遗嘱QoS必须为0;

设置为01:遗嘱QoS可以等于0(0x00),1(0x01),2(0x02)。不能等于3。

遗嘱标志位(Will Flag):

设置为1:表示Will Message消息必须被存储在服务端,并与这个网络连接关联;连接标志中的Will QoS和Will Retain字段会被服务端用到,且必须包含Will Topic字段,

设置为0:表示连接标志中的Will QoS和Will Retain字段必须设置为0,并且不能包含Will Message和Will Topic字段。

清理会话(Clean Session Flag):

指定了会话状态的处理方式;控制会话状态的生存时间;

设置为0:服务端基于当前会话(以Client ID识别)的状态恢复与客户端的通信,若没有关联会话,则创建新会话;

设置为1:服务端与客户端丢弃之前的任何会话,重新开始新的会话,与该会话关联的状态数据不能被任何会话复用;

有效载荷 playload:

必须以这个顺序出现:客户端标识符、遗嘱主题、遗嘱消息、用户名、密码

客户端标识符 Client ID:

客户端连接服务端的唯一标识;

服务端可以允许编码后超过23个字节的客户端标识符 (ClientId)。

“0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ”

服务端可以允许包含不是上面列表字符的客户端标识符 (ClientId)。

当客户端提供零字节的客户端标识符时,必须将清理会话标志设置为1;

如果提供了零字节但清理会话标志为0,服务端拒绝,并返回0x02(表示标识符不合格),然后关闭网络连接。

遗嘱主题Will Topic

遗嘱消息Will Message

用户名密码:

用于身份验证和授权。

服务端根据对应的标志位来对消息采取对应操作。

②确认连接请求

③发布消息

重发标志(DUP Flag):

为0:表示客户端或服务的第一次请求发送这个PUBLISH报文;
为1:表示可能是一个早前报文请求的重发。

服务质量等级(QoS Level):

指定发布遗嘱消息时使用的服务质量等级。

设置为00:遗嘱标志位为0,遗嘱QoS必须为0;

设置为01:遗嘱QoS可以等于0(0x00),1(0x01),2(0x02)。不能等于3。

保留标志(Retain):

为0:表示客户端或服务的第一次请求发送这个PUBLISH报文;
为1:表示服务端必须存储这个消息和其QoS,以便分发给遗嘱主题匹配的订阅者;

④发布确认

⑤订阅主题

⑥订阅确认

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

米莱虾

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值