草庐IT

C语言实现MQTT协议(一)协议讲解

熠熠L 2023-04-21 原文

MQTT介绍

MQTT是一个客户端服务端架构的发布/订阅模式的消息传输协议。它的设计思想是轻巧、开放、简单、规范,易于实现。这些特点使得它对很多场景来说都是很好的选择,特别是对于受限的环境如机器与机器的通信(M2M)以及物联网环境(IoT)。

特点

开放消息协议,简单易实现

  1. 发布订阅模式,一对多消息发布
  2. 基于TCP/IP网络连接
  3. 1字节固定报头,2字节心跳报文,报文结构紧凑
  4. 消息QoS支持,可靠传输保证

优点

MQTT协议广泛应用于物联网、移动互联网、智能硬件、车联网、电力能源等领域。

  1. 物联网M2M通信,物联网大数据采集
  2. Android消息推送,WEB消息推送
  3. 移动即时消息,例如Facebook Messenger
  4. 智能硬件、智能家具、智能电器
  5. 车联网通信,电动车站桩采集
  6. 智慧城市、远程医疗、远程教育

一些术语的解释

网络连接

MQTT 使用的底层传输协议(TCP)基础设施。

  • 客户端使用它连接服务端。
  • 它提供有序的、可靠的、双向字节流传输。

应用消息

MQTT 协议通过网络传输应用数据。应用消息通过 MQTT 传输时,它们有关联的服务质量和主题。

客户端

使用 MQTT 的程序或设备。客户端总是通过网络连接到服务端。它可以

  • 发布应用消息给其它相关的客户端。.
  • 订阅以请求接受相关的应用消息
  • 取消订阅以移除接受应用消息的请求。
  • 从服务端断开连接。

服务端

一个程序或设备,作为发送消息的客户端和请求订阅的客户端之间的中介。服务端

  • 接受来自客户端的网络连接
  • 接受客户端发布的应用消息
  • 处理客户端的订阅和取消订阅请求。
  • 转发应用消息给符合条件的客户端订阅。

订阅

订阅包含一个主题过滤器(Topic Filter)和一个最大的服务质量(QoS)等级。订阅与单个会话(Session)关联。会话可以包含多于一个的订阅。会话的每个订阅都有一个不同的主题过滤器。

主题名

附加在应用消息上的一个标签,服务端已知且与订阅匹配。服务端发送应用消息的一个副本给每一个匹配的客户端订阅。

会话

客户端和服务端之间的状态交互。一些会话持续时长与网络连接一样,另一些可以在客户端和服务端的多个连续网络连接间扩展。

MQTT 控制报文格式

MQTT 控制报文的结构

Fixed header 固定报头,所有控制报文都包含
Variable header 可变报头,部分控制报文包含
Payload 有效载荷,部分控制报文包含

固定报头

固定报头的格式

控制报文的类型

其中控制报文的类型有


比较重要并且常使用报文类型的有CONNECT,CONNACK,PUBLISH,SUBSCRIBE,SUBACK,PINGREQ,PINGRESP,DISCONNECT。
用于指定控制报文类型的标志位在这里不介绍,在之后介绍每个报文的再说明。

剩余长度

剩余长度(Remaining Length)表示当前报文剩余部分的字节数,包括可变报头和负载的数据。剩余长度不包括用于编码剩余长度字段本身的字节数。也就是剩余长度 = 可变报头 + 有效载荷

剩余长度的编码方式:
剩余长度字段使用一个变长度编码方案,对小于 128 的值它使用单字节编码。更大的值按下面的方式处理。低 7 位有效位用于编码数据,最高有效位用于指示是否有更多的字节。因此每个字节可以编码 128 个数值和一个延续位(continuation bit)。剩余长度字段最大 4 个字节。
例如,十进制数 64 会被编码为一个字节,数值是 64,十六进制表示为 0x40,。十进制数字
321(=65+2*128)被编码为两个字节,最低有效位在前。第一个字节是 65+128=193。注意最高位为
1 表示后面至少还有一个字节。第二个字节是 2。

剩余长度的范围:
编码伪代码如下:

相应的解码方式如下:

接下里的可变报头和有效载荷将在各个报文里说明。

剩余长度编码解码的代码实现

编码剩余长度

/** \brief  编码剩余长度
 *
 * \param   X 剩余长度
 * \return  无
 *
 */
