聂永的博客

记录工作/学习的点点滴滴。

MQTT协议笔记之连接和心跳

前言

本篇会把连接(CONNECT)、心跳(PINGREQ/PINGRESP)、确认(CONNACK)、断开连接(DISCONNECT)和在一起。

CONNECT

像前面所说,MQTT有关字符串部分采用的修改版的UTF-8编码,CONNECT可变头部中协议名称、消息体都是采用修改版的UTF-8编码。前面基本上可变头部内容不多,下面是一个较为完整的CONNECT消息结构:

 Description76543210
Fixed header/固定头部

 Message Type(1)DUP flagQoS levelRETAIN
byte 1
 0001xxxx
byte 2Remaining Length
Variable header/可变头部
Protocol Name
byte 1Length MSB (0)00000000
byte 2Length LSB (6)00000110
byte 3'M'01001101
byte 4'Q'01010001
byte 5'I'01001001
byte 6's'01110011
byte 7'd'01100100
byte 8'p'01110000
Protocol Version Number
byte 9Version (3)00000011
Connect Flags

User Name FlagPassword FlagWill RetainWill QoSWill FlagClean SessionReserved
byte 10
1100111x
Keep Alive timer
byte 11Keep Alive MSB (0)00000000
byte 12Keep Alive LSB (10)00001010
Payload/消息体

Client Identifier(客户端ID)

1-23个字符长度,客户端到服务器的全局唯一标志,如果客户端ID超出23个字符长度,服务器需要返回码为2,标识符被拒绝响应的CONNACK消息。
处理QoS级别1和2的消息ID中,可以使用到。
必填项。

Will Topic

Will Flag值为1,这里便是Will Topic的内容。QoS级别通过Will QoS字段定义,RETAIN值通过Will RETAIN标识,都定义在可变头里面。

Will Message

Will Flag若设为1,这里便是Will Message定义消息的内容,对应的主题为Will Topic。如果客户端意外的断开触发服务器PUBLISH此消息。
长度有可能为0。
在CONNECT消息中的Will Message是UTF-8编码的,当被服务器发布时则作为二进制的消息体。

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”,大概如下:

 Description76543210
byte 1Length MSB (0)00000000
byte 2Length LSB (4)00000100
byte 3'a' (0x61)01100001
byte 4'b' (0x62)01100010
byte 5'c' (0x63)01100011
byte 6'd' (0x64)01100100

有一点需要记住,PUBLISH的Payload/消息体中以二进制编码保存。

某刻客户端异常关闭触发服务器会PUBLISH此消息。那么服务器会直接把byte3-byte6之间字符取出,保存为二进制,附加到PUBLISH消息体中,大概存储如下:

 Description76543210
byte 1'a' (0x61)01100001
byte 2'b' (0x62)01100010
byte 3'c' (0x63)01100011
byte 4'd' (0x64)01100100

另外,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消息作为响应:

  1. 若客户端绕过CONNECT消息直接发送其它类型消息,服务器应关闭此非法连接 若客户端发送CONNECT之后未收到CONNACT,需要关闭当前连接,然后重新连接
  2. 相同Client ID客户端已连接到服务器,先前客户端必须断开连接后,服务器才能完成新的客户端CONNECT连接 客户端发送无效非法CONNECT消息,服务器需要关闭

CONNACK

一个完整的CONNACK消息大致如下:

 Description76543210
Fixed header/固定头部
byte 1 Message type (2)DUP flagQoS flagsRETAIN
  0010xxxx
byte 2 Remaining Length (2)
  00000010
Variable header/可变头部
Topic Name Compression Response
byte 1Reserved values. Not used.xxxxxxxx
Connect Return Code
byte 2Return Code        

可变头部第一个字节为保留,无甚用处。第二个字节为连接握手返回码:

返回值16进制含义
00x00Connection Accepted
10x01Connection Refused: unacceptable protocol version
20x02Connection Refused: identifier rejected
30x03Connection Refused: server unavailable
40x04Connection Refused: bad user name or password
50x05Connection Refused: not authorized
6-255 Reserved for future use

只有0-5目前被使用到,其他值有待日后使用。一般返回值为0x00,表示连接建立。非法的请求,需要返回相应的数值。

从上面看出,一个CONNACT,四个字节表示。一个正常的CONNACT消息实际内容可能如下: 0x20 0x02 0x00 0x00

若是在私有协议中,两个字节就足够了。

很多时候,客户端和服务器端在没有消息传递时,会一直保持着连接。虽然不能依靠TCP心跳机制(比如SO_KEEPALIVE选项),业务层面定义心跳机制,会让连接状态检测、控制更为直观。

PINGREQ

由客户端发送到服务器端,证明自己还在一直连接着呢。两个字节,固定值。

 Description76543210
