全网唯一物联网MQTT协议报文
结构分析以及基于C#代码的报文组装实现
介绍
MQTT是一种基于TCP/IP协议的应用层协议,它规定了不同应用之间进行数据交换时的传送格式。既然是协议,理论上可以被任何开发语言实现它,以运行在任何平台,这个特性就可以将所有可联网的物品通过此协议的方式进行数据通信,这是其一,之所以被物联网所青睐,还因为它的几个主要的特性:
1、使用发布/订阅消息模式,提供一对多的消息发布,解除耦合,各终端之间无关
2、对负载内容屏蔽的消息传输,可以对消息订阅者所接受到的内容有所屏蔽
3、具体有三种消息发布的服务质量(以后细说)
4、小型传输,开销小,固定长度的头部是 2 字节,协议交换最小化,以降低网络流量
5、使用Last Will和Testament特性通知有关各方客户端异常中断的机制
应用实现
为了更方便的抓包分析,进行了MQTT协议的服务端与客户端的应用实现
运行机制
服务端:通过云端一个服务器程序开启MQTT服务器(Broker),常见的如EMQ
客户端:不管是IoT管理应用程序还是IoT设备,都属性于客户端程序
订阅:各客户端程序如果想要接收到别人发送过来的数据,就需要订阅一个主题(Topic)
发布:任何客户端都可以根据一个主题向服务器发布消息,服务器会根据订立记录,将消息推送至订阅了对应主题的客户端
数据传输格式
MQTT报文大体上包含三大部分:固定报头、可变报头、报文载荷,整体结构如下
固定报头
第一个字节中高4位保存了消息的类型信息,包含1-14种类型(5.0版本扩充了第15个:认证交换)
1 CONNECT – 连接服务端
2 CONNACK – 确认连接请求
3 PUBLISH – 发布消息
4 PUBACK –发布确认
5 PUBREC – 发布收到(QoS 2,第一步)
6 PUBREL – 发布释放(QoS 2,第二步)
7 PUBCOMP – 发布完成(QoS 2,第三步)
8 SUBSCRIBE - 订阅主题
9 SUBACK – 订阅确认
10 UNSUBSCRIBE –取消订阅
11 UNSUBACK – 取消订阅确认
12 PINGREQ – 心跳请求
13 PINGRESP – 心跳响应
14 DISCONNECT – 断开连接
15 AUTH – 认证交换
可变报头
报文载荷
报文截获
利用 WireShark进行报文截获,以连接请求报文为例:
C#代码实现
此处以客户端发送连接请求为例,完整报文拼接实例如下
static void Main(string[] args)
{Console.WriteLine("欢迎关注朝夕教育,我是Jovan");try{string ip = "127.0.0.1";int port = 1883;string username = "admin";string password = "123456";string clientId = "C001";//Socket socketClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);socketClient.Connect(ip, port);Console.WriteLine(">>> TCP 连接通道已建立");// 建立MQTT连接{// 构建MQTT报文Listbytes = new List();bytes.AddRange(new byte[] { 0, 4 });// Protocol Name Lengthbytes.AddRange(Encoding.ASCII.GetBytes("MQTT"));// Protocol Namebytes.Add(4);byte flag = 0;flag |= 128;// 用户名标记flag |= 64; // 密码标记flag |= 2; // Clean Session 标记bytes.Add(flag);// KeepAlivebyte[] bytesKeepAlive = BitConverter.GetBytes((int)TimeSpan.FromSeconds(100).TotalSeconds);bytes.Add(bytesKeepAlive[1]);bytes.Add(bytesKeepAlive[0]);// ClientIDbyte[] clienIdBytes = Encoding.ASCII.GetBytes(clientId);bytes.Add((byte)(clienIdBytes.Length / 256));bytes.Add((byte)(clienIdBytes.Length % 256));// 长度占两个字节bytes.AddRange(clienIdBytes);// UserNamebyte[] usernameBytes = Encoding.ASCII.GetBytes(username);bytes.Add((byte)(usernameBytes.Length / 256));bytes.Add((byte)(usernameBytes.Length % 256));// 长度占两个字节bytes.AddRange(usernameBytes);// Passwordbyte[] passwordBytes = Encoding.ASCII.GetBytes(password);bytes.Add((byte)(passwordBytes.Length / 256));bytes.Add((byte)(passwordBytes.Length % 256));// 长度占两个字节bytes.AddRange(passwordBytes);byte[] bufferLen = new byte[] { (byte)bytes.Count };MemoryStream memoryStream = new MemoryStream();memoryStream.WriteByte(1 << 4);memoryStream.Write(bufferLen, 0, (int)bufferLen.Length);memoryStream.Write(bytes.ToArray(), 0, (int)bytes.Count);byte[] array = memoryStream.ToArray();memoryStream.Close();socketClient.Send(array);}}catch (Exception ex){Console.WriteLine(ex.Message);}Console.ReadLine();
}