很多搞物联网的朋友一开始最容易纠结的问题就是:设备端通信到底该用 MQTT、TCP 还是 HTTP?网上资料不少,但大多数要么停留在概念对比,要么只讲某个协议的单独用法,真正能指导选型的并不多。这篇文章我想结合自己做过的几个实际项目,把 MQTT、TCP、HTTP 这三者的本质区别、适用场景、选型逻辑和部署中容易踩的坑一次讲透。不管你是刚入门 embedded 开发,还是已经在做物联网平台选型,这篇都值得你花几分钟看完。
1. 先搞清楚一个前提:这三兄弟根本不在同一个层级
很多人把 MQTT、TCP、HTTP 放在一起比“谁更强”,这个出发点其实就有点偏了。因为它们并不是同一层的东西——TCP 是传输层协议,负责把数据可靠地从 A 点搬到 B 点;而 HTTP 和 MQTT 是应用层协议,它们依靠 TCP 来做底层传输,自己则定义了一套业务语义。
打个比方,TCP 就好比一条公路,HTTP 和 MQTT 是在这条公路上跑的两种不同规则的车——HTTP 是那种“一问一答、办完事就走”的出租车,MQTT 则是“双向都留了个司机待命、随时可以推送”的专线班车。公路本身不关心车上坐的是谁、要去干什么,它只管把货物安全送到。
这个基本认知决定了后续所有的选型思路:
| 协议 | 所属层级 | 核心职责 | 侧重能力 |
|---|---|---|---|
| TCP | 传输层 | 可靠字节流传输 | 连通性、可靠性、顺序保证 |
| HTTP | 应用层 | 请求/响应式资源访问 | 简单、通用、易调试 |
| MQTT | 应用层 | 发布/订阅式消息分发 | 轻量、低带宽、实时推送 |
所以咱们真正要对比的其实是:在应用层,你是选 HTTP 那一套“请求-响应”模式,还是选 MQTT 那一套“发布-订阅”模式。至于 TCP,它往往不是“选不选”的问题,而是当你发现 HTTP 和 MQTT 都撑不住你的场景时,才会考虑“直接用裸 TCP 定制协议”的终极大招。
理解了这个层级差异,你就知道为什么很多“MQTT 比 TCP 强”的说法是不严谨的——MQTT 正是在 TCP 之上才跑起来的,它俩根本不算对手。
2. 三种协议各自的“舒适区”到底在哪
既然不能孤立地比强弱,那我们就得看每种协议最适合干什么样的活。判断标准无非三件事:终端设备的硬件条件、网络环境的稳定性、业务逻辑对实时性和双向通信的要求。
2.1 TCP:裸通信,适合“我就想自己说了算”的场景
TCP 本身不规定消息格式,你往里面写什么内容都行。这既是优点也是缺点——优点是灵活到极致,缺点是所有的活都得你自己干:拆包粘包、消息确认、重传机制、心跳保活、异常恢复,一个都跑不掉。
我自己实际用裸 TCP 的典型场景就是长连接网关与服务器之间的数据通道。比如说某个环境监测项目,现场几十个采集点通过串口把数据交给边缘网关,网关再用 TCP 长连接把数据上报到中心服务器。这种场景之所以不选 HTTP,是因为数据频率高(每秒都有多条采集记录),每一条都走 HTTP 的请求响应模式,光握手开销就让人头疼;不选 MQTT 则是因为这条链路的数据格式完全是自己定义的二进制帧,换成 MQTT 反而要多套一层编码解码,收益不大。
不过我得提醒一句:直接拿 TCP 下手,对开发者的要求确实高。只要链路不稳定,你需要补的逻辑就是一大堆。当时做网关那段时间,处理半包、粘包就花了不小的精力,后来干脆封装了一个带“帧头+长度+CRC”的自定义协议,才把这一块的稳定性做扎实。
2.2 HTTP:请求响应,适合“设备主动找服务器办事”
HTTP 的模型特别简单:客户端发起请求,服务端给一个响应,完事。在物联网里,这个模型最匹配的就是设备主动上报数据、或者设备主动拉取配置这类场景。
比如家里的智能插座,它每 15 分钟上报一次用电量,用 HTTP POST 一个 JSON 过去,服务器返回 200 就完事。这种需求你用 MQTT 反而显得杀鸡用牛刀——你得先搭 Broker、管理 Topic、维护长连接,而 HTTP 只需要一个 URL 就能搞定。
再比如某个设备开机后需要做 OTA 升级检查,先发一个 GET 请求问服务器“有没有新固件”,有就下载,没有就下次再说。这种一问一答的交互方式,HTTP 天然就是最直白的表达。HTTP 还自带成熟的生态:RESTful API、JSON 格式、各种现成的客户端库、调试工具,团队上手成本非常低。
需要说明的是,HTTP/1.1 的痛点在于没法主动推送,服务器只能被动等请求。设备要想“被实时控制”,要么用轮询硬扛,要么就得考虑别的路子。轮询这个东西,数据频率一高,浪费带宽是小问题,关键是实时性很难看。
2.3 MQTT:发布订阅,专治“双向通信”和“弱网环境”
MQTT 最核心的武器不是省流量,而是发布/订阅模型。它把“谁生产消息”和“谁消费消息”彻底解耦,中间再加了一个消息代理做中转。
举个例子:有一批温湿度传感器,它们不需要知道谁在关心自己的数据,反正只管往sensor/temp/room1这个主题发消息就行。订阅了这个主题的看板程序、手机 App、告警服务各自都能同时收到数据。这种一对多、多方接管的通信模式,在 HTTP 里要实现就非常别扭,但在 MQTT 里是天生的。
更关键的是,MQTT 是为低带宽、高延迟、弱网环境设计的。它的报文头非常小,连接建立之后一条消息的开销很小,还能通过 QoS 等级控制消息到达的可靠性,允许客户端在断网恢复后自动重连并续传离线期间的消息。这些特性组合起来,让 MQTT 成了大量物联网设备接入的首选,尤其是那些用电池供电、不能频繁通信、网络质量还不咋地的设备。
我最初接触 MQTT 是因为项目里需要远程控制设备——服务器要把一条控制指令实时推送到现场几百台设备上去。用 HTTP 轮询的话,实时性做不到秒级;用裸 TCP 自己写协议的话,服务器端要管的状态又多又杂。后来换了 MQTT,服务器往“设备的控制主题”发一条消息,设备端立刻就能收到并执行,整个逻辑一下子清爽多了。
3. 核心选型思路:我拿一个门锁项目给你走一遍决策逻辑
理论说再多,都不如拿一个真实场景来推演。这里分享一个我参与过的智能门锁项目,正好把三种协议在同一个项目里分别怎么用、为什么这么用,从头到尾捋一遍。
这个项目的终端种类大概分三类:
- 门锁本体:电池供电,MCU 资源非常有限,平时深度休眠,只有在开锁、上报电量、接收临时密码时才需要联网。
- 室内网关:常年插电,通过 Wi-Fi 联网,负责把家里几把门锁的通信汇聚起来。
- 云端平台:面向用户 App 和物业管理后台,需要实时展示门锁状态、下发指令、生成记录报表。
3.1 门锁到网关之间:选了 MQTT,而不是 HTTP 或裸 TCP
门锁和网关走的是低功耗通信(比如 BLE 或 Sub-1GHz),网关往上是 Wi-Fi。最开始讨论的是不是可以让门锁直接走 HTTP 上报数据到云端?被否了,原因有三个:一是门锁大部分时间在休眠,HTTP 长连接撑不住,每次唤醒再重新建连,功耗和时延都不理想;二是服务器要主动下发临时密码,HTTP 做不了实时推送;三是远端信号差的时候,HTTP 大批量重传的表现很一般。
后来改成 MQTT 之后,效果就顺了:门锁唤醒后通过网关接入本地 MQTT Broker,用 QoS 1 发布状态消息;平台要下发临时密码时,往门锁对应的主题发一条消息,门锁在唤醒周期内就能收到确认。MQTT 的遗嘱消息机制(LWT)帮了大忙——门锁异常断电时 Broker 能立刻感知并推送离线消息,这在 HTTP 的模型里根本没法实现。
至于为什么不直接在门锁上跑裸 TCP?原因很简单:门锁 MCU 的资源太少,要自己处理分包、续传、心跳保活,太吃力了。MQTT 客户端库已经把这些都封装好了,哪怕占用资源多几百字节,换来的可靠性也完全值。
3.2 网关到云端之间:MQTT 和 HTTP 各管一段
网关作为“二传手”,它和云端的通信其实不是单一协议,而是按业务拆开用:
- 实时状态上报与指令下发:走 MQTT。网关维护一条到云端 Broker 的长连接,状态数据按秒级频率推送,指令下发同样走主题订阅,实现双向实时通道。
- 固件升级与批量数据导出:走 HTTP。OTA 升级的固件包动辄几十上百 MB,走 MQTT 就不合适了——MQTT 毕竟面向轻量消息设计,传大文件效率不高;HTTP 可以支持断点续传、分块下载,生态里还有各种 CDN 加速方案,所以这里果断用 HTTP。
- 开锁记录、操作日志这类低频数据:可以攒一批然后用 HTTP POST 上报,没必要每条都走 MQTT 实时推送,能省就省。
这类“MQTT 管实时、HTTP 管大文件”的组合拳,在实际工业项目里非常常见。你别把协议当成单选题,它们完全可以共存、各司其职。
3.3 裸 TCP 在这个项目里的位置:网关与设备之间的“底层管道”
要说这个项目里完全没有裸 TCP 也不太准确。网关下接的那些采集器,比如门磁状态、人体感应、环境温湿度这些,走的是 RS-485 总线转 TCP 的桥接方式。网关和传感器之间用了自封装协议,到了网关再统一翻译成 MQTT 消息发给云端。
这里的逻辑是:传感器和网关之间是“局域网内的固定链路”,网络环境简单可控,用什么应用层协议意义都不大,直接用裸 TCP 打底反而最干净。而到了广域网这一段,环境复杂了,才需要 MQTT 去应对不可控因素。协议选型的第一原则,永远是看清这段链路的边界在哪里。局域网内你可以自己说了算,一旦上了公网,就该把可靠性的担子交给成熟协议去扛。
4. 部署实战:MQTT Broker 选型与参数设置的经验总结
很多教程讲到 MQTT 就停在“用客户端连一下 broker、订阅一个主题”就完事了。真到了部署阶段,会有一堆参数设置和稳定性问题等着你。我根据实际部署经验把这部分关键点串一遍。
4.1 Broker 怎么选:容量、稳定性、插件生态缺一不可
Broker 是 MQTT 架构里的中枢神经,选错后面全盘难受。我用的比较多的主要是两类:一类是轻量级的开源 Broker,部署简单、配置少、资源占用小,适合中小规模项目;另一类是功能更全的商用级 Broker,支持集群、规则引擎、数据持久化、多租户管理,适合设备量上了几十万台、需要横向扩容和运维监控的场景。
选型时别只看“能不能跑通”,要重点考察这几个维度:
- 单机连接数上限:你的设备在线规模有多大?Broker 能不能撑住?连接数上去之后 CPU 和内存的表现是否线性?
- 消息吞吐量:你每秒最多要处理多少条消息?有的 Broker 在千级消息时性能还行,上万级就开始丢消息了。
- 集群能力:以后设备多了能不能横向加节点?主从切换时业务会不会断?
- 插件生态:有没有现成的鉴权插件、桥接插件、规则引擎、WebHook 集成?
我一开始做项目时图省事,直接用轻量级 Broker 跑了几百台设备,也没出什么问题。后来业务增长到几千台设备在线,加上消息频率提高,单机 Broker 开始频繁 CPU 告警,最后只能迁移到支持集群的方案。现在给我的教训就是,即便初期设备量小,也要把 Broker 的横向扩展能力纳入评估,否则后期迁移非常折腾。
4.2 QoS 别一上来就全用 2,那是给自己找事
MQTT 提供了三档 QoS 可靠级别,很多人一看到“最可靠”就全选 QoS 2,实际上这是新手最容易犯的错误。
| QoS 级别 | 语义 | 适用场景 |
|---|---|---|
| QoS 0 | 至多一次,消息可能丢失 | 高频监测数据,丢了下一帧还有 |
| QoS 1 | 至少一次,可能重复 | 设备状态上报、指令下发,允许业务侧去重 |
| QoS 2 | 恰好一次,严格不重不丢 | 支付指令、关键配置修改、离线期间补发 |
QoS 2 的好处是“恰好一次”,但代价是协议交互次数多、开销大,吞吐量反而比 QoS 1 低不少。现实里绝大多数业务用 QoS 1 就足够了。你可以在消息里带一个递增的序列号,接收方依据序列号就能做去重,效果和 QoS 2 一样,但性能压力小得多。
再说个细节:消息发布端的 QoS 和订阅端的 QoS 是分开协商的。最终消息实际按两者中较低的 QoS 来投递。所以你想让某一组订阅方拿到更可靠的消息,应该在订阅方这一侧做设置,而不是光靠发布端指定。
4.3 Keepalive 心跳、Session 过期和 Clean Session 怎么配
设备断线重连是物联网的家常便饭。Broker 用 Keepalive 机制来感知客户端是否在线:客户端在设定周期内必须至少发一个报文,否则 Broker 就判定连接断开。
Keepalive 设置太短,Wi-Fi 稍微抖一下就频繁掉线;设置太长,设备真挂了服务器要等很久才能感知。我一般建议根据业务容忍度取 30~120 秒之间。之前调试一个室内网关项目,Keepalive 设了 10 秒,结果网关每次 DHCP 续租的时候都会触发一次误掉线,日志刷得飞起,后来改成 60 秒整个世界都清净了。
Session 的概念也很关键。MQTT 客户端可以跟 Broker 约定,断线后是否保留它的订阅关系和离线消息,这就涉及 Clean Session 参数:
- Clean Session = true:每次连接都是全新会话,断线后订阅关系和离线消息一概不留。
- Clean Session = false(持久会话):订阅关系会一直保留,断线期间发给它的消息也会暂存在 Broker 侧,等它重连后再补发。
对于不能常在线、有离线消息补发需求的设备,推荐用持久会话。但注意,持久会话会占用 Broker 内存,设备量大的时候,Session 堆积也会成为性能瓶颈,要配好过期清理策略。
4.4 遗嘱消息(LWT)不是可选项,而是保命的
前面提到门锁项目里用到了遗嘱消息。这个机制我多说几句——它其实是一个“预先写好的遗言”:设备连接时先告诉 Broker“如果我不正常断线,就帮我往某个主题发一条特定的离线消息。”
这个功能在物联网里的价值太大了:设备没电、被拔线、网络长期不可达时,平台都能及时感知到“它掉线了”,然后触发告警或调度逻辑。如果没有 LWT,平台只能靠“心跳超时”去猜,那个反应速度要慢得多。
用 LWT 的时候注意一点:遗嘱主题和普通业务主题尽量分开。如果混在一起,业务消息处理逻辑会变得混乱。我当时专门弄了一个/status主题,专门记录设备上下线状态,业务数据走另一套主题,两套互不干扰。
5. 从实战里爬出来的坑:三条最值得说的经验
协议选型不是看了对比表就能拍板的,真正有参考价值的往往是那些踩过坑之后才知道的事情。下面这几条,都是我在项目里实际淌过水的。
5.1 HTTP 轮询能做到“实时”,但代价你可能付不起
很多设备选 HTTP 的时候,为了拿到实时指令,只能把轮询间隔从 30 秒一路缩到 3 秒。服务器端如果承载几千台设备,每 3 秒一次的请求量就非常可观了,尤其在早晚高峰时段,网关和数据库的压力会被瞬间怼满。
我当时优化过一个类似的系统,光是把轮询间隔从 10 秒改成 5 秒,服务器的负载就翻了一倍,后来实在是撑不住,才下决心切到 MQTT。实时性不该靠加轮询频率硬刚出来,换一种通信模型才是治本。如果业务确实需要秒级响应,MQTT 的长连接推送几乎是唯一靠谱的选择。
5.2 裸 TCP 的上层设计不做好,后面全是坑
裸 TCP 虽然灵活,但“灵活”的另一面是“什么都得自己管”。最常见的问题就是粘包和半包:一条完整业务消息可能被拆成多个 TCP 包发出来,也可能多条消息合并成一个包。如果消息没有明确边界,接收方根本不知道从哪里截取才算一条完整的数据。
解决方式无非几种:固定长度、特殊分隔符、长度字段前置。我强推**“长度字段前置”**的方案——消息头固定若干字节,里面写明整个消息体的长度,接收方先读头、再按长度读体。当时为这个封装没少加班,但一套做下来,后面所有基于 TCP 的业务都直接复用了这个链路层框架,反而省了很多事。
另外,裸 TCP 的“心跳保活”不能只靠 TCP 自带的 Keepalive 机制,TCP 层心跳间隔太长、感知断线慢,而且有些网络环境会吞掉探测包。一定要在上层业务里自己定义应用层心跳,比如每隔 30 秒发一个空的心跳帧,超过 90 秒没收到就判死,然后主动重连。这一条做好了,链路稳定性会有质的提升。
5.3 MQTT 的消息大小限制别忽视,传大文件会翻车
MQTT 的报文格式决定了它处理长消息的效率远不如 HTTP。默认情况下 Broker 对单条消息的大小是有限制的,不同 Broker 可能从几百 KB 到几十 MB 不等。你要真拿 MQTT 去传几十 MB 的固件包,大概率会撞上限制,即便没撞上,也会把 Broker 的内存和带宽消耗得非常难看。
正确做法是混合使用:实时指令走 MQTT,固件升级、日志批量导出这类大型数据传输走 HTTP。两者共存不冲突,反而是我目前见过最顺手的物联网通信架构。具体做法可以是设备通过 MQTT 收到“有新固件”的通知,然后从 MQTT 消息里取出一个下载链接,再走 HTTP 去下载。既发挥了 MQTT 实时推送的优势,又利用了 HTTP 高效传大文件的长处。
6. 回到选型本身:一张决策表收尾,但别让它替你思考
文章的最后,我把整个选型思路浓缩成一张决策表,方便你直接对照项目情况做判断。但我得强调一句,决策表只是帮你理清思路的工具,真正拍板还是要回到你的具体业务场景、设备性能、网络环境和团队维护能力一起权衡。
| 维度 | 推荐 MQTT | 推荐 HTTP | 推荐裸 TCP |
|---|---|---|---|
| 双向实时通信 | 强项 | 不适合 | 可自研实现,成本高 |
| 设备低功耗、弱网、低频唤醒 | 强项 | 勉强 | 需要自己投入较多工作 |
| 设备主动上报,一问一答 | 可用 | 天然适配 | 可用 |
| 大文件传输/OTA 升级 | 不适合 | 强项 | 可自研实现 |
| 一对多消息分发、跨系统解耦 | 强项 | 需要额外架构 | 实现难度大 |
| 团队开发成本 | 中 | 低 | 高 |
| 弱网下的可靠性保障 | 内置 QoS | 需自己处理重试 | 需自己封装 |
我见过不少团队选协议,最后反而不是因为技术不行而翻车,而是因为一开始没想清楚“这条链路到底要承担什么任务”就匆忙定了方案,后面越做越别扭。
如果你现在还没有明确需求,我的建议是:从“设备能不能常在线、服务器要不要主动找设备、网络环境是否可控”这三个问题开始问自己。答案清晰了,协议选型的水到渠成。而一旦在真实项目里跑通了这套组合逻辑,后面再遇到新方案、新业务,你大概率不会再纠结协议之间的“高低强弱”,而是直接把每种协议放到最合适的位置上让它们协作起来。