void codeRemainLength(unsigned int X)
{
    unsigned encodedByte = 0;

    //编码
    do
    {
        encodedByte = X %128;
        X = X / 128;
        if(X > 0)
        {
            encodedByte = encodedByte | 128;
        }
        //输出已编码的剩余长度
        printf("%02X ", encodedByte);
    }while(X > 0);
}

解码剩余长度

/** \brief  解码剩余长度
 *
 * \param   data 指向已编码的剩余长度数组的首个元素的指针
 * \return  无
 *
 */
void decodeRemainLength(const char *data)
{
    unsigned int multiplier = 1;
    unsigned int value = 0;
    unsigned char encodedByte = 0;
    do {
        encodedByte = *data++;
        value += (encodedByte & 127) * multiplier;
        multiplier *= 128;
        if (multiplier > 128 * 128 * 128) {
            // throw Error(Malformed Remaining Length)
            // error 出错
            return;
        }
    }
    while ((encodedByte & 128) != 0);
	//输出已解码的剩余长度
    printf("%u", value);
}

控制报文

这里我只挑选一些重要常用的报文并结合实例讲解。

CONNECT连接服务端

客户端到服务端的网络连接建立后,客户端发送给服务端的第一个报文必须是 CONNECT 报文

固定报头


第一个字节毫无疑问是0x10;剩余长度 = 可变报头 + 有效载荷,所以剩余长度得之后再计算,并且剩余长度最大占用4个字节,所以我们先留着4个字节的位置出来。

byte1byte2byte3byte4byte5byte6byte7byte8
10???????

注意一下上面表格的数据都是16进制的数据。

可变报头

CONNECT 报文的可变报头按下列次序包含四个字段:协议名(Protocol Name),协议级别(Protocol Level),连接标志(Connect Flags)和保持连接(Keep Alive)。

协议名字节构成


协议名的数据都是固定的,直接转换成16进制的数据填入。

byte1byte2byte3byte4byte5byte6byte7byte8
10????00044D(‘M’)
51(‘Q’)54(‘T’)54(‘T’)?????

协议级别字节构成

byte1byte2byte3byte4byte5byte6byte7byte8
10????00044D(‘M’)
51(‘Q’)54(‘T’)54(‘T’)04????

连接标志

这里需要根据需要来将相应的位设置为1,具体位的含义请参考MQTT协议,一般用到用户名和密码,所以需要把第6位和第7位设置1,其他位根据需要设置,这里我设置为0xC2。

byte1byte2byte3byte4byte5byte6byte7byte8
10????00044D(‘M’)
51(‘Q’)54(‘T’)54(‘T’)04C2???

保持连接时间

这里我设置为300秒,转换为16进制为01 2C,注意高字节在前。

byte1byte2byte3byte4byte5byte6byte7byte8
10????00044D(‘M’)
51(‘Q’)54(‘T’)54(‘T’)04C2012C?

有效载荷

CONNECT 报文的有效载荷(payload)包含一个或多个以长度为前缀的字段,可变报头中的标志决定是否包含这些字段。如果包含的话,必须按这个顺序出现:客户端标识符,遗嘱主题,遗嘱消息,用户名,密码。

字段的格式

在上面连接标志中我仅使用到了用户名和密码,所以我们需要给出客户端标识、用户名和密码。

假如客户端标识是"Client1"则转换为16进制为43 6C 69 65 6E 74 31,数据长度为7,转换为16进制为 00 07(注意,数据长度占用2个字节,高字节在前)。

byte1byte2byte3byte4byte5byte6byte7byte8
10????00044D(‘M’)
51(‘Q’)54(‘T’)54(‘T’)04C2012C00
07436C69656E7431

用户名和密码字段同理,就不说了。

剩余长度的计算

最后计算剩余长度,剩余长度 = 可变报头 + 有效载荷,故剩余长度为19个字节,再进行编码最后为0x13。
故最后得到的CONNECT报文为:

byte1byte2byte3byte4byte5byte6byte7byte8
101300044D(‘M’)51(‘Q’)54(‘T’)54(‘T’)
04C2012C0007436C
69656E7431

CONNACK确认连接请求

CONNACK确认连接请求是服务端发给客户端的报文。

固定报头


第一个字节是0x20,剩余长度之后再计算。

byte1byte2byte3byte4byte5byte6byte7byte8
20???????

可变报头

连接确认标志具体含义可以看MQTT协议。

连接返回码的数值请看下图,可以发现如果服务端发送客户端是0x00,则代表连接已经成功,其他数值则代表出现错误。