Fixed header/固定头部
byte 1 Message type (12)DUP flagQoS flagsRETAIN
  1100xxxx
byte 2 Remaining Length (0)
  00000000

客户端会在一个心跳周期内发送一条PINGREQ消息到服务器端。

心跳频率在CONNECT可变头部“Keep Alive timer”中定义时间,单位为秒,无符号16位short表示。

PINGRESP

服务器收到PINGREQ请求之后,会立即响应一个两个字节固定格式的PINGRESP消息。

 Description76543210
Fixed header/固定头部
byte 1 Message type (13)DUP flagQoS flagsRETAIN
  1101xxxx
byte 2 Remaining Length (0)
  00000000

服务器一般若在1.5倍的心跳周期内接收不到客户端发送的PINGREQ,可考虑关闭客户端的连接描述符。此时的关闭连接的行为和接收到客户端发送DISCONNECT消息的处理行为一致,但对客户端的订阅不会产生影响(不会清除客户端订阅数据),这个需要牢记。

若客户端发送PINGREQ之后的一个心跳周期内接收不到PINGRESP消息,可考虑关闭TCP/IP套接字连接。

DISCONNECT

客户端主动发送到服务器端,表明即将关闭TCP/IP连接。此时要求服务器要完整、干净的进行断开处理,不能仅仅类似于关闭连接描述符类似草草处理之。 需要两个字节,值固定:

 Description76543210
Fixed header/固定头部
byte 1 Message type (14)DUP flagQoS flagsRETAIN
  1110xxxx
byte 2 Remaining Length (0)
  00000000

服务器要根据先前此客户端在发送CONNECT消息可变头部Connect flag中的“Clean session flag”所设置值,再次复习一下:

  1. 值为0,服务器必须在客户端断开之后继续存储/保持客户端的订阅状态。这些状态包括:

    • 存储订阅的消息QoS1和QoS2消息
    • 正在发送消息期间连接丢失导致发送失败的消息
    • 以便当客户端重新连接时以上消息可以被重新传递。
  2. 值为1,服务器需要立刻清理连接状态数据。

有一点需要牢记,服务器在接收到客户端发送的DISCONNECT消息之后,需要主动关闭TCP/IP连接。

posted on 2014-02-09 13:41 nieyong 阅读(60144) 评论(7)  编辑  收藏 所属分类: MQTT

评论

# re: MQTT协议笔记之连接和心跳 2014-02-10 15:08 电驴资源

MARK,学习下  回复  更多评论   

# re: MQTT协议笔记之连接和心跳 2014-10-22 13:49 来雨

学习了,有系统学习的文章或者书么  回复  更多评论   

# re: MQTT协议笔记之连接和心跳 2015-03-07 23:20 zer0

最近在实现MQTT协议的3.1.1版本,遇到一个问题,想请教一下。
问题是,3.1.1版本的协议P29页说如果服务器拒绝ClientID,它返回的CONNACK包的return code必须是0x02。
但是我找了半天,都没找到协议里说 哪里应该拒绝clientID。

我想问的是,什么样的情况下服务器需要拒绝clientID。
  回复  更多评论   

# re: MQTT协议笔记之连接和心跳 2015-03-09 09:43 nieyong

@zer0
clientID可以用于授权,若授权不通过(比如检测是否他人没有按照已定义规则生成的的恶意ClientId),可以直接拒绝之。比如在企业/公司内部,用于PUBLISH的MQTT服务器端,可以选择对ClientId进行指定,若非指定值,则属于恶意的ClientId。  回复  更多评论   

# re: MQTT协议笔记之连接和心跳 2015-04-23 15:50 h2appy

不是playload,而是payload  回复  更多评论   

# re: MQTT协议笔记之连接和心跳 2015-04-23 18:01 nieyong

@h2appy
十分感谢,已做修改。希望您以后给我多提改进意见 :))  回复  更多评论   

# re: MQTT协议笔记之连接和心跳 2016-01-06 02:23 阿旭

@nieyong
clientId 是否可以理解为用户id这种东西, 我想把用户id放这儿标识这个客户端是否可行

username, password.
我用netty+mqtt只是实现了推送的功能, 登录会以http请求另外的服务器. 这时候我就用token代替用户名密码作为权限检查, 我token可以放在username这个字段吗?

请指教  回复  更多评论   


只有注册用户登录后才能发表评论。


网站导航:
博客园   IT新闻   Chat2DB   C++博客   博问  
 

公告

所有文章皆为原创,若转载请标明出处,谢谢~

新浪微博,欢迎关注:

导航

<2015年3月>
22232425262728
1234567
891011121314
15161718192021
22232425262728
2930311234

统计

常用链接

留言簿(58)

随笔分类(130)

随笔档案(151)

个人收藏

最新随笔

搜索

最新评论

阅读排行榜

评论排行榜