MQTT在iOS中的应用实践

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

一、场景引出

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

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

客户端需要定时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. 架构层级

scss 复制代码
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、总体架构 ------ 四层结构

scss 复制代码
┌──────────────────────────────────────────────────────────────────────┐
│                    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 协议的完整实现,管理连接生命周期、消息收发、心跳保活。

**内部状态机: **

vbnet 复制代码
MQTTSessionStatus:
  Created → Connecting → Connected → Disconnecting → Closed
                          ↘ Error

关键属性:

属性 类型 说明
status MQTTSessionStatus 当前连接状态
transport id<MQTTTransport> 传输层实现(依赖注入)
decoder MQTTDecoder MQTT 协议解码器
persistence id<MQTTPersistence> 消息持久化(QoS 1/2 需要)
keepAliveInterval UInt16 心跳间隔(默认 60s)
dupTimeout double QoS 1/2 消息超时重发间隔(默认 20s)
clientId NSString 客户端标识(nil 时自动生成)
delegate id<MQTTSessionDelegate> 事件回调
connectHandler block 连接结果回调
cleanSessionFlag BOOL 清理服务端旧会话
protocolLevel MQTTProtocolVersion 协议版本(3.1 / 3.1.1 / 5.0)

内部管理结构:

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

  • unsubscribeHandlers --- 取消订阅回调字典

  • publishHandlers --- 发布回调字典

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

  • keepAliveTimer / checkDupTimer --- GCDTimer 定时器

2. MQTTSessionManager --- 会话管理器

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

内部状态机:

vbnet 复制代码
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 协议定义了传输层的抽象接口:

perl 复制代码
open → send(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解码状态机:

scss 复制代码
Initializing → DecodingHeader → DecodingLength → DecodingData → (循环回 DecodingHeader)
                    ↘ ConnectionError / ProtocolError

解码流程:

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

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

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

6 MQTTMessage --- 消息模型

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

类型 方向 说明
1 CONNECT C→S 连接请求
2 CONNACK S→C 连接确认
3 PUBLISH 双向 发布消息
4 PUBACK 双向 QoS 1 确认
5 PUBREC 双向 QoS 2 接收
6 PUBREL 双向 QoS 2 释放
7 PUBCOMP 双向 QoS 2 完成
8 SUBSCRIBE C→S 订阅请求
9 SUBACK S→C 订阅确认
10 UNSUBSCRIBE C→S 取消订阅
11 UNSUBACK S→C 取消订阅确认
12 PINGREQ C→S 心跳请求
13 PINGRESP S→C 心跳响应
14 DISCONNECT C→S 断开连接
15 AUTH 双向 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
MQTTProperties MQTT 5.0 属性模型

3.2、调用链路详解

1 连接建立全链路

ini 复制代码
调用方
  │
  ├─> 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 消息接收全链路

yaml 复制代码
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 消息发布全链路

ini 复制代码
调用方
  │
  ├─> 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()(自增,1~65535 循环)
              ├── 检查飞行窗口大小 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 完整握手流程

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

5 心跳保活机制

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

6 消息重发机制

scss 复制代码
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 2 第 3 步):
                    └── 超时 → 重发 PUBREL → deadline 延后 20s

7 断线重连机制

csharp 复制代码
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 → 重新走连接流程
相关推荐
ALINX技术博客1 小时前
【黑金云课堂】FPGA技术教程Vitis开发:TCP以太网通信
网络协议·tcp/ip·fpga开发
W.W.H.3 小时前
Ping 与 TCP:网络连通性探测的两种维度
网络·网络协议·tcp/ip
IpdataCloud4 小时前
担心IP查询泄露隐私?用离线查询工具安全查IP,数据不出内网
网络协议·tcp/ip·安全
码农飞哥4 小时前
RocketMQ消费接口设计实战:为什么HTTP回调接口必须吞掉所有异常,始终返回成功?
网络协议·http·中间件·消息队列·rocketmq
行走__Wz4 小时前
【网工入门-04】局域网、城域网、广域网
网络协议
白露与泡影5 小时前
为什么 RPC 要比 HTTP 更快?我:之前项目只用过 HTTP...
网络协议·http·rpc
上海云盾-小余5 小时前
弱口令专项整治:批量检测与强制加固方案
网络协议·安全
大神15735 小时前
Jetty 6 HTTPS 配置指南
网络协议·https·jetty
network_tester6 小时前
TSN交换机研发测试怎么做?一套可落地的“信而泰仪器 + 康芯源服务”方案解读
网络·网络协议·tcp/ip·车载系统·汽车·信息与通信·信号处理