MQTT在iOS中的应用实践

22 阅读10分钟

以下是结合自己大型项目中的应用做一个回想总结。

一、场景引出

先看以下几个网络通信场景:

场景一: 客户端需定时主动上报轻量级消息

客户端需要定时N秒上报位置轨迹给服务端,要求尽量实时和轻量级,该如何实现?HTTP的请求和响应都是比较重的,轮询不太合适。

场景二:客户端需要及时获取状态变更信息,但变更时间不确定

客户端A发起一个修改订单请求需要客户端B同意才能生效,那么如何才能及时得到B的同意或拒绝呢?HTTP请求轮询的方式及时性不高,且轮询间隔太小又导致后端QPS过高。

场景三:客户端A收到推送特定指令后需执行特定响应事件

客户端A在前台运行时间过久(疲劳驾驶之类的),被服务器端检测到需要下线,此时需要收到服务端的推送消息后执行下线动作。

客户端A在订单流程中,不定时可能收到(也可能没有)特定的消息,需要处理..。

这些场景,该用什么方案实现?----- 这就是本篇要介绍的主题MQTT协议。

它是一种极其轻量级的发布/订阅消息传输协议,基于TCP长连接,专为低带宽、高延迟、不可靠网络环境下的通信而设计。关于细节可以查看# MQTT消息通道-基础篇

二、系统如何集成MQTT

2.1 开源库

MQTT 原本是 IBM 的私有协议,2011 年捐赠给 OASIS 开放标准组织,成为 OASIS 标准。我们需要后台工程师配合部署到服务器,各客户端基本都有开源版本集成对接。

开源库:

2.2 iOS项目集成改造

上述开源库是一个MQTT协议的实现基础,在我们项目中需要做一层功能封装适配,这样各个业务模块能更方便地实现调用。

1. 架构层级

XXMQTT (项目针对开源库的上层封装 Pod)
  │  负责:业务标签分发(bizTag)、消息去重、多级重试、配置管理、匿名连接
  │
  ├── XXMQTTSeesionManager  ──调用──>  MQTTSessionManager.connectTo:
  ├── XXMQTTDispatcher      ──接收──>  MQTTSessionManagerDelegate.newMessage
  └── XXMQTTConfigManager   ──提供──>  host/port/token (传给 connectTo:)

MQTTClient (开源库)
  └── 纯粹 MQTT 协议实现,不感知上层业务逻辑(bizTag、去重、配置管理等)

2.关键设计要点

  1. 绕过 MQTTSessionManager:XXMQTT 直接操作 MQTTSession 而非 MQTTSessionManager,用自己的重试体系替代 Client 库自带的重连机制,实现更精细的控制(四级降级、30s 订阅超时)

  2. 双 Topic 模式:底层仅订阅 /rtc/global/im/{clientId}/rtc/global/push/{clientId} 两个 topic,业务层通过 bizTag 在 Dispatcher 中做二级分发,减少服务端订阅数

  3. 消息缓存兜底:IM 缓存 ≤10 条、PUSH ≤5 条,FIFO 淘汰。首次订阅时自动回放缓存消息

  4. 线程安全:pthread_mutex(状态锁)+ pthread_rwlock(读写锁)+ 两个串行队列(连接/发送分离)

  5. 环境隔离:stg/pre/prd 三套环境,不同域名(llrtc-center[-stg|-pre].xxx.cn),ConfigManager 自动选择

  6. 网络快速通道:AFNetworking 监听网络恢复 → 立即 quickConnect(),不等待心跳定时器

  7. connection refused 自动清配置:error.code==4(MQTTRefusedBadUserNameOrPassword)时自动清掉过期 token 缓存

  8. 发送节流:未连接状态下发消息,最多每 60s 触发一次 quickConnect(),防止高频无效重连

三、iOS库的代码架构及实现原理

代码来自开源库:MQTT-Client-Framework(Christoph Krey, 2013-2017)

3.1、总体架构 —— 四层结构

┌──────────────────────────────────────────────────────────────────────┐
│                    MQTTSessionManager                                │
│  会话管理:自动重连 · 订阅管理 · 前后台生命周期 · 连接参数持久化              │
└────────────────────────────┬─────────────────────────────────────────┘
                             │ 持有并代理
                             ▼
