MQTT协议笔记之连接和心跳
前言
本篇会把连接(CONNECT)、心跳(PINGREQ/PINGRESP)、确认(CONNACK)、断开连接(DISCONNECT)和在一起。
CONNECT
像前面所说,MQTT有关字符串部分采用的修改版的UTF-8编码,CONNECT可变头部中协议名称、消息体都是采用修改版的UTF-8编码。前面基本上可变头部内容不多,下面是一个较为完整的CONNECT消息结构:
| Description | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | ||
|---|---|---|---|---|---|---|---|---|---|---|
| Fixed header/固定头部 | ||||||||||
| Message Type(1) | DUP flag | QoS level | RETAIN | |||||||
|
byte 1
|
0 | 0 | 0 | 1 | x | x | x | x | ||
| byte 2 | Remaining Length | |||||||||
| Variable header/可变头部 | ||||||||||
| Protocol Name | ||||||||||
| byte 1 | Length MSB (0) | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | |
| byte 2 | Length LSB (6) | 0 | 0 | 0 | 0 | 0 | 1 | 1 | 0 | |
| byte 3 | 'M' | 0 | 1 | 0 | 0 | 1 | 1 | 0 | 1 | |
| byte 4 | 'Q' | 0 | 1 | 0 | 1 | 0 | 0 | 0 | 1 | |
| byte 5 | 'I' | 0 | 1 | 0 | 0 | 1 | 0 | 0 | 1 | |
| byte 6 | 's' | 0 | 1 | 1 | 1 | 0 | 0 | 1 | 1 | |
| byte 7 | 'd' | 0 | 1 | 1 | 0 | 0 | 1 | 0 | 0 | |
| byte 8 | 'p' | 0 | 1 | 1 | 1 | 0 | 0 | 0 | 0 | |
| Protocol Version Number | ||||||||||
| byte 9 | Version (3) | 0 | 0 | 0 | 0 | 0 | 0 | 1 | 1 | |
| Connect Flags | ||||||||||
| User Name Flag | Password Flag | Will Retain | Will QoS | Will Flag | Clean Session | Reserved | ||||
|
byte 10
|
1 | 1 | 0 | 0 | 1 | 1 | 1 | x | ||
| Keep Alive timer | ||||||||||
| byte 11 | Keep Alive MSB (0) | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | |
| byte 12 | Keep Alive LSB (10) | 0 | 0 | 0 | 0 | 1 | 0 | 1 | 0 | |
| Payload/消息体 | ||||||||||
|
Client Identifier(客户端ID) 1-23个字符长度,客户端到服务器的全局唯一标志,如果客户端ID超出23个字符长度,服务器需要返回码为2,标识符被拒绝响应的CONNACK消息。 |
||||||||||
|
Will Topic Will Flag值为1,这里便是Will Topic的内容。QoS级别通过Will QoS字段定义,RETAIN值通过Will RETAIN标识,都定义在可变头里面。 |
||||||||||
|
Will Message Will Flag若设为1,这里便是Will Message定义消息的内容,对应的主题为Will Topic。如果客户端意外的断开触发服务器PUBLISH此消息。 |
||||||||||
|
User Name 如果设置User Name标识,可以在此读取用户名称。一般可用于身份验证。协议建议用户名为不多于12个字符,不是必须。 |
||||||||||
|
Password 如果设置Password标识,便可读取用户密码。建议密码为12个字符或者更少,但不是必须。 |
||||||||||
可变头部
协议名称和协议版本都是固定的。
连接标志(Connect Flags)
一个字节表示,除了第1位是保留未使用,其它7位都具有不同含义。
业务上很重要,对消息总体流程影响很大,需要牢记。
Clean Session
0,表示如果订阅的客户机断线了,要保存为其要推送的消息(QoS为1和QoS为2),若其重新连接时,需将这些消息推送(若客户端长时间不连接,需要设置一个过期值)。 1,断线服务器即清理相关信息,重新连接上来之后,会再次订阅。
Will Flag
定义了客户端(没有主动发送DISCONNECT消息)出现网络异常导致连接中断的情况下,服务器需要做的一些措施。
简而言之,就是客户端预先定义好,在自己异常断开的情况下,所留下的最后遗愿(Last Will),也称之为遗嘱(Testament)。 这个遗嘱就是一个由客户端预先定义好的主题和对应消息,附加在CONNECT的可变头部中,在客户端连接出现异常的情况下,由服务器主动发布此消息。
只有在Will Flag位为1时,Will Qos和Will Retain才会被读取,此时消息体payload中要出现Will Topic和Will Message具体内容,否则,Will QoS和Will Retain值会被忽略掉。
Will Qos
两位表示,和PUBLISH消息固定头部的QoS level含义一样。这里先掠过,到PUBLISH消息再回过头来看看,会更明白些。
若标识了Will Flag值为1,那么Will QoS就会生效,否则会被忽略掉。
Will RETAIN
如果设置Will Flag,Will Retain标志就是有效的,否则它将被忽略。
当客户端意外断开服务器发布其Will Message之后,服务器是否应该继续保存。这个属性和PUBLISH固定头部的RETAIN标志含义一样,这里先掠过。
User name 和 password Flag:
用于授权,两者要么为0要么为1,否则都是无效。都为0,表示客户端可自由连接/订阅,都为1,表示连接/订阅需要授权。
Payload/消息体
消息体定义的消息顺序(如上表所示),约定俗成,不得更改,否则将可能引起混乱。
若Will Flag值为0,那么在payload中,Client Identifer后面就不会存在Will Topic和Will Message内容。
若User Name和Password都为0,意味着Payload/消息体中,找不到User Name和password的值,就算有,也是无效。标志决定着是否读取与否。
心跳时间(Keep Alive timer)
以秒为单位,定义服务器端从客户端接收消息的最大时间间隔。一般应用服务会在业务层次检测客户端网络是否连接,不是TCP/IP协议层面的心跳机制(比如开启SOCKET的SO_KEEPALIVE选项)。 一般来讲,在一个心跳间隔内,客户端发送一个PINGREQ消息到服务器,服务器返回PINGRESP消息,完成一次心跳交互,继而等待下一轮。若客户端没有收到心跳反馈,会关闭掉TCP/IP端口连接,离线。 16位两个字节,可看做一个无符号的short类型值。最大值,2^16-1 = 65535秒 = 18小时。最小值可以为0,表示客户端不断开。一般设为几分钟,比如微信心跳周期为300秒。
Will Message编码
Will Message在CONNECT Payload/息体中,使用UTF-8编码。假设内容为“abcd”,大概如下:
| Description | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
|---|---|---|---|---|---|---|---|---|---|
| byte 1 | Length MSB (0) | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| byte 2 | Length LSB (4) | 0 | 0 | 0 | 0 | 0 | 1 | 0 | 0 |
| byte 3 | 'a' (0x61) | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 1 |
| byte 4 | 'b' (0x62) | 0 | 1 | 1 | 0 | 0 | 0 | 1 | 0 |
| byte 5 | 'c' (0x63) | 0 | 1 | 1 | 0 | 0 | 0 | 1 | 1 |
| byte 6 | 'd' (0x64) | 0 | 1 | 1 | 0 | 0 | 1 | 0 | 0 |
有一点需要记住,PUBLISH的Payload/消息体中以二进制编码保存。
某刻客户端异常关闭触发服务器会PUBLISH此消息。那么服务器会直接把byte3-byte6之间字符取出,保存为二进制,附加到PUBLISH消息体中,大概存储如下:
| Description | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
|---|---|---|---|---|---|---|---|---|---|
| byte 1 | 'a' (0x61) | 0 | 1 | 1 | 0 | 0 | 0 | 0 | 1 |
| byte 2 | 'b' (0x62) | 0 | 1 | 1 | 0 | 0 | 0 | 1 | 0 |
| byte 3 | 'c' (0x63) | 0 | 1 | 1 | 0 | 0 | 0 | 1 | 1 |
| byte 4 | 'd' (0x64) | 0 | 1 | 1 | 0 | 0 | 1 | 0 | 0 |
另外,MQTT 3.1协议对Will message的说明很容易引起误解,3.1.1草案已经得到修正。
相关说明:
http://mqtt.org/wiki/doku.php/willmessageutf8_support
https://tools.oasis-open.org/issues/browse/MQTT-2
连接异常中断通知机制
CONNECT消息一旦设置在可变头部设置了Will flag标记,那就启用了Last-Will-And-Testament特性,此特性很赞。
一旦客户端出现异常中断,便会触发服务器发布Will Message消息到Will Topic主题上去,通知Will Topic订阅者,对方因异常退出。
接收CONNECT后的响应动作
接收到CONNECT消息之后,服务器应该返回一个CONNACK消息作为响应:
- 若客户端绕过CONNECT消息直接发送其它类型消息,服务器应关闭此非法连接 若客户端发送CONNECT之后未收到CONNACT,需要关闭当前连接,然后重新连接
- 相同Client ID客户端已连接到服务器,先前客户端必须断开连接后,服务器才能完成新的客户端CONNECT连接 客户端发送无效非法CONNECT消息,服务器需要关闭
CONNACK
一个完整的CONNACK消息大致如下:
| Description | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
|---|---|---|---|---|---|---|---|---|---|
| Fixed header/固定头部 | |||||||||
| byte 1 | Message type (2) | DUP flag | QoS flags | RETAIN | |||||
| 0 | 0 | 1 | 0 | x | x | x | x | ||
| byte 2 | Remaining Length (2) | ||||||||
| 0 | 0 | 0 | 0 | 0 | 0 | 1 | 0 | ||
| Variable header/可变头部 | |||||||||
| Topic Name Compression Response | |||||||||
| byte 1 | Reserved values. Not used. | x | x | x | x | x | x | x | x |
| Connect Return Code | |||||||||
| byte 2 | Return Code | ||||||||
可变头部第一个字节为保留,无甚用处。第二个字节为连接握手返回码:
| 返回值 | 16进制 | 含义 |
| 0 | 0x00 | Connection Accepted |
| 1 | 0x01 | Connection Refused: unacceptable protocol version |
| 2 | 0x02 | Connection Refused: identifier rejected |
| 3 | 0x03 | Connection Refused: server unavailable |
| 4 | 0x04 | Connection Refused: bad user name or password |
| 5 | 0x05 | Connection Refused: not authorized |
| 6-255 | Reserved for future use |
只有0-5目前被使用到,其他值有待日后使用。一般返回值为0x00,表示连接建立。非法的请求,需要返回相应的数值。
从上面看出,一个CONNACT,四个字节表示。一个正常的CONNACT消息实际内容可能如下: 0x20 0x02 0x00 0x00
若是在私有协议中,两个字节就足够了。
很多时候,客户端和服务器端在没有消息传递时,会一直保持着连接。虽然不能依靠TCP心跳机制(比如SO_KEEPALIVE选项),业务层面定义心跳机制,会让连接状态检测、控制更为直观。
PINGREQ
由客户端发送到服务器端,证明自己还在一直连接着呢。两个字节,固定值。
| Description | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
|---|---|---|---|---|---|---|---|---|---|
| Fixed header/固定头部 | |||||||||
| byte 1 | Message type (12) | DUP flag | QoS flags | RETAIN | |||||
| 1 | 1 | 0 | 0 | x | x | x | x | ||
| byte 2 | Remaining Length (0) | ||||||||
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ||
客户端会在一个心跳周期内发送一条PINGREQ消息到服务器端。
心跳频率在CONNECT可变头部“Keep Alive timer”中定义时间,单位为秒,无符号16位short表示。
PINGRESP
服务器收到PINGREQ请求之后,会立即响应一个两个字节固定格式的PINGRESP消息。
| Description | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
|---|---|---|---|---|---|---|---|---|---|
| Fixed header/固定头部 | |||||||||
| byte 1 | Message type (13) | DUP flag | QoS flags | RETAIN | |||||
| 1 | 1 | 0 | 1 | x | x | x | x | ||
| byte 2 | Remaining Length (0) | ||||||||
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ||
服务器一般若在1.5倍的心跳周期内接收不到客户端发送的PINGREQ,可考虑关闭客户端的连接描述符。此时的关闭连接的行为和接收到客户端发送DISCONNECT消息的处理行为一致,但对客户端的订阅不会产生影响(不会清除客户端订阅数据),这个需要牢记。
若客户端发送PINGREQ之后的一个心跳周期内接收不到PINGRESP消息,可考虑关闭TCP/IP套接字连接。
DISCONNECT
客户端主动发送到服务器端,表明即将关闭TCP/IP连接。此时要求服务器要完整、干净的进行断开处理,不能仅仅类似于关闭连接描述符类似草草处理之。 需要两个字节,值固定:
| Description | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | |
|---|---|---|---|---|---|---|---|---|---|
| Fixed header/固定头部 | |||||||||
| byte 1 | Message type (14) | DUP flag | QoS flags | RETAIN | |||||
| 1 | 1 | 1 | 0 | x | x | x | x | ||
| byte 2 | Remaining Length (0) | ||||||||
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | ||
服务器要根据先前此客户端在发送CONNECT消息可变头部Connect flag中的“Clean session flag”所设置值,再次复习一下:
值为0,服务器必须在客户端断开之后继续存储/保持客户端的订阅状态。这些状态包括:
- 存储订阅的消息QoS1和QoS2消息
- 正在发送消息期间连接丢失导致发送失败的消息
- 以便当客户端重新连接时以上消息可以被重新传递。
值为1,服务器需要立刻清理连接状态数据。
有一点需要牢记,服务器在接收到客户端发送的DISCONNECT消息之后,需要主动关闭TCP/IP连接。
原文 http://www.blogjava.net/yongboy/archive/2014/02/09/409630.html
MQTT协议笔记之连接和心跳的更多相关文章
- MQTT(三)-----连接与心跳
MQTT协议笔记之连接和心跳 - 推酷 http://www.tuicool.com/articles/AFvmee 互联网推送服务原理:长连接+心跳机制(MQTT协议) - clh604的专栏 - ...
- MQTT协议笔记之订阅
前言 记忆不太好的时候,只能翻看以前的文章/笔记重新温习一遍,但找不到MQTT协议有关订阅部分的描述,好不容易从Evernote中找到贴出来,这样整个MQTT协议笔记,就比较齐全了. SUBSCRIB ...
- MQTT协议笔记之头部信息
前言 记忆不太好的时候,只能翻看以前的文章/笔记重新温习一遍,但找不到MQTT协议有关订阅部分的描述,好不容易从Evernote中找到贴出来,这样整个MQTT协议笔记,就比较齐全了. SUBSCRIB ...
- MQTT协议笔记之发布流程
MQTT协议笔记之发布流程 前言 这次要讲到客户端/服务器的发布消息行为,与PUBLISH相关的消息类型,会在这里看到. PUBLISH 客户端发布消息经由服务器分发到所有对应的订阅者那里.一个订阅者 ...
- MQTT协议笔记之消息流
前言 前面的笔记已把所有消息类型都过了一遍,这里从消息流的角度尝试解读一下. 网络故障 在任何网络环境下,都会出现一方连接失败,比如离开公司大门那一刻没有了WIFI信号.但持续连接的另一端-服务器可能 ...
- MQTT协议笔记之mqtt.io项目HTTP协议支持
前言 MQTT协议诞生之初,就未曾考虑通过HTTP传输.这也正常,网络受限.不稳定网络不太适合HTTP(2G/3G网络大家使用WAP不也OK嘛).在网络较为充裕的桌面端而言,虽纯文本对比二进制而言没多 ...
- MQTT协议笔记之mqtt.io项目Websocket协议支持
前言 MQTT协议专注于网络.资源受限环境,建立之初不曾考虑WEB环境,倒也正常.虽然如此,但不代表它不适合HTML5环境. HTML5 Websocket是建立在TCP基础上的双通道通信,和TCP通 ...
- MQTT协议笔记之mqtt.io项目TCP协议支持
前言 MQTT定义了物联网传输协议,其标准倾向于原始TCP实现.构建于TCP的上层协议堆栈,诸如HTTP等,在空间上多了一些处理路径,稍微耗费了CPU和内存,虽看似微乎其微,但对很多处理能力不足的嵌入 ...
- HTTP权威协议笔记-4.连接管理
4.1 TCP连接 TCP为HTTP提供了一条可靠的比特传输管道,按顺序正确的传输,步骤如下: 浏览器解析主机名. 查询这个主机名的IP地址(DNS) 获得端口号. 浏览器对服务器该端口号发起连接. ...
随机推荐
- fullcalendar案例一<原>
fullcalendar是个很强大的日历控件,可以用它进行排班.排会议.拍任务,很直观,用户体验良好. 看下效果图: #parse("index/head.vm") <lin ...
- uboot中MAC网络(待续)
start ->start_armboot ->main_loop 实际应用中问题:为什么从nandflash读出的MAC(写到物理phy上)与上层网口地址不同(上层网口采用env的mac ...
- WPF教程五:布局之Canvas面板
Canvas:画布面板 画布,用于完全控制每个元素的精确位置.他是布局控件中最为简单的一种,直接将元素放到指定位置,主要来布置图面.使用Canvas,必须指定一个子元素的位置(相对于画布),否则所有元 ...
- 将BAT文件注册为服务的方法
一.什么是instsrv.exe和srvany.exe instsrv.exe.exe和srvany.exe是Microsoft Windows Resource Kits工具集中 的两个实用工具,这 ...
- 各种api接口
实用号码归属地查询(IP 地址,手机号码): 默认格式: http://api.liqwei.com/location/ (使用来访者的 IP 地址) 指定 IP 地址格式: http://api.l ...
- 关于Cocos2d-x程序运行时候提示关闭程序的框框的解决方法
1.这个情况是资源没有被加载的表现 如果AppDelegate.cpp里面没有文件索引的语句 FileUtils::getInstance()->addSearchPath("res& ...
- e666. 创建缓冲图像
A buffered image is a type of image whose pixels can be modified. For example, you can draw on a buf ...
- 云计算中auto-scaling 最早的来源
什么是弹性?首先,整合计算资源,将计算资源池化,通过虚拟机按需使用计算资源;其次,按量计费,让用户能够根据使用量按月按时甚至按秒来进行付费. 不过,光有了这两条还不够.为什么?我举个例子: 很多做运维 ...
- JavaSE集合(八)之Map
前面给大家介绍了集合家族中的Collection家族,这一篇给大家分享的是集合中的另一个家族就是Map家族.以前的时候学习Map的时候没有很认真的去学习,我觉得很多东西还是不是很清楚. 这次我将总结的 ...
- informatica 中的workflow连接mysql数据配置DSN
先要下载mysqlodbc 一步步安装之后 ,再从管理工具里面找到ODBC,最后选择系统DSN,添加mysql驱动之后,进入添加编辑: 在workflow里面的配置连接字符串就写刚刚配置的连接名称 比 ...