IoT 设备发送 MQTT 请求上云的曲折经历
为了搞清楚 IoT 设备从传感器获取到数据,通过网络发送到云端的整个网络过程,我们先来看一下网络分层模型:
上图中例举了网络分层中最常见的协议:
应用层:应用程序负责将数据以相应规则(协议)进行包装,发给传输层
- MQTT:消息队列遥测传输
- CoAP:受限应用协议
- HTTP:超文本传输协议
- FTP:文件传输协议
- SMTP:简单邮件传送协议
- MQTT:消息队列遥测传输
传输层:负责将应用层传过来的数据进行分组,为确保终端接收数据的顺序和完整性,会对每个分组进行标记,交给网络层
- TCP:传输控制协议
- UDP:用户数据协议
网络层:负责将传输层发来的数据分组发送到目标终端
- IP:网际协议
- ICMP:互联网控制报文协议
- IGMP:互联网组管理协议
链路层:为网络层发送和接收数据单元
- ARP:地址解析协议
- RARP:逆地址解析协议
封装 和 分用
数据在经过每一层的时候都要被对应的协议包装,即封装 (Encapsulation),到达终端的时候,要一层一层的解包,即分用(Demultiplexing)。
发送时,设备采集的业务数据被应用程序封装为 MQTT 报文,每一层会将上层传过来的报文作为本层的数据块,并添加自己的首部,其中包含了协议标识,这一整体作为本层报文向下传递。
接收时,数据自下而上流动,经过每一层时被去掉报文首部,根据报文标识确定正确的上层协议,最终到应用层被应用程序处理。
IoT设备采集的业务数据被设备端上的应用程序封装为 MQTT 报文,MQTT 报文会以数据流的形式通过一条已经建立的TCP长连接按序传输,TCP收到数据流后会将其分割成小的数据块,每个小块被添加的TCP首部与数据块共同组成了TCP分组,分组经由网络层发送,网络层遵循IP协议,当收到分组发送请求后,会将分组其放入IP数据报,填充报头,将数据报发经由链路层发送出去。
云端系统从链路层接收到数据请求后,进入网络层对数据进行解析,交给给传输层,校验分组顺序和完整性,从数据块中取出数据,得到MQTT报文,交给应用层进行处理。这个过程会逐层剥离报头还原出IoT设备采集的业务数据。
应用层 -MQTT协议
MQTT是一个客户端服务端架构的发布/订阅模式的消息传输协议。它的设计思想是轻巧、开放、简单、规范,易于实现。这些特点使得它对很多场景来说都是很好的选择,特别是对于受限的环境如机器与机器的通信(M2M)以及物联网环境(IoT)。
MQTT 协议的数据包格式非常简单,由固定报头(Fixed header)、可变报头(Variable header)、有效载荷(Payload)三个部分组成。
固定报头:包含控制报文类型,标志位和剩余长度。
MQTT 报文的首字节高4位(bit7~bit4)表示控制报文类型,总共可以表示16种协议类型:
MQTT 报文的首字节低4位(bit4~bit0)用于指定数据包的标志位,仅PUBLISH控制报文有使用。
剩余长度: MQTT 报文的第2 字节开始是用于标识 MQTT 数据包长度的字段,最少一个字节,最大四个字节,每一个字节的低 7 位用于标识值,范围为 0~127。
可变报头:存在于部分类型的 MQTT 数据包中,具体内容由相应类型的数据包决定。
有效载荷:存在于部分 MQTT 数据包中,存储消息的具体业务数据。
传输层-TCP 协议 **
MQTT 连接是建立在 TCP 连接的基础之上的,TCP 提供可靠的数据连接。当要传输一个 MQTT 报文时,报文数据会以流的形式通过一条已经打开的TCP连接按顺序传输,TCP会将收到的数据分成小块,每块是一个TCP分组。
由于数据是分成小块发送的,所以完整可靠的数据传输主要体现在:分组是否完整、分组顺序是否正常、分组是否损坏、分组数据是否重复。这些可以通过TCP的检验和、序列号、确认应答、重发控制、连接管理和窗口机制来控制。
TCP是传输控制协议,传输控制主要依赖首部包含的6个标志,它们控制报文的传输状态,以及发送端和接收端应对数据采取的动作。当它们的值为1时,标志对应的各自功能才允许被执行,比如当URG为1时,报文首部的紧急指针部分才有效。
- URG 紧急指针
- ACK 确认序号有效
- PSH 接收方应该尽快将这个报文段交给应用层。
- RST 重建连接
- SYN 同步序号用来发起一个连接
- FIN 发端完成发送任务
源端口和目的端口:标识发送方和接收方的端口号,一个TCP连接通过4个值确认:源IP、源端口、目的IP、目的端口,其中源IP和目的IP包含在IP分组内。
首部长度:表示TCP首部的字节长度,也能标记出从多少个字节开始,才是需要传输的数据。
TCP段序号:本段报文发送的数据第一个字节的序号,每段报文中的数据的每个字节都有序号,第一个字节的序号从0开始,依次加1,加到2的32次方减1后再次从0开始。
TCP段确认序号 :当首部标志ACK为1时,确认序号有效。TCP段被接收端接收后,会回送给发送端一个确认号,为上次接受的最后一个字节序号加1。
检验和:由发送端计算,接收端验证,如果接收方检测到检验和不正确,表明该TCP段可能有损坏,会被丢弃,同时接收端向回送一个重复的确认号(与最近的一次正确的报文传输的确认号重复),表明接收到的TCP段是错误的,并告知自己希望收到的序号。这时发送端需要立即重传出错的TCP段。
紧急指针:当首部标志URG为1时,紧急指针有效,表示发送端向接收端要发送紧急数据。紧急指针是一个正偏移量,它和TCP段序号相加,计算出紧急数据的最后一个字节的序号。比如接收方接收到数据,从序号为1000的字节开始读取,紧急指针为1000,那么紧急数据就是序号从1000到2000之间的字节。这些数据由接收方决定如何处理。
窗口尺寸:决定了TCP一次成块数据流的吞吐量。需要注意的是,它表示的是发送一方的允许对方发送的数据量,比如发送方首部中的窗口大小为1000,就表示发送方最多可以接受对方发来的1000个字节的数据量。这与发送方的数据缓存空间有关,会影响TCP的性能。
首部标志PSH:如果需要告诉接收方将数据立即全部提交给接收进程,发送方需要将PSH置为1,这里的数据是和PSH一起传送的数据以及之前接收到的全部数据。如果接收方收到了PSH为1的标志,需要立即将数据提交给接收进程,不用再等待有没有其他数据进来。
复位标志RST:当RST为1时,表示连接出现了异常情况,接收方将终止连接,通知应用层重新建立连接。
同步序号SYN:用来建立连接,涉及到TCP的三次握手。
- 开始建立连接时,客户端向服务器发送一个TCP分组,分组首部的SYN为1,并携带一个初始序号,表明这是一个连接请求。
- 如果服务器接受了连接,会向客户端发送一个TCP分组,分组中会包含SYN和ACK,都为1,同时包含一个确认序号,值为来自客户端的初始序号 + 1,表示连接已经被接受。
- 客户端收到上一步发来的分组后,会再向服务器发送一段确认报文分组,ACK为1,会再次携带确认序号,值是第二步来自客户端的确认序号 + 1。服务端收到确认信息后,进入已经连接的状态。
在第三步的确认分组中,是可以携带要发送的数据的。
连接终止标志FIN:用来关闭连接,当一端完成数据发送任务后会发送一个FIN标志来终止连接,但因为TCP在两个方向(C-S,S-C)上会有数据传递,每个方向有各自的发送FIN & 确认关闭流程,所以会有四次交互,也称为四次挥手。
- 如果客户端应用层的数据发送完毕,会导致客户端的TCP报文发送一个FIN,告知服务器准备关闭数据传送。
- 服务器接收到这个标志后,它发回一个ACK,确认序号为收到的序号加1,同时TCP还要向应用程序发一个文件结束符。
- 此时服务器关闭这个方向的连接,导致它的TCP也会发送一个FIN。
- 客户端接收到之后发回一个确认ACK,序号为收到的序号 + 1,连接完全关闭。
TCP段序号与确认序号保证了数据的顺序,检验和确保数据的完整性,紧急指针保证紧急数据可被及时处理。另外,TCP还有一些超时重传、 拥塞避免、慢启动的机制,都可以保证分组数据按照顺序完整的传到目标端。
网络层- IP 协议
如果说TCP分组是包装货物的集装箱,那么IP就是运送集装箱的卡车。IP协议提供了两个节点之间的连接,保证将TCP数据尽可能快地从源端送到终端,但却不能保证传输的可靠性。
IP层会将上层传过来的TCP分组封装,带上自己的首部,再进行选路、是否分片以及重组的工作,最终到达目的地,这个过程中,IP首部起了重要的作用,下面让我们看一下首部的结构。
版本:表示当前IP协议的版本,目前版本号是4,还有一种是6,也就是IPV4和IPV6,如果发送和接收这两端的版本不一致,那么当前IP数据报会被丢弃。
首部长度:整个首部的长度,最长为60字节。
服务类型(TOS):用来区分服务的类型,但其实IP层在工作的时候一直没有实际使用过,现有的TOS只有4bit的子字段,和1bit的未用位。未用位必须置为0。TOS的4个bit中只能将一个置成1,用来表示当前服务类型。4bit对应的4个服务类型分别为:最小时延、最大吞吐量、最高可靠性和最小费用。
总长度:表示当前的数据报报文的总长度,单位为字节,可以结合首部长度计算出报文内数据的大小以及起始位置。
下面这三个首部字段涉及到IP数据报的分片与重组过程,由于网络层一般会限制每个数据帧的最大长度,IP层发送数据报会在选路的同时查询当前设备网络层的每个数据帧的最大传输长度,一旦超出,数据报就会被进行分片,到达目的地之后再进行重组,此时就会用以下三个字段作为重组依据。需要注意的是:因为存在选路的过程,数据报经过的每层路由设备对于数据帧的最大传输长度都不同,所以分片可能发生在任意一次选路的过程中。
分组标识:这个标识相当于ID,每成功发送一个分片,IP层就会把这个分组ID加1。
标志:共占用三位,分别是R、D、M,R目前还没有被使用,有用的是D、和M。这个字段表示了数据报的分片行为。D如果为1的话,表示数据无需分片,一次传输完;M如果为1,表示数据是分片的,后边还有数据,当它为0时,就表示当前数据报是最后一个分片,或者只有这一个分片。
片偏移:标识了当前分片距离原始数据报开始处的位置,分片之后,每一片的总长度会改成这一片的长度值,而不是整个数据报的长度。
生存时间:(TTL) 可以决定数据报是否被丢弃。因为IP发送数据是逐跳的,数据有可能在被设置了路由功能的不同的IP层之间转发,所以生存时间表示了数据报最多个可以经过多少个处理过它的路由,每经过一层路由,值减去1,当值为0时数据报就被丢弃,并且发送一个带有错误消息的报文(ICMP,IP层的组成部分,被用来传递一些错误信息)给源端。生存时间可以有效解决数据报在一个路由环路中一直转发的问题。
首部检验和:校验数据报的完整性,发送端对首部进行求和,将结果存在检验和中,接收端再计算一遍,如果计算结果与存在检验和中的结果一致,则说明传输过程是OK的,否则这个数据报就会被丢弃。
上层协议:决定了接收端在分用的时候将数据交给哪个上层协议去处理,例如TCP或者UDP。
源IP:记录了发送端的IP,在回送错误消息时用到。
目的IP:表示目的IP,每一次选路都要以它来做决策。
路由选择
因为IP首部只包含了目的IP地址,并不体现完整的路径,当向外发送数据时,IP层会根据目的IP在本机路由表中的查询结果来做出选路决策,数据报会逐跳地被运送到目的地,这里的每一跳,就是一次路由选择。
IP层既可配置成路由器,也可以配置成主机。当配置成路由功能时,可以对数据报进行转发,配置成主机时,如果目的IP不是本机IP,数据报会被丢弃。
具有路由功能的IP层在当目标IP不是本机地址的时候是根据什么判断转发到哪一站呢?要理解这个问题,需要先明白路由表的结构,以下是IP层维护的路由表:
- Destination(目的IP):表示IP数据报最终要到达或者经过的网络地址或者主机地址。
- Gateway(下一跳地址):当前维护路由表设备的相邻路由器的地址
Flags(标志):表示当前这一条路由记录的属性,具体用五个不同的标志来表示:
- U:该路由可以使用
- G:如果有这个标志,表示是下一跳是一个网关,如果没有,表示下一跳是和当前设备在一个网段,也就是可以直接把数据报发过去
- H: 下一跳是一个主机还是一个网络,有这个标志,表示主机,没有,则表示下一跳的路由是一个网络
- D:该路由是由重定向报文创建的
- M:该路由已被重定向报文修改
- Interface:当前路由项的物理端口
每收到一个数据报时候,IP层就会根据目的IP在路由表里查询,根据查询状态会导向三种结果:
- 找到了与目的IP完全匹配的路由项,将报文发给该路由项的下一站路由(Gateway)或者网络接口(Interface)
- 找到了与目的IP的网络号匹配的路由项,将报文发给该路由项的下一站路由(Gateway)或者网络接口(Interface)
- 前两者都没有找到,就看路由表里有没有默认路由项(default),有的话发给它指定的下一站路由(Gateway)
要是上边三个都没有结果,那么数据报就不能被发送。IP数据报就是这样一跳一跳地被送往目的主机的,但数据报有固有的长度,一旦超出了目的主机的MTU,就会被分片。
数据报分片的概念
TCP在进行握手的时候,会根据目的端IP层的最大传输单元(MTU)来决定TCP数据每次能传输的最大数据量(MSS),之后TCP会对数据依照MSS来进行分组,每个分组会被包装进一个IP数据报内。当IP数据报经过选路过程中的任意一层路由时,有可能被MTU限制住从而被分片,这时IP首部的3bit标志中的M标志被置为1,表示需要分片。每个分片的首部基本一样,只是片偏移有所不同。依据片偏移,这些分片在目的端被重组成一个完整的IP数据报(一个TCP分组)。IP传输是无序的,所以得到的数据报也是无序的,但如果数据完整,TCP会根据首部中的字段对其进行排序。一旦IP分片丢失,IP层无法组成完整的数据报,就会告诉TCP层,TCP进行重传。
链路层-ARP 协议 **
当IP层将数据封装好之后,只有目标主机的IP地址。光有IP地址并不能直接把数据报发送过去,因为每一台硬件设备都有自己的MAC地址,是一个48bit的值。现在知道目标IP的地址,需要找到这个IP对应的MAC地址。这个过程要通过查询路由表,再结合链路层的ARP协议,最终获得目标IP对应的MAC地址。
ARP协议实现了从IP地址到MAC地址的映射。一开始,起点并不知道目标的MAC地址,只有目标IP,要获取这个地址就涉及到了ARP的请求和应答。同样,ARP也有自己的分组,先看一下分组格式。
以太网目的地址: 目的端的MAC地址,当ARP缓存表中没有的时候,这里为广播地址。
以太网源地址: 发送端的MAC地址。
帧类型: 不同的帧类型有不同的格式和MTU值,不同的类型有不同的编号,这里ARP对应的编号是0x0806。
硬件类型: 指链路层网络类型,1为以太网。
协议类型: 指的是要转换的地址类型,0x0800为IP地址。比如将以太网地址转换为IP地址。
操作类型: 有四种,分别是ARP请求(1),ARP应答(2),RARP请求(3),RARP应答(4)。
源MAC地址: 表示发送端MAC地址。
源IP地址: 表示发送端IP地址。
目的以太网地址: 表示目标设备的MAC物理地址。
目的IP地址: 表示目标设备的IP地址。
当两台设备发送报文之前,源端的链路层会用ARP协议去询问目的端的MAC地址,ARP会将一个请求广播出去,以太网上的每一个主机都会收到这份广播,广播的目的是询问目标IP的MAC地址,内容主要是先介绍自己的IP和MAC地址,再询问如果你有目标IP,请回复你的硬件地址。如果一个主机收到广播后看到自己有这个IP,并且请求内有源IP和MAC地址,那么就会向源主机回应一个ARP应答。如果没有目标IP,就会丢弃这个请求。可以看出请求是向外广播的,而应答是单独回应的。
但不能每次通信之前都去经历一次请求-应答过程,在成功地接收到应答之后,IP和MAC地址的映射关系就会缓存在ARP缓存表中,有效期一般为20分钟,便于网络层下次直接进行封装,所以,完整的过程应该是:
IP层接收到TCP分组后,发送或者封装之前,通过查询路由表:
- 当目标IP和自己在同一个网段时,先去ARP缓存表里找有没有目标IP对应的MAC地址,有的话交给链路层进行封装发送出去。如果缓存表内没有,进行广播,获得MAC地址后缓存起来,IP层再对TCP进行封装,然后交给链路层再封装发送出去。
- 当目标IP和自己不在同一个网段,需要将报文发给默认的网关。如果ARP缓存表中有网关IP对应的MAC地址,那么交给链路层进行封装发送出去。如果没有,进行广播,获得地址后缓存起来,IP层再对TCP进行封装,然后交给链路层再封装发送出去。
以太网数据帧
上面所有东西都准备好了,封装发送的其实是以太网数据帧。以太网目的地址、以太网源地址、帧类型这三者组成了帧首部。在首部之前还会插入前同步码和帧开始定界符,告知接收端做一些准备工作。帧检验序列 FCS被添加进尾部,用来检测帧是否出错。
前同步码: 协调终端接收适配器的时钟频率,让它与发送端频率相同。
帧开始定界符: 帧开始的标志,表示帧信息要来了,准备接收。
目的地址: 接收帧的网络适配器的MAC地址,接收端收到帧时,会首先检查目的地址与本机地址是否相符,不是的话就会丢弃。
源地址: 发送端设备的MAC地址。
类型: 决定接收到帧之后将数据交由那种协议处理。
数据: 交给上层的数据。在本文的场景中指IP数据报。
帧检验序列: 检测这一帧是否出错,发送方计算帧的循环冗余码校验(CRC)值,把这个值写到帧里。接收方计算机重新计算 CRC,与 FCS 字段的值进行比较。如果两个值不相同,则表示传输过程中发生了数据丢失或改变。这时,就需要重新传输这一帧。
传输和接收
**
- 接收到上层传过来的数据报之后,根据MTU以及数据报大小来决定是否分割成小块,也就是IP数据报被分片的过程。
- 把数据报(块)封装成一帧,传给底层组件,底层组件将帧转换为比特流,并发送出去。
- 以太网上的设备接收到帧,检查帧里边的目标地址,如果与本机地址匹配,帧就会被处理,一层一层向上传递(分用过程)。
结语
以上,我们梳理了 IoT 设备将传感器采集数据,被端上应用程序封装成 MQTT 报文,通过网络协议一层层封装,再到云端接收系统一层层拆分的完整网络过程,希望对大家认识物联网相关 MQTT,TCP,IP,ARP 网络协议有所帮助。
【往期回顾】
1、39张IoT传感器工作原理GIF图汇总
2、自建 MQTT 集群 迁移 阿里云IoT实践
3、智能手持测温枪开发实践
4、JMeter 压测 MQTT 服务性能实战
5、IoT物联网平台日志服务详解
6、工业 Modbus,电力104规约, 车联网JT808
7、网关与子设备上云开发实战
8、IoT中实现 M2M 设备之间联动