┌──────────────────────────────────────────────────────────────────────┐
│                       MQTTSession                                    │
│  MQTT 协议核心:CONNECT/DISCONNECT · SUBSCRIBE/PUBLISH ·QoS· KeepAlive│
│  作为 MQTTDecoderDelegate + MQTTTransportDelegate 的接收方            │
└──────────┬──────────────────────────────┬────────────────────────────┘
           │                              │
           ▼                              ▼
┌──────────────────────┐    ┌──────────────────────────────────┐
│    MQTTDecoder       │    │      MQTTTransport (协议)         │
│  字节流 → MQTTMessage │    │      MQTTCFSocketTransport       │
│  (解析 MQTT 协议帧)    │    │  CFStream TCP Socket · TLS/SSL   │
└──────────────────────┘    └──────────────┬───────────────────┘
                                           │
                         ┌─────────────────┼─────────────────┐
                         ▼                 ▼                 ▼
              ┌──────────────┐  ┌──────────────┐  ┌──────────────────┐
              │MQTTCFSocket  │  │MQTTCFSocket  │  │MQTTSSLSecurity   │
              │   Decoder    │  │   Encoder    │  │PolicyTransport   │
              │ (读流包装)    │   │ (写流包装)   │   │(SSL 证书固定)     │
              └──────────────┘  └──────────────┘  └──────────────────┘

3.2、核心类职责明细

1. MQTTSession — 协议核心(最核心的类)

MQTT 协议的完整实现,管理连接生命周期、消息收发、心跳保活。

**内部状态机: **

MQTTSessionStatus:
  Created → Connecting → Connected → Disconnecting → Closed
                          ↘ Error

关键属性:

属性类型说明
statusMQTTSessionStatus当前连接状态
transportid<MQTTTransport>传输层实现(依赖注入)
decoderMQTTDecoderMQTT 协议解码器
persistenceid<MQTTPersistence>消息持久化(QoS 1/2 需要)
keepAliveIntervalUInt16心跳间隔(默认 60s)
dupTimeoutdoubleQoS 1/2 消息超时重发间隔(默认 20s)
clientIdNSString客户端标识(nil 时自动生成)
delegateid<MQTTSessionDelegate>事件回调
connectHandlerblock连接结果回调
cleanSessionFlagBOOL清理服务端旧会话
protocolLevelMQTTProtocolVersion协议版本(3.1 / 3.1.1 / 5.0)

内部管理结构:

  • subscribeHandlers — 订阅回调字典,key = messageId

  • unsubscribeHandlers — 取消订阅回调字典

  • publishHandlers — 发布回调字典

  • txMsgId — 自增消息 ID(从 1 开始,1~65535 循环)

  • keepAliveTimer / checkDupTimer — GCDTimer 定时器

2. MQTTSessionManager — 会话管理器

在 MQTTSession 之上封装自动重连、订阅管理、前后台切换。

内部状态机:

MQTTSessionManagerState:
  Starting → Connecting → Connected → Closing → Closed
                ↘ Error

核心职责:

  • 连接参数记忆:保存 host/port/tls/keepalive/clean/auth/user/pass/clientId 等,参数变化时重建 Session
  • 自动重连:通过 ReconnectTimer 实现指数退避重连(1s → 2s → 4s → … → 64s)
  • 订阅管理:维护 internalSubscriptions / effectiveSubscriptions 字典,连接成功后自动复订阅
  • 前后台管理:通过 ForegroundReconnection 实现退后台断开、回前台重连
  • 消息透传:接收 MQTTSession 的消息事件,转发给 MQTTSessionManagerDelegate

3 MQTTTransport(协议) / MQTTCFSocketTransport(实现)

MQTTTransport 协议定义了传输层的抽象接口:

opensend(data) → close
状态: Created → Opening → Open → Closing → Closed