我们假如服务端给我们发送过来是连接已被服务端接受,则有

byte1byte2byte3byte4byte5byte6byte7byte8
20????0000?

有效载荷

该报文没有有效载荷。

剩余长度的计算

该报文很简单,剩余长度等于2,转换为16进制为0x02。
最后得到就是下表。

byte1byte2byte3byte4byte5byte6byte7byte8
20020000

PUBLISH发布消息

客户端向服务端发布消息,或者服务端向客户端发布消息。

这里我以客户端向服务端发送消息为例。

固定报头


第一个字节为0x30,后面的标志位我全设置为0,同样的这些位得根据需求才能确定下来是设置为0还是1。

byte1byte2byte3byte4byte5byte6byte7byte8
30???????

可变报头

可变报头按顺序包含主题名和报文标识符。
只有当 QoS 等级是 1 或 2 时,报文标识符(Packet Identifier)字段才能出现在 PUBLISH 报文中。

假设主题为ABCQoS 等级为0,则主题名长度为3,转换为16进制为 00 03,有

byte1byte2byte3byte4byte5byte6byte7byte8
30????000341(A)
42(B)43(C)??????

有效载荷

有效载荷包含将被发布的应用消息。数据的内容和格式是应用特定的。
假如我们要发送的数据是Hello,转换之后为48 65 6C 6C 6F ,有

byte1byte2byte3byte4byte5byte6byte7byte8
30????000341(A)
42(B)43(C)48(H)65(e)6C(l)6C(l)6F(o)?

剩余长度的计算

剩余长度为10,即0xA0。

byte1byte2byte3byte4byte5byte6byte7byte8
30A0000341(A)42(B)43(C)48(H)
65(e)6C(l)6C(l)6F(o)

SUBSCRIBE订阅主题

客户端向服务端发送 SUBSCRIBE 报文用于创建一个或多个订阅。每个订阅注册客户端关心的一个或多个主题。

固定报头


注意下,第一个字节是0x82

byte1byte2byte3byte4byte5byte6byte7byte8
82???????

可变报头

可变报头包含客户端标识符。

假设我们的客户端标识符是12 34

byte1byte2byte3byte4byte5byte6byte7byte8
82????1234?

有效载荷

SUBSCRIBE 报文的有效载荷必须包含至少一对主题过滤器 和 QoS 等级字段组合。

假设主题名是abc则转换后为61 62 63,长度为00 03,QoS 服务等级为00

byte1byte2byte3byte4byte5byte6byte7byte8
82????123400
0361626300

剩余长度的计算

byte1byte2byte3byte4byte5byte6byte7byte8
8208123400036162
6300

其他的报文就不介绍。

C语言实现MQTT协议(一)协议讲解

C语言实现MQTT协议(二)头文件介绍

C语言实现MQTT协议(三)源代码介绍及连接阿里云

源代码下载链接

