
先提一个反直觉的问题如果你说“TCP是可靠的UDP是不可靠的”在日常技术交流里基本不会有人反对。这句话几乎成了网络编程的入门共识面试题里也常拿它当标准答案。但如果我这几年调过跨地域专线、做过弱网环境下的视频传输、盯过Wi-Fi环境下的TCP重传统计我得提醒你这句话在原理层面是对的在工程层面却是一个过于简化、甚至会误导人的“hoax”伪命题。不是说TCP不好也不是说UDP神奇地变可靠了。而是“可靠”这个词的边界被大家下意识地模糊了。TCP确实提供了可靠的字节流传输但这种可靠是有前提的是需要路径上所有环节配合的甚至在某些极端场景下TCP表现出的“不可靠”一点都不比UDP少。反过来UDP虽然不承诺可靠交付但工程界早就发展出一整套在UDP之上建立可靠性的方案从游戏同步到WebRTC视频通话大家都在“不可靠”的UDP之上交付了可靠的业务。这篇文章我想把这个话题拆开聊透。既不捧TCP也不贬UDP而是从协议设计目标、真实网络环境、以及一堆我实际踩过的坑出发讲清楚“可靠”到底是由谁定义的什么情况下TCP的可靠会失效什么情况下UDP反而更适合业务以及我们做技术选型时到底该信哪句话。1. 内容整体设计与思路拆解1.1 “可靠”这个词被TCP和UDP都说烂了要聊清楚TCP可靠、UDP不可靠这个论断第一步得回到协议的原始设计目标上。TCPTransmission Control Protocol在设计之初想的就非常清楚要在不可靠的IP层之上向上层提供一个看起来像“本地文件读写”一样可信的字节流通道。为了实现这个目标TCP在协议栈里塞了一整套保障机制——序列号、确认应答、重传超时、滑动窗口、拥塞控制、流量控制。这些机制组合起来保证了这样一个性质只要连接还在应用层把数据交给TCPTCP就确保这些数据以正确的顺序、完整地到达对端。这个性质听起来很硬核但它有一个隐含前提路径上不能有设备主动切断连接物理链路不能持续恶化到无法维持连接的程度端到端的缓冲区不能长期溢出。一旦这些前提被打破TCP所谓的“可靠”就变成了有条件的可靠——而条件到底是什么大部分做应用开发的人其实是说不清的。UDPUser Datagram Protocol的设计哲学则完全相反。它不承诺可靠性不承诺顺序不承诺不重复甚至不承诺对端一定在线。它只做一件事把应用层给的报文原样塞进IP层然后就撒手不管。这种设计常被认为是缺陷但换个角度看UDP不承诺可靠恰恰意味着它没有为“可靠”付出任何代价——没有连接状态、没有重传、没有头部开销之外的额外负担、没有拥塞控制的约束这些特性让UDP在实时性优先的场景里变得异常好用。1.2 两个协议的设计目标对比要真正理解TCP和UDP的差异不能只看“可靠”和“不可靠”这个二元标签而要看它们各自为达成目标付出了什么。我整理过一个对比表能比较直观地看出两者的分工逻辑维度TCPUDP连接状态面向连接有完整的连接生命周期无连接发完即走数据边界字节流无消息边界报文保留完整消息边界可靠交付序号ACK重传保证有序完整不保证丢了就丢了流量控制滑动窗口防止接收方被冲垮无应用层自行处理拥塞控制慢启动、拥塞避免、快速重传无发送速率完全由应用决定头部开销20字节起步可选字段更多固定8字节能容忍的延迟重传和排队可能带来较大抖动无重传延迟抖动主要来自网络本身这张表背后的逻辑很清晰TCP把可靠性相关的复杂度全部收敛到了协议栈内部应用层几乎不需要关心数据会不会丢UDP把这一切复杂度全部留给了应用层协议栈只管透传。换句话说TCP替应用层承担了可靠性责任UDP把可靠性责任交还给了应用层。所以UDP不是不能可靠只是它不负责可靠可靠与否完全取决于上层怎么用它。1.3 这个“hoax”是怎么在工程里被放大的那么问题来了为什么“TCP可靠 / UDP不可靠”这句话会成为一种直觉并且在实际工程里带来误导我觉得至少有三个层面的原因。第一教科书和面试题把这句话简化成了一个判断标准没有带上前置条件。久而久之很多人就默认TCP等于不会丢数据UDP等于一定会丢数据。第二大部分人在局域网、本地回环环境下测试网络条件太好了TCP的可靠性机制几乎从不触发UDP的丢包也几乎为零测试结果反而让“TCP稳、UDP不稳”这个印象更固化。第三一旦换到公网、移动网络、跨地域链路TCP的重传、拥塞控制、队头阻塞这些问题会集中爆发表现出的“不可靠”与教科书描述严重不符但很少有人回头质疑那句口号本身的边界。我的观点很明确“可靠”不是协议的属性而是工程的成果。TCP提供了实现可靠性所需要的机制和框架UDP提供了实现可靠性所需要的自由和灵活性。两者都只是工具最终是否可靠取决于你如何理解网络、如何设计应用。2. TCP的“可靠”在真实网络中是如何失效的2.1 校验和TCP的可靠性审计并非完美很多人不知道TCP的可靠性保证里有一环是“校验和”但TCP校验和的设计强度其实一般。TCP头部的校验和字段只有16位虽然覆盖了TCP头和整个数据块但16位的校验和面对随机比特翻转、尤其是连续多位错误时漏检率并非为零。以太网帧和IP层还有各自的校验机制兜底但链路层的FCS帧校验序列同样不是绝对可靠Wi-Fi环境下偶发的静默损坏是真实存在的。说个我实际碰到过的案例。有一次排查一个跨机房的文件传输损坏问题两端都是TCP连接传输过程中没有任何重传和报错但最终落盘文件的哈希值跟源文件不一致。当时排查下来就是交换机链路上的偶发误码没有被TCP的校验和捕获数据被“可靠地”传错了。这个案例非常珍贵因为它让我意识到TCP的可靠是一个概率上的高可靠不是数学上的绝对可靠。对于极端重要的数据应用层加一层完整的校验和摘要再做比对永远是值得的。2.2 重传风暴与延迟放大重传机制的另一面TCP的可靠依赖重传但重传本身就是一把双刃剑。在丢包率较高的链路上TCP的行为是超时等待然后重传数据同时指数退避。这个过程带来的直接后果是数据虽然最终能送达但延迟可能从几十毫秒飙升到几秒甚至几十秒。举个例子我在一次跨地域专线调优中看到过这样的现象链路往返时延RTT本来约30ms但因为线路偶尔丢包TCP进入重传退避后单次数据传输的实际耗时可以达到数秒。对文件传输来说最终成功也算可接受但对实时交互业务比如远程操作、实时协作编辑、行情推送这种延迟放大是致命的。业务要求的是“尽量快”TCP却为了保证“最终到”付出了巨大的时间代价。这时候UDP反而好控制丢了就丢应用层自己决定是重发还是跳过至少不会让整条链路被一个丢失的包拖死。2.3 队头阻塞与连接级故障可靠传输的代价TCP的第三个问题是队头阻塞Head-of-Line Blocking。因为TCP保证有序交付所以一旦某个报文丢失后续已经到达的报文都卡在接收缓冲区里必须等丢失的报文重传成功之后才能一并交给应用层。这在丢包环境下特别要命。我做过一个HTTP/1.1和HTTP/2的对比测试在模拟1%丢包的链路上HTTP/2多路复用的优势几乎被队头阻塞吃掉因为底层那条TCP连接一旦丢包所有复用的流都得等着。这也是后来HTTP/3直接换用UDPCUDP即QUIC的根本原因——与其在TCP的字节流里做多路复用不如在UDP的报文之上自己做独立的可靠流这样一条流的丢包不会阻塞另一条流。除了队头阻塞TCP的连接级故障也不可忽视。TCP的可靠性是建立在连接之上的但连接本身会断。服务器进程崩溃、网络设备重置、NAT会话超时、运营商RST注入任何一个环节都可能让看似“可靠”的连接瞬间失效。连接断了之后TCP并不会帮你把数据“续传”应用层如果不能感知并恢复数据的丢失就是实实在在的。这时候你回头看那句“TCP是可靠的”会觉得很讽刺TCP保证的只是连接存活期间的可靠连接本身可能是脆弱的。2.4 内核缓冲区与背压被忽略的静默丢数据点还有一个容易被人忽略的细节TCP的可靠是针对“已提交给协议栈的数据”而言的但数据从应用层到对端应用层之间要经过多次内核缓冲区的拷贝和排队。任何一个缓冲区满了行为就会变得微妙。说一个具体场景。接收端的接收缓冲区如果因为应用层读取不及时而被填满TCP的流量控制机制会通过缩小窗口来阻止发送端继续发送这是TCP的优雅处理方式。但如果因为拥塞控制或某些实现细节内核丢掉了入队的数据且没有反馈TCP的重传机制通常会在之后补上。真正的问题是很多应用层代码假设“send成功数据到了对端”这个假设在TCP上也不完全成立。send只是把数据放进了内核发送缓冲区真正到达对端还要经历网络传输和对端接收处理。如果对端进程崩溃TCP连接会以RST或超时结束发送端那些“send成功”的数据对端一个都没收到。所以我在团队内部一直强调一个原则TCP的可靠是传输层的可靠不是应用层的可靠。应用层需要关心对端到底有没有处理成功需要自己的确认机制不能把一切寄托在“TCP不会丢数据”上。3. UDP的“不可靠”如何被工程实践改造成可靠3.1 先认清UDP到底丢在哪儿要说UDP不可靠首先得承认这个论断有其现实基础。UDP报文在传递过程中确实有可能因为中间设备缓冲满、链路拥塞、MTU分片丢失等原因被丢弃。在公网环境下UDP丢包率通常比TCP高这也不是错觉。但同时也得看清另一面UDP的丢包并不是均匀发生的也不是不可避免的。在我做过的局域网和公网对比测试里局域网环境下的UDP丢包率常常是零公网环境下的丢包也往往集中在特定链路、特定时段。换句话说UDP的“不可靠”更像是一种“上限风险”而不是“必然命运”。这跟TCP的重传放大延迟问题放在一起看有意思的地方就出来了丢包率低的网络里UDP的体验远好于TCP因为省掉了连接管理和ACK反馈的往返开销丢包率高的网络里TCP有重传兜底UDP裸用则惨不忍睹。所以UDP是否“不能可靠”取决于你愿不愿意在应用层补上TCP那一套机制。3.2 应用层可靠性设计在UDP之上重建秩序既然TCP的可靠是一套机制堆出来的那这套机制的理论是可以复用的——只是换成应用层来实现。经典的RUDPReliable UDP思路就是干这件事的在UDP报文上增加序列号、ACK、重传、超时、乱序缓冲区把TCP的核心机制移植到应用层。我实现过一个简化版的设计核心要素包括每个应用数据包附带一个自增的序列号接收端收到数据后周期性返回ACK带上已收到的最小连续序列号发送端维护一个发送缓冲区超过一定时间未确认的包就重发接收端维护一个乱序缓冲区序列号不连续的包先缓存等前序包到达后再按序提交给上层。这套机制写起来不难真正麻烦的是处理各种边界情况比如ACK本身丢失导致的重发风暴、重复包的去重、接收缓冲区的上限管理、带宽估算与发送速率控制。做一圈下来你会非常深刻地理解一句话TCP把苦活累活都干完了而“在UDP上重建可靠”就是把TCP的苦活累活重新做一遍只不过这次你能按业务需求做定制。实际工程里这种定制是有价值的。比如游戏同步场景业务关心最新状态的及时送达旧状态丢了无所谓所以发送策略往往是“只保留最新状态”而不是TCP那样按序重传所有数据。再比如音视频通话视频帧之间有关联但迟到的帧再补发已无意义所以应用层会选择“跳过重传”而不是“死等重传”。这些业务诉求是TCP那个二元的“可靠”模型根本无法满足的——TCP认为所有数据同等重要但业务知道有些数据过期即是垃圾。3.3 QUIC把可靠流嵌进UDP的现代答案如果要在今天的工程语境里谈“UDP之上建可靠”QUIC是无法绕开的名字。QUIC本质上是一个运行在UDP之上的传输协议它保留了TCP的可靠性语义——序列号、确认、重传、流量控制、拥塞控制同时又把可靠性拆成了多流并行的模型每个流独立确认、独立重传从根本上规避了TCP队头阻塞的问题。用QUIC做传输层的好处我在实际部署中感受很深。首先是连接建立耗时大幅降低TCPTLS的握手通常需要一个RTT以上QUIC可以在首个RTT内同时完成传输和加密协商。其次是在弱网环境下的表现QUIC对丢包的处理更加细粒度单流的丢包不会拖累整个会话。再加上连接迁移能力——IP地址变了连接还在这对移动端网络切换的帮助极大。我还得说一个细节QUIC虽然用了UDP但它对可靠性的实现比很多TCP实现还要严谨。它的数据包有完整的前向纠错和重传编号它的拥塞控制可以适应不同链路它的接收缓冲管理比传统TCP栈更精细。这个事实本身就是对“UDP不等于不可靠”的最好注脚——不可靠的只是裸UDP可靠与否取决于你往UDP之上堆了多少协议智慧。3.4 面向消息的设计UDP天然免粘包还有一个常被忽略的点UDP的报文边界让它在做消息通信时比TCP更干净。TCP是字节流没有消息边界应用层必须自行处理粘包与半包——这就是“tcp粘包处理”成为热门关键词的原因。而UDP一个报文就是一个完整消息应用层收一次就是完整包天然没有粘包烦恼。我做过高并发日志上报系统当时面临的选择就是TCP还是UDP。如果选TCP就得在应用层设计完整的分帧协议魔数、长度、序号、校验。如果选UDP每条日志一个报文直接带个序列号和校验就发出去接收端按序列号做乱序整理和去重反而代码量更少。最后我选了UDP加上简单可靠机制跑了大半年稳定得很。这件事也让我更坚定了一个想法可靠性不是传输协议的专利而是整体设计的产物。TCP只是提供了一种实现可靠性的现成框架不代表它适合所有业务。4. 实操过程与核心环节实现4.1 一步步做一个TCP与UDP的对比实验光讲理论不过瘾我建议你亲手做一个实验在同一台机器上用TCP和UDP同时向同一个接收端发送10万条消息然后在人为制造丢包的情况下对比两者的表现。这个实验能非常直观地打破“TCP绝对可靠”的印象。实验环境可以选择Linux虚拟机或者云服务器我用的是两台云主机中间网络有轻微丢包。发送端用同一个数据生成器产生10万条带序号的消息TCP侧把所有消息按字节流发给接收端UDP侧每条消息封装为一个报文。接收端分别统计收到的消息数、乱序数、按序完整率、耗时。实验的关键是控制变量两条路径并发同样的数据源同样的网络。TCP侧因为要建立连接实际耗时必然包含握手时间UDP侧因为没有连接管理纯传输耗时更短。如果网络上完全没有丢包TCP和UDP在数据完整性上的区别为零唯一区别是耗时和开销。如果加上丢包TCP会表现为“总耗时变长但最终完整”UDP会表现为“耗时稳定但数据残缺”——这时候你就能亲眼看到TCP的可靠是用延迟换来的UDP的不可靠是以牺牲完整性换来的。4.2 工具实测iperf3、Wireshark与tcpdump的使用经验光有自己的小实验还不够要想深入理解TCP和UDP在网络中的真实行为有几个工具是绕不开的。iperf3是最经典的打流工具。我以前专门测过一个跨机房链路命令大致是这么用的服务端iperf3 -s客户端打TCP流量iperf3 -c 服务器IP -t 60 -i 5客户端打UDP流量并指定带宽iperf3 -c 服务器IP -u -b 100M -t 60 -i 5这个测试的价值在于能看到TCP和UDP在相同链路下的吞吐和丢包对比。TCP会自动适应带宽跑满链路不在话下UDP如果指定的发送速率超过链路能力就会产生大量丢包而且会拖累整个链路的稳定性。我在一次测试中发现一个UDP打满带宽的会话能让同链路上的TCP吞吐掉一半——原因就是UDP抢占带宽导致TCP的拥塞控制不断误判丢包而退避。用UDP打流一定要控制速率不能无脑全速发否则会制造网络风暴。Wireshark是分析TCP行为的神器。抓包后重点看几个字段TCP Retransmission重传、TCP Dup ACK重复确认、TCP Zero Window窗口零。这三个信号分别对应丢包重传、乱序触发快速重传、接收端处理不过来。我排查线上问题时的经验是先把这三类事件的分布情况拉出来基本就能判断问题出在链路层还是应用层。tcpdump是命令行下的抓包利器在服务器上排查问题时比Wireshark更实用tcpdump -i eth0 tcp port 8080 -w tcp.pcap tcpdump -i eth0 udp port 8000 -w udp.pcap抓完包之后可以带回本地用Wireshark分析也可以直接用tcpdump的统计模式看汇总情况。实际工作中很多TCP的“灵异现象”——比如连接明明建立却发不出去数据或者收到一堆重复ACK——都能在抓包里现出原形。4.3 Python实测代码与核心结论这里我贴一个简化版的对比实验代码你可以直接拿去跑数据量可以根据网络情况调整。发送端主要逻辑import socket import time def tcp_send(host, port, total100000): # 接入业务约定的帧格式实际中先发4字节长度再发包体避免粘包 buf b for i in range(total): buf fmsg-{i}-.encode() bx * 64 with socket.create_connection((host, port)) as s: t0 time.time() s.sendall(buf) s.shutdown(socket.SHUT_WR) t1 time.time() print(fTCP total: {total}, bytes: {len(buf)}, cost: {t1 - t0:.3f}s) def udp_send(host, port, total100000): # UDP每条消息独立报文attach序号供对端校验 s socket.socket(socket.AF_INET, socket.SOCK_DGRAM) t0 time.time() for i in range(total): payload fmsg-{i}-.encode() bx * 64 s.sendto(payload, (host, port)) t1 time.time() print(fUDP total: {total}, cost: {t1 - t0:.3f}s)接收端主要逻辑import socket def tcp_recv(port): # 需要按自定义格式分割字节流但这里简化按缓冲收集后切分 lsock socket.socket() lsock.bind((0.0.0.0, port)) lsock.listen(5) conn, _ lsock.accept() chunks [] while True: data conn.recv(8192) if not data: break chunks.append(data) payload b.join(chunks) msgs payload.split(bmsg-) # 解析序号统计完整 print(fTCP recv total: {len(msgs) - 1}) def udp_recv(port, expect100000): s socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.bind((0.0.0.0, port)) found set() while len(found) expect: data, _ s.recvfrom(2048) # 提取序号 try: start data.find(bmsg-) end data.find(b-, start 4) idx int(data[start 4:end]) found.add(idx) except Exception: pass print(fUDP recv total: {len(found)})这段代码的价值不在于多么完善而在于暴露了最核心的差异TCP的接收端要解析字节流必须处理半包和粘包UDP的接收端每个包都是完整的。如果网络有丢包TCP发送端的耗时会明显增加因为重传要等超时UDP发送端的耗时几乎不变但接收端能收到的包数会明显少于发送数。我在一个有1%丢包的网络里跑过一次结论是TCP最终收到了全部的10万条数据但耗时比无丢包环境增加了差不多3倍且连接里出现了大量重传和窗口收缩UDP耗时基本没变但只收到了98300多条数据缺失接近1700条。这就是那句话的具象化版本TCP慢而全UDP快而缺。选择哪个取决于业务更需要“全”还是更需要“快”。4.4 可靠UDP的最小实现方案如果你需要在UDP之上做可靠传输又不打算直接用现成的QUIC库我给你一个最小可行方案的设计框架。核心思路是ACK序号超时重传。发送端为每条数据分配一个16位序号数据放入发送队列等待对应ACK。接收端收到数据后立即返回ACK带上已收到的序号。发送端为每条数据启动一个定时器超过500毫秒这个值可以根据网络的RTT调整仍未收到ACK则重发。接收端维护一个乱序表序号连续的提交给应用不连续的暂存同时通过ACK里的信息告知发送端还需要哪些序号。伪代码级别的实现大致是发送端# 每条消息: seq(4字节) payload inflight {} seq 0 last_ack 0 def send(udp_sock, peer, payload): nonlocal seq msg struct.pack(H, seq) payload udp_sock.sendto(msg, peer) inflight[seq] (time.time(), payload) seq 1 def on_ack(ack_seq): # 连续确认可以滑窗推进 for i in range(seq): if i ack_seq: inflight.pop(i, None) def retransmit(udp_sock, peer): now time.time() for seq_id, (t, payload) in inflight.items(): if now - t 0.5: msg struct.pack(H, seq_id) payload udp_sock.sendto(msg, peer)接收端recv_buf {} expected 0 def on_recv(data): nonlocal expected seq_id struct.unpack(H, data[:2])[0] payload data[2:] recv_buf[seq_id] payload while expected in recv_buf: deliver(recv_buf.pop(expected)) expected 1 send_ack(expected - 1) # 连续序号前的都确认这套代码非常轻量但已经具备了可靠传输的骨架。要上生产还有几个细节要处理ACK的间隔合并以避免洪泛、乱序缓冲的上限与淘汰策略、重复包的去重、拥塞控制的粗略估算。做完整之后你会发现你其实是在造一个私有版的TCP——但因为你控制着每一条策略可以按业务需求做取舍这正是UDP最迷人的地方。5. 常见问题与故障排查实录5.1 TCP场景下的坑现象一TCP连接建上了但发不出去也没报错。这种问题我排查过好几次最常见的元凶是接收端的接收缓冲区满了内核通告了零窗口发送端即使有数据也只能等窗口恢复。如果发送端的应用层对超时没有感知就会表现出“既不成功也不失败”的卡死状态。排查方法是用netstat看发送队列和接收窗口netstat -ant | grep 8080 ss -ant | grep -E Send-Q|Recv-Q输出里如果Send-Q持续不为零说明数据积压在本地发送缓冲区大概率是对端没读走或者窗口为零。现象二TCP连接频繁断开重连但应用层没逻辑问题。这种常见于跨地域长连接比如手机App与服务器保持的推送通道。原因通常是NAT会话超时把映射表项清掉了TCP连接被迫中断。解决方式一般是调短保活周期或者直接用支持连接迁移的协议。如果你还在用TCP比较实用的做法是在应用层做一个心跳心跳间隔小于NAT超时时间通常取30到60秒。现象三TCP传输速度上不去明明带宽很大。这里最常见的原因是接收缓冲区太小或者窗口缩放没生效导致链路无法填满。可以用iperf3确认打到哪个吞吐量再用sysctl调大内核参数sysctl -w net.core.rmem_max134217728 sysctl -w net.core.wmem_max134217728同时确认TCP窗口缩放选项在两端都开启。还有一个我以前踩过的坑是MTU问题一旦路径上存在MTU黑洞TCP会频繁降MSS重传吞吐量自然上不去。5.2 UDP场景下的坑现象一UDP发送没有报错但对端就是收不到。排查第一步永远是确认对端的socket有没有绑定正确的端口和IP以及防火墙有没有拦截UDP流量。第二步是抓包看发送端出去的包是否真的达到了对端主机。很多UDP“丢包”其实是防火墙规则挡掉的不是网络丢的。第三步才是怀疑真正的丢包比如发送速率超过链路带宽导致中间设备主动丢弃。现象二UDP接收端出现“unknown error code10054”Windows环境常见。这个错误其实是ICMP端口不可达消息导致的说明对端根本没在监听你发过去的UDP端口或者通信过程中对端进程退出了。排查办法是对端确认UDP服务是否还在跑以及检查接收缓冲是否溢出。这个错误的本质是ICMP反馈但UDP本身是不保证反馈的所以它更像一个附带情报。现象三UDP缓冲区溢出导致大量丢包应用层无感知。这是一个特别隐蔽的问题。UDP的接收端如果处理速度跟不上数据到达速度数据会在内核接收队列里排队。队列满了之后新到的UDP报文会被内核直接丢弃应用层完全不知道。TCP在这种情况下会通过流量控制让发送端减速UDP没有这种机制所以只能在应用层努力——加大缓冲区sysctl -w net.core.rmem_max26214400 sysctl -w net.core.rmem_default26214400同时让接收线程尽快把数据从内核队列里读走。如果还不行就得考虑在应用层做丢包统计和反馈机制让发送端降速。5.3 选型建议别再背“TCP可靠、UDP不可靠”的口诀了聊了这么多最后聊选型。我自己的判断框架是从这几个维度看业务需求维度优先TCP优先UDP数据完整性文件传输、数据库同步实时状态、音视频帧时效性不重要迟到也行极其重要过期即废消息边界需要自行分包天然消息边界连接保持长连接有状态无连接随时发弱网表现丢包后延迟暴增丢包后内容缺失但延迟稳定开发成本协议栈替你兜底应用层自己做可靠逻辑对应到具体场景文件同步、消息推送、数据库复制选TCP音视频通话、游戏位置同步、实时控制指令选UDP如果你既要可靠又要低时延可以选QUIC或者自己实现RUDP而不是硬扛TCP或裸UDP。物联网领域还有个常见的组合模式——控制指令走TCP数据上报走UDP。控制指令数量少、对可靠性要求高TCP的重传机制能确保指令送达传感器数据量大、时效性强、允许少量丢点UDP的高效透传正合适。这篇文章里我没有想说服你放弃TCP或者拥抱UDP我只想拆掉那句话——“TCP可靠、UDP不可靠”本身就是一个简化到失真甚至有点误导的“hoax”。可靠从来不是协议单方面承诺的而是网络环境、协议机制、应用层设计三者共同作用的结果。TCP给了你一套现成的可靠框架也把连接状态、排队延迟、队头阻塞这些负担一并给了你UDP把可靠性还给了你也把设计空间和全部责任同时交给你。我个人这几年的体感是遇到传输问题最不应该做的就是拿这句话出来贴标签。先看数据再看丢包再抓包分析最后根据业务要什么选择TCP、选择UDP加自己造轮子或者选择直接在UDP上跑QUIC。真正的可靠是在理解了“可靠有条件、可靠有代价”之后还能为业务选对协议、设计对机制的工程能力。