MQTTCFSocketTransport 基于 Apple CFStream 实现:

  • CFStreamCreatePairWithSocketToHost() 创建 TCP socket
  • 内部持有 MQTTCFSocketDecoder(读流)+ MQTTCFSocketEncoder(写流)
  • 读/写流绑定到指定 dispatch_queue_t
  • 支持 TLS/SSL(kCFStreamSocketSecurityLevelNegotiatedSSL
  • 支持 VoIP 后台模式(NSStreamNetworkServiceTypeVoIP
  • 支持客户端证书(PKCS12 格式)

4 MQTTSSLSecurityPolicyTransport — SSL 证书固定

继承 MQTTCFSocketTransport,增强 SSL 安全性:

  • 替换系统默认的证书链验证
  • 支持自定义 MQTTSSLSecurityPolicy(证书固定 / 自签名证书)
  • 通过 MQTTSSLSecurityPolicyDecoder / Encoder 包装原始流

5 MQTTDecoder — MQTT 协议解码器

基于 NSInputStream 的 MQTT 帧解析器,将原始字节流解析为 MQTTMessage解码状态机:

Initializing → DecodingHeader → DecodingLength → DecodingData → (循环回 DecodingHeader)
                    ↘ ConnectionError / ProtocolError

解码流程:

  1. DecodingHeader:读 1 字节 → 解析固定头部(消息类型 + 标志位)

  2. DecodingLength:逐字节读取剩余长度(变长编码,每字节低 7 位有效)

  3. DecodingData:读取指定长度的 payload 数据 → 完整消息 → 回调 decoder:didReceiveMessage:

6 MQTTMessage — 消息模型

MQTT 控制报文类型枚举(MQTTCommandType):

类型方向说明
1CONNECTC→S连接请求
2CONNACKS→C连接确认
3PUBLISH双向发布消息
4PUBACK双向QoS 1 确认
5PUBREC双向QoS 2 接收
6PUBREL双向QoS 2 释放
7PUBCOMP双向QoS 2 完成
8SUBSCRIBEC→S订阅请求
9SUBACKS→C订阅确认
10UNSUBSCRIBEC→S取消订阅
11UNSUBACKS→C取消订阅确认
12PINGREQC→S心跳请求
13PINGRESPS→C心跳响应
14DISCONNECTC→S断开连接
15AUTH双向MQTT 5.0 认证

工厂方法:为每种报文类型提供静态构造方法,自动序列化为 wireFormat(NSData 二进制格式)。

7 MQTTPersistence(协议) / 实现类

QoS 1/2 消息需要持久化存储以支持超时重发。

MQTTFlow 实体属性:clientId、方向(incoming/outgoing)、topic、data、qos、messageId、deadline、commandType

两种实现:

  • MQTTCoreDataPersistence:基于 CoreData 的持久化实现(默认)
  • MQTTInMemoryPersistence:仅内存存储

关键约束:

  • maxWindowSize(默认 16)— 飞行窗口大小,控制未确认消息数量
  • maxMessages(默认 1024)— 最大暂存消息数
  • maxSize(默认 64MB)— 最大存储空间

8 ReconnectTimer — 重连定时器

基于 GCDTimer 的指数退避重连:

  • 初始间隔 1.0s,每次触发后翻倍(2s → 4s → 8s → … → maxRetryInterval,默认 64s)

  • schedule — 启动定时器

  • stop — 停止

  • resetRetryInterval — 重置为初始间隔(连接成功后调用)

9 ForegroundReconnection — iOS 前后台管理

监听三个系统通知:

通知动作
UIApplicationWillResignActive断开 MQTT 连接
UIApplicationDidEnterBackground申请后台任务延长时间
UIApplicationDidBecomeActive重新连接

10 辅助类

职责
MQTTStrict全局开关,控制是否对 MQTT 协议参数做严格校验
MQTTLog日志宏(DDLogVerbose/DDLogWarn/DDLogError)
MQTTReport未在 umbrella header 公开(内部使用)
MQTTSessionLegacy对 mqttio-OBJC 旧版 API 的向后兼容扩展 Category
MQTTPropertiesMQTT 5.0 属性模型

3.2、调用链路详解

1 连接建立全链路

调用方
  │
  ├─> MQTTSessionManager.connectTo:port:tls:...connectHandler:
  │     │
  │     ├── ① 判断参数是否变化,若变则创建新 MQTTSession
  │     │      └─> MQTTSession.initWithClientId:userName:password:...
  │     │            │  设置 persistence (CoreData)
  │     │            │  设置 delegate = MQTTSessionManager
  │     │
  │     ├── ② 若已有 session 且参数未变 → reconnect:
  │     │      否则 → connectToInternal:
  │     │
  │     └── ③ connectToInternal:
  │           ├── 创建 MQTTCFSocketTransport(或 SSLSecurityPolicyTransport)
  │           ├── 设置 transport.host / port / tls / certificates / voip / queue
  │           ├── session.transport = transport
  │           └── [session connectWithConnectHandler:]
  │
  └─> MQTTSession.connect
        │
        ├── ① 严格模式参数校验(clientId / userName / protocolLevel / will 等)
        ├── ② cleanSessionFlag=YES → 清空 persistence + 所有 handler 字典
        ├── ③ tell(发送队列中待发消息)
        ├── ④ status = MQTTSessionStatusConnecting
        ├── ⑤ 创建 MQTTDecoder,设 delegate=self,open
        ├── ⑥ transport.delegate = self
        └── ⑦ [transport open]
              │
              └─> MQTTCFSocketTransport.open
                    ├── CFStreamCreatePairWithSocketToHost(NULL, host, port, &readStream, &writeStream)
                    ├── 如 tls=YES → 设置 SSL 属性(证书固定等)
                    ├── 创建 MQTTCFSocketEncoder → 绑定 writeStream → open
                    └── 创建 MQTTCFSocketDecoder → 绑定 readStream → open
                          │
                          └── encoderDidOpen: 回调
                                └── state = MQTTTransportOpen
                                    └── mqttTransportDidOpen: (MQTTSession 收到)
                                          │
                                          └── 发送 CONNECT 报文
                                                encode([MQTTMessage connectMessageWithClientId:...])
                                                └── transport.send(wireFormat)
                                                      └── encoder.send(data)
                                                            └── writeStream 写入
  


        服务端返回 CONNACK →
          │
          └── MQTTCFSocketTransport 收到数据
                └── decoder:didReceiveMessage: → transport delegate
                      └── MQTTSession.mqttTransport:didReceiveMessage:
                            └── MQTTDecoder.decodeMessage(data)
                                  │
                                  └── decoder:didReceiveMessage: (MQTTSession 收到已解析消息)
                                        │
                                        ├── status=Connecting + type=CONNACK:
                                        │   ├── returnCode == Success:
                                        │   │   ├── status = Connected
                                        │   │   ├── 启动 checkDupTimer(每 1s 检查待重发消息)
                                        │   │   ├── 启动 keepAliveTimer(按 effectiveKeepAlive 间隔)
                                        │   │   ├── 回调 delegate.connected:sessionPresent:
                                        │   │   ├── 回调 connectionHandler(MQTTSessionEventConnected)
                                        │   │   └── 回调 connectHandler(nil) ← 调用方收到连接成功
                                        │   │
                                        │   └── returnCode != Success:
                                        │       └── 回调 connectHandler(error)
                                        │
                                        └── status=Connected + type=PUBLISH:
                                            └── delegate.newMessage:data:onTopic:qos:retained:mid:

2 消息接收全链路

MQTT Broker  TCP Socket
  
  └── CFReadStream 有数据到达
        └── MQTTCFSocketDecoder.stream:handleEvent: (NSStreamEventHasBytesAvailable)
              └── decoder.delegate (MQTTCFSocketTransport)
                    └── transport.delegate (MQTTSession)
                          └── MQTTSession.mqttTransport:didReceiveMessage:
                                └── [self.decoder decodeMessage:data]
                                      
                                      └── MQTTDecoder.decodeMessage:
                                            ├── 创建 NSInputStream 读取原始字节
                                            ├── DecodingHeader:  1 字节  获取 type+flags
                                            ├── DecodingLength: 读变长长度
                                            ├── DecodingData:  payload
                                            └── decoder:didReceiveMessage:  MQTTSession
                                                  
                                                  └── MQTTSession.decoder:didReceiveMessage:
                                                        ├── [MQTTMessage messageFromData:]  解析为消息对象
                                                        ├── 通知 delegate.received:type:qos:...
                                                        ├── 询问 delegate.ignoreReceived:... (可选过滤)
                                                        
                                                        └── switch (message.type):
                                                              ├── PUBLISH:
                                                                 ├── QoS 0: 直接回调 newMessage  delegate
                                                                 ├── QoS 1:  persistence  发送 PUBACK  回调
                                                                 └── QoS 2:  persistence  发送 PUBREC 
                                                                      PUBREL  发送 PUBCOMP  回调
                                                              ├── PUBACK/PUBREC/PUBCOMP: 更新 persistence flow
                                                              ├── SUBACK: 回调 subscribeHandler
                                                              └── PINGRESP: 无操作

3 消息发布全链路

调用方
  │
  ├─> MQTTSessionManager.sendData:topic:qos:retain:
  │     └── [session publishData:onTopic:retain:qos:]
  │
  └─> MQTTSession.publishData:onTopic:retain:qos:
        │
        ├── ① 严格模式参数校验(topic 非空、不含通配符、长度限制)
        │
        ├── ② QoS 0(At Most Once):
        │     直接构造 PUBLISH → encode → transport.send → 完成
        │
        └── ② QoS 1/2(At Least Once / Exactly Once):
              ├── msgId = nextMsgId()(自增,165535 循环)
              ├── 检查飞行窗口大小 windowSize ≤ maxWindowSize(16)
              ├── 窗口未满:
              │   ├── 构造 PUBLISH 报文 → encode
              │   ├── 存入 persistence(topic/data/qos/msgId/deadline=now+20s)
              │   └── 等待服务端 PUBACK/QoS2 流程
              ├── 窗口已满:
              │   └── 存入 persistence(commandType=MQTT_None)→ 等待 checkDupTimer 下次发送
              └── tell()(触发 checkTxFlows)

4 QoS 2 完整握手流程

Client                              Server
  │                                    │
  ├─ PUBLISH (msgId=N, qos=2) ────────>│
  │                                    │
  │<─────────────── PUBREC (msgId=N) ──┤  (服务端确认收到)
  │                                    │
  ├─ PUBREL (msgId=N) ────────────────>│  (客户端确认可发布)
  │                                    │
  │<────────────── PUBCOMP (msgId=N) ──┤  (服务端确认完成)
  │                                    │
  └─ 回调 publishHandler               │

5 心跳保活机制

MQTTSession.keepAliveTimer (GCDTimer)
  │  间隔 = effectiveKeepAlive(服务端返回的 serverKeepAlive 或本地 keepAliveInterval)
  │
  └─ 定时触发 keepAlive()
        ├── delegate.beforeKeepAlive: → 允许 delegate 修改 PINGREQ 消息
        ├── encode([MQTTMessage pingreqMessage]) → transport.send
        └── delegate.afterKeepAlive:

6 消息重发机制

MQTTSession.checkDupTimer (GCDTimer, 每 1 秒触发)
  │
  └─ checkDup() → checkTxFlows()
        │
        ├── 获取 persistence 中所有出站 flow
        ├── 遍历,找到 deadline 已过期的 flow
        │
        └── 按 commandType 处理:
              ├── 0 (MQTT_None, 排队中):
              │     └── 窗口未满 → 构造 PUBLISH → encode → commandType=MQTTPublish → deadline 延后 20s
              ├── MQTTPublish (已发送未确认):
              │     └── 超时 → 重发 PUBLISH(dupFlag=YES)→ deadline 延后 20s
              └── MQTTPubrel (QoS 23 步):
                    └── 超时 → 重发 PUBREL → deadline 延后 20s

7 断线重连机制

MQTTSessionManager 作为 MQTTSessionDelegate 接收事件:
  │
  ├── on MQTTSessionEventConnectionClosedByBroker (非主动关闭):
  │     └── triggerDelayedReconnect
  │           └── ReconnectTimer.schedule → 1s 后 reconnect:
  │
  ├── on MQTTSessionEventProtocolError / ConnectionRefused / ConnectionError:
  │     └── triggerDelayedReconnect
  │           └── ReconnectTimer.schedule
  │
  ├── on MQTTSessionEventConnected:
  │     └── ReconnectTimer.resetRetryInterval (重置为 1s)
  │     └── 自动复订阅 internalSubscriptions
  │
  └── ReconnectTimer.reconnect():
        ├── stop timer
        ├── currentRetryInterval = min(currentRetryInterval * 2, maxRetryInterval)  // 指数退避
        └── 执行 reconnectBlock → MQTTSessionManager.reconnect:
              └── state = Starting → connectToInternal → 重新走连接流程