本节摘要:把 TCP 的"可靠但重"与 UDP 的"轻但不管"两种底牌摊开来对比,给出物联网场景下选 TCP 还是 UDP 的判别式,并用一段最小 socket 示例直观呈现两者差异。承接 2.1 的选型坐标,通往 2.3 应用层协议(MQTT 坐 TCP,CoAP 坐 UDP)。
数据要在两台机器之间流动,先得过传输层这一关。而传输层只有两张底牌:TCP 把每条消息办成受过保的挂号信,UDP 把每个包当成不报销的平信往外面一扔,丢不丢随缘。本节就两面各讲利弊,再给判别式。
TCP 的卖点是"一定送达、且不乱序"。为了这句承诺,它要维护连接状态:三次握手建立通道、每个包都带序号、收包要回 ACK、丢包自动重传、收端还得按序重组。这些机制换来的是安全送达,付出的却是三层成本:
对一台连着公网、电源充足的网关,这些代价可以忽略;对一颗跑在手电筒供电里的传感器,每字节都得精打细算。
UDP 只做一件事:把报文投进网络,无连接、不重传、不保序。它的好处显而易见——头部小、无握手、延迟低、天然适合"发一条算一条"的遥测。但代价也明明白白:收不收得到靠天命,乱序也没人帮你排。所以工程上 UDP 通常要在应用层自建一套轻量可靠机制(序列号、超时重试、去重),这恰恰是后面 CoAP 的设计动机。
下面用一段伪 Python 示意(聚焦差异,省略完整网络代码细节)对比两种写法:
# TCP:先建立连接,之后的所有收发都建立在"这条管道"上 import socket s = socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.connect(("broker.example", 1883)) # 三次握手发生在 connect s.sendall(b"\x30..." ) # 应用层只管写,有序不丢交给 TCP s.close() # UDP:无连接,直接投递,丢不丢靠上层自己 import socket u = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) u.sendto(b"\x40...", ("gateway.example", 5683)) # 不握手、不重传 # 若要可靠,应用层自己加:序号 + 超时重发 + 去重
差异一眼可见:TCP 的 connect 帮你把可靠性包圆了,UDP 的 sendto 则把可靠性甩给应用层。
| 维度 | TCP | UDP |
|---|---|---|
| 连接状态 | 有,需握手 | 无连接 |
| 可靠性 | 保证送达与有序 | 不保证 |
| 数据报头 | 最小 20 字节 | 最小 8 字节 |
| 设备功耗倾向 | 高(须维持连接) | 低(用完即走) |
| 排查复杂度 | 需关注半开/保活 | 自己补重试去重 |
| IoT 典型宿主 | MQTT、HTTP | CoAP、LoRaWAN 之上的 IP 层、实时音视频 |
💡 关键直觉:判断口径一句话——"这条消息丢了重发一次能接受",就走 UDP 自建重试;"绝不能丢、顺序也不能乱",才坐 TCP。别因为"UDP 轻"就一概用轻,可靠语义成本早就写在应用层账本里了。
这对底牌直接决定上层协议的选择:MQTT 选择坐在 TCP 上,把可靠性外包给传输层,自己专注于主题与 QoS 语义;CoAP 选择坐在 UDP 上,把轻量可靠机制(确认、重传、去重)挪进自己报文里。理解了这一步,2.3 讲应用层全家福时才不会觉得 MQTT"为什么这么重"。
空谈优劣不如看一场真实对比。假设一个传感器每分钟上报一帧遥测,网络里随机丢包率约 1%,我们先看 TCP 会发生什么:若某一帧 ACK 丢失,TCP 触发重传,重传又经历退避,报文可能晚到几秒钟才补上;在公网跨运营商链路上,TCP 还会因慢启动和拥塞窗口,把一次本来几十毫秒的传输拉长到几百毫秒。对"每分钟一条、晚到一秒也无所谓"的遥测,TCP 付出的这些延迟其实毫无收益。
换到 UDP:这一帧丢了就丢了,等下一分钟自然有新的数据补齐。此时 1% 的丢包率意味着一年约五千帧里丢五十帧左右,对趋势监测几乎无感。
于是真实项目的取舍常变成:遥测类上报→UDP 自建轻量序号(错了算了或轻量重发);控制/计量类指令→TCP 或加确认的回执机制。 用"丢了重发能不能接受"这条判别式,多数场景能立刻落定。
日常排查时,一条消息神秘消失,先用短短三问给自己定位:报文是不是 UDP 直接丢了?是不是 TCP 半开连接导致对端认为没数据?是不是应用层把可靠误解成"发给一层就完事"? 若是前者,检查是否应加应用层重试;若是次者,检查保活与超时;若是末者,回头读本节的判别式。这类"按层归因"的思路,和 1.2 里"按层排查"一脉相承。
很多人选了 TCP,却低估了"维持一条长期 TPC 连接"本身的持续开销。一条常驻的 MQTT 连接,除了握手那一下,还藏着三笔没能写进交易明细的账:保活心跳——设备得周期性发 PingReq 证明自己活着,一发就是一个小小的上行开销;TCP Keep-Alive——协议栈层面定时发探测包,探测老设备、NAT 老化都靠它;半开连接——一端断网退出、另一端还傻等,占着连接号与内存不放。对一个常年上电的网关,这些都只是安静的电费;但对一颗靠纽扣电池、平时只想睡大觉的传感节点,每多一条心跳就等于多几次唤醒、多抢几毫秒的射频时间。所以"轻量、能随时睡"的诉求,会把设备端的选择天然推向无连接的 UDP 一族(CoAP/LoRaWAN 之上),而不是让它驮着一条永远在线的 TCP 管道。这也是"重不重在报文长度,更重在长期持有的成本"的又一注脚。
把上面全部压缩成一张能现场勾选的清单,遇到实际设备时按顺序打勾即可落定:
| 判据问题 | 答案为是则倾向 | 答案为否则倾向 |
|---|---|---|
| 这条消息绝对不能丢/顺序必须对? | TCP 或"确认制回执" | UDP |
| 设备是否常年上电、能长期在线? | TCP(长连接友好) | UDP(可随时睡) |
| 是否依赖无线且电量敏感须省着用? | UDP(弱调制/省功耗优先) | TCP |
| 每次连接都要先走一次低频握手,值得吗? | TCP(长数据流划算) | UDP(短消息划算) |
清单本身并不神秘——它只是把本节讲过的"可靠语义、长期成本、电量敏感、握手代价"四个维度整理成四个可回答的问题。手边没有文档时,照这份清单逐条点过去,多数场景很快就能收敛到一个明确答案,而不至于在两套协议间摇摆不定。
下一节把应用层协议排成全家福,对比 HTTP、MQTT 与 CoAP 谁更适合谁。