有关C语言实现MQTT协议(一)协议讲解的更多相关文章

  1. ruby - 如何将脚本文件的末尾读取为数据文件(Perl 或任何其他语言) - 2

    我正在寻找执行以下操作的正确语法(在Perl、Shell或Ruby中):#variabletoaccessthedatalinesappendedasafileEND_OF_SCRIPT_MARKERrawdatastartshereanditcontinues. 最佳答案 Perl用__DATA__做这个:#!/usr/bin/perlusestrict;usewarnings;while(){print;}__DATA__Texttoprintgoeshere 关于ruby-如何将脚

  2. ruby - 如何根据特征实现 FactoryGirl 的条件行为 - 2

    我有一个用户工厂。我希望默认情况下确认用户。但是鉴于unconfirmed特征,我不希望它们被确认。虽然我有一个基于实现细节而不是抽象的工作实现,但我想知道如何正确地做到这一点。factory:userdoafter(:create)do|user,evaluator|#unwantedimplementationdetailshereunlessFactoryGirl.factories[:user].defined_traits.map(&:name).include?(:unconfirmed)user.confirm!endendtrait:unconfirmeddoenden

  3. ruby - 寻找通过阅读代码确定编程语言的ruby gem? - 2

    几个月前,我读了一篇关于ruby​​gem的博客文章,它可以通过阅读代码本身来确定编程语言。对于我的生活,我不记得博客或gem的名称。谷歌搜索“ruby编程语言猜测”及其变体也无济于事。有人碰巧知道相关gem的名称吗? 最佳答案 是这个吗:http://github.com/chrislo/sourceclassifier/tree/master 关于ruby-寻找通过阅读代码确定编程语言的rubygem?,我们在StackOverflow上找到一个类似的问题:

  4. 世界前沿3D开发引擎HOOPS全面讲解——集3D数据读取、3D图形渲染、3D数据发布于一体的全新3D应用开发工具 - 2

    无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD

  5. 华为OD机试用Python实现 -【明明的随机数】 2023Q1A - 2

    华为OD机试题本篇题目:明明的随机数题目输入描述输出描述:示例1输入输出说明代码编写思路最近更新的博客华为od2023|什么是华为od,od薪资待遇,od机试题清单华为OD机试真题大全,用Python解华为机试题|机试宝典【华为OD机试】全流程解析+经验分享,题型分享,防作弊指南华为o

  6. Unity 热更新技术 | (三) Lua语言基本介绍及下载安装 - 2

    ?博客主页:https://xiaoy.blog.csdn.net?本文由呆呆敲代码的小Y原创,首发于CSDN??学习专栏推荐:Unity系统学习专栏?游戏制作专栏推荐:游戏制作?Unity实战100例专栏推荐:Unity实战100例教程?欢迎点赞?收藏⭐留言?如有错误敬请指正!?未来很长,值得我们全力奔赴更美好的生活✨------------------❤️分割线❤️-------------------------

  7. 7个大一C语言必学的程序 / C语言经典代码大全 - 2

    嗨~大家好,这里是可莉!今天给大家带来的是7个C语言的经典基础代码~那一起往下看下去把【程序一】打印100到200之间的素数#includeintmain(){ inti; for(i=100;i 【程序二】输出乘法口诀表#includeintmain(){inti;for(i=1;i 【程序三】判断1000年---2000年之间的闰年#includeintmain(){intyear;for(year=1000;year 【程序四】给定两个整形变量的值,将两个值的内容进行交换。这里提供两种方法来进行交换,第一种为创建临时变量来进行交换,第二种是不创建临时变量而直接进行交换。1.创建临时变量来

  8. 基于C#实现简易绘图工具【100010177】 - 2

    C#实现简易绘图工具一.引言实验目的:通过制作窗体应用程序(C#画图软件),熟悉基本的窗体设计过程以及控件设计,事件处理等,熟悉使用C#的winform窗体进行绘图的基本步骤,对于面向对象编程有更加深刻的体会.Tutorial任务设计一个具有基本功能的画图软件**·包括简单的新建文件,保存,重新绘图等功能**·实现一些基本图形的绘制,包括铅笔和基本形状等,学习橡皮工具的创建**·设计一个合理舒适的UI界面**注明:你可能需要先了解一些关于winform窗体应用程序绘图的基本知识,以及关于GDI+类和结构的知识二.实验环境Windows系统下的visualstudio2017C#窗体应用程序三.

  9. CAN协议的学习与理解 - 2

    最近在学习CAN,记录一下,也供大家参考交流。推荐几个我觉得很好的CAN学习,本文也是在看了他们的好文之后做的笔记首先是瑞萨的CAN入门,真的通透;秀!靠这篇我竟然2天理解了CAN协议!实战STM32F4CAN!原文链接:https://blog.csdn.net/XiaoXiaoPengBo/article/details/116206252CAN详解(小白教程)原文链接:https://blog.csdn.net/xwwwj/article/details/105372234一篇易懂的CAN通讯协议指南1一篇易懂的CAN通讯协议指南1-知乎(zhihu.com)视频推荐CAN总线个人知识总

  10. MIMO-OFDM无线通信技术及MATLAB实现(1)无线信道:传播和衰落 - 2

     MIMO技术的优缺点优点通过下面三个增益来总体概括:阵列增益。阵列增益是指由于接收机通过对接收信号的相干合并而活得的平均SNR的提高。在发射机不知道信道信息的情况下,MIMO系统可以获得的阵列增益与接收天线数成正比复用增益。在采用空间复用方案的MIMO系统中,可以获得复用增益,即信道容量成倍增加。信道容量的增加与min(Nt,Nr)成正比分集增益。在采用空间分集方案的MIMO系统中,可以获得分集增益,即可靠性性能的改善。分集增益用独立衰落支路数来描述,即分集指数。在使用了空时编码的MIMO系统中,由于接收天线或发射天线之间的间距较远,可认为它们各自的大尺度衰落是相互独立的,因此分布式MIMO

随机推荐