TCP与UDP深度解析:从三次握手到拥塞控制,彻底搞懂传输层协议选型
开头先聊几句题外话。TCP和UDP这两个词大概是网络领域里被讨论最多、也最容易被误解的一对概念。很多人张口就能说出“TCP可靠、UDP不可靠”“TCP慢、UDP快”但真到了抓包看流量、调接口超时、排延时抖动的时候又常常卡壳。这篇文章不打算写成教科书式的协议说明而是想站在实际使用的角度把这两兄弟的设计逻辑、工作方式和适用边界摊开来聊一聊顺便把我自己踩过的一些坑和摸索出来的判断方法一并写进去。我一直觉得搞懂一个技术点最好的方式不是背结论而是理解它为什么要这样做。TCP和UDP都不是凭空设计出来的它们背后是两种完全不同的网络哲学。一个追求“说到做到”一个追求“说完就完”。这篇文章会把这两种哲学背后的报文结构、连接机制、拥塞控制、适用场景逐个拆开配合我实际调试中遇到的案例来讲。适合刚入门网络编程的初学者也适合已经写了一阵子业务代码、想系统理清传输层思路的后端开发者。1. 为什么TCP要做这么复杂而UDP可以如此简单1.1 从一段实际数据说起一张表看懂两者本质差异先抛一张我整理的功能对比表后面所有内容都围绕这张表展开对比维度TCPUDP连接状态面向连接需三次握手无连接直接发送可靠性确认、重传、排序保证到达尽力而为丢包不重传数据边界字节流无边界数据报保留边界流量控制有基于滑动窗口无拥塞控制有慢启动/拥塞避免无传输模式全双工可靠字节流单次数据报交互头部开销20字节以上8字节适用场景文件传输、网页、远程登录实时音视频、游戏、DNS查询这张表不是我随便列的每一项背后都对应着实际网络问题的根源。TCP做了那么多事最终目的只有一个让上层应用觉得网络是完美可靠的。而UDP的设计哲学相反我不承诺任何可靠性把控制权全部交给应用层。1.2 报文的骨架TCP头部的每一个字段都不是白给的TCP头部固定20字节里面装着源端口、目的端口、序号、确认号、标记位、窗口大小、校验和等字段。每个字段都对应一个真实的问题场景序号Seq和确认号Ack用来解决“网络中的包乱序了怎么办”和“对方到底收到没有”的问题。发送方给每个字节编号接收方通过序号重排乱序数据通过确认号告诉发送方“我这边已经收到了哪些数据”。标记位SYN/ACK/FIN/RST等控制连接的建立、确认、断开和异常复位。比如SYN表示“我想建立连接”RST表示“我这边直接拒绝了”。窗口大小Window这个字段直接参与流量控制接收方通过通告窗口告诉发送方“我现在最多能接收多少数据”防止发送过快导致接收方缓冲区溢出。校验和所有TCP报文都要求强制计算校验和用来检测数据在传输过程中是否被破坏。1.3 为什么说UDP的8字节头部是“穷人家的孩子早当家”UDP头部只有固定的8字节源端口2字节、目的端口2字节、长度2字节、校验和2字节。没有序号、没有确认号、没有窗口。校验和在UDP里是可选的IPv4下如果计算出来为0表示发送端没有启用校验。这带来的直接后果就是UDP不需要维护状态。TCP的发送方必须记住每个包发没发出去、对方确认没有、计时器到了没有UDP的发送方把这些数据往socket一丢就完事了状态管理全部交给应用层。这种设计在大规模并发场景下很重要无状态意味着内存占用低、上下文切换开销小、单机承载的并发数可以远超TCP。但这里有个很多人容易误解的点UDP校验和可关闭不代表你可以不关心数据完整性。校验和本来就只有16位它只能发现比特级别的损坏发现不了丢包、乱序、重复这些问题。说白了UDP协议本身只保证“我在尽力送”不保证“你一定收得全”。这个差异带来的实际体验我之前在一场在线答题活动里体会特别深。当时客户端上报答题结果走的是TCP高并发下偶尔出现连接堆积后端同事分析后发现这种“每个请求都很小、但量非常大、允许少量失败后重试”的场景改用UDP传输再加应用层确认反倒把单机并发扛了上去延迟还降了不少。后来我把这个经验用在了其他项目上效果同样明显。2. 三次握手与四次挥手之间连接不是口头约定而是状态一致2.1 三次握手为什么恰好是“三次”很多人背过三次握手的过程客户端发SYN服务端回SYNACK客户端再回ACK连接就算建立了。但为什么不是两次、不是四次这个问题的答案才真正揭示了TCP的设计思路。两军问题在网络协议里是一个经典悖论一方发送消息说“我要进攻”另一方回信“收到”但发送方无法确认“确认消息”是否真的送达。只要有不可靠信道的存在没有任何握手次数能保证双方同时百分百确认。所以TCP设计的不是“双方互相确认”而是“双方各自确认一个事实”客户端在发出SYN之后需要确认“服务端收到了我的SYN请求并且愿意建立连接”服务端在收到SYN之后需要确认“客户端确实有建立连接的意愿”。三次握手特殊在哪关键在服务端的SYNACK这一个合并报文。如果分成两次就等于服务端先回一个ACK告诉客户端“我收到你的SYN了”再单独发一个SYN去建立反向通道。这样不仅多占用一次报文往返还增加了一次不必要的超时等待窗口。合并之后服务端用一个包同时承担两个角色确认客户的请求、发起自己的同步。更关键的逻辑在于三次握手让双方都对“通道可用”这件事做了验证。客户端第一次发SYN验证了客户端到服务端的发送通路服务端回SYNACK验证了服务端到客户端也是通的客户端再回ACK服务端才放心地认为“客户端能收到我的数据”。所以在三次握手完成之前TCP是没法传业务数据的——它还在验证通道。2.2 断开连接为什么是“四次”且经常出现“卡住”的现象断开连接用四次挥手原因比三次握手更容易理解TCP是双工的两个方向的数据通道要独立关闭。客户端说“我不发了”只代表它这一半结束服务端可能还有数据要发给客户端所以服务端要先回ACK确认收到但继续保持自己的发送通道等服务端自己也发完了再发FIN客户端最后回ACK整个连接才彻底关闭。实际调试中我见过最多的问题是客户端主动断开后服务端连接迟迟进不了TIME_WAIT、大量连接堆积在CLOSE_WAIT状态。这个状态的含义是服务端收到了客户端的FIN知道对方要关了但服务端自己的close()一直没被调用——通常就是业务代码里忘记释放资源或者有某个线程长期占着socket不放手。排查时用netstat -anp看状态分布CLOSE_WAIT如果只有一两个可能是某个请求特别慢一旦数量成片增长基本可以断定是资源泄漏。TCP状态机里还有一个时间很长的状态TIME_WAIT持续2MSL通常约2分钟。这个状态的意义有两个保证最后的ACK能到达对端如果丢了可以重发以及让旧连接的延迟报文在网络中自然消散不会污染新连接。代价是端口/四元组被占用一段时间。高并发短连接场景下TIME_WAIT数量会非常大触发Cannot assign requested address。常规解法是开启net.ipv4.tcp_tw_reuse表示允许在TIME_WAIT状态复用连接同时配合调低tcp_fin_timeout但要注意有些优化手法在特定网络环境里有副作用改动前最好先在压测环境验证一下。2.3 抓包验证握手过程中的真实时序纸上谈兵没意思我建议有条件的读者自己抓一次包。tcpdump抓TCP握手非常直观sudo tcpdump -i any -c 20 -nn tcp port 8080然后另开终端用curl请求一次本地服务。正常情况下会看到这样的交互12:00:01.123456 IP 127.0.0.1.50000 127.0.0.1.8080: Flags [S], seq 1000 12:00:01.123457 IP 127.0.0.1.8080 127.0.0.1.50000: Flags [S.], seq 2000, ack 1001 12:00:01.123458 IP 127.0.0.1.50000 127.0.0.1.8080: Flags [.], ack 2001[S]表示SYN[S.]表示SYNACK[.]表示ACK。注意第一个包的seq是1000第二个包返回的ack是1001——加1代表“我期待你下一个字节的序号是1001”这已经隐含了对SYN这个控制包本身的消耗。如果能看到这个字段变化说明你对TCP的序号机制已经建立了直观认知。三次握手的第三个包是[.]这其实也有个实用价值很多安全设备会放行首包SYN却对后续数据内容做深度检查。如果第三个包直接带上业务数据TCP Fast Open的做法部分旧设备会不合规地拦截。实际项目中开启Fast Open前需要确认全链路设备都支持。3. 拥塞控制从单机诚实到全局有序的复杂演化3.1 拥塞控制到底在控制谁流量控制管的是“发送方别把接收方缓冲区塞爆”拥塞控制管的是“发送方别把整条网络链路塞爆”。网络上的路由器和中间设备有自己的处理上限这跟接收方机器没有任何关系。如果大家都觉得“网络带宽够大”然后拼命发数据最终结果是所有流都被拖垮——这就是拥塞崩溃的可能路径。TCP的经典拥塞控制经历过一段混乱的演化期。我读书时实验室里跑过一个模拟实验两台主机传大文件把中间链路的缓冲配小结果整个实验室的延迟都异常变高。自己机器发的包没有问题但对端的回包和路由器的转发全部受影响。这个实验让实验室里所有人都意识到拥塞控制不是“自己管好自己”就能收工的它维护的是公共网络的秩序。TCP拥塞控制的核心状态机包含四个阶段慢启动连接刚建立发送方不知道网络水深水浅于是从很小的拥塞窗口开始每收到一个ACK指数增长快速试探可用带宽。拥塞避免达到慢启动阈值后窗口改为线性增长避免一次探得太多导致链路拥塞。拥塞发生重传/超时如果发生丢包或超时窗口直接折半或者重置并把慢启动阈值也调低。恢复阶段从新阈值继续探测逐渐回到最优窗口。这个机制最反直觉的一点是TCP在一个高带宽但高丢包的网络里吞吐量会表现得很糟糕。经典估算公式是吞吐量约等于窗口除以RTT乘以丢包率开方这意味着千分之一的丢包率在某些场景下就会让吞吐量腰斩。所以你会看到很多优化方案的思路不是“让TCP更激进”反而是让TCP感知不到丢包——比如在链路层做向前纠错或者干脆用UDP自己控制拥塞。3.2 我的抓包体会重传、快速重传和“三次冗余ACK”实际抓TCP包时我见过三次重复的ACK就是快速重传的典型标志。正常情况下ACK是一路推进的比如收到数据块1-1000回ack 1001收到1001-2000回ack 2001。如果中间丢了1501-2000这一块接收方拿到1001-1500回ack 1501收到2001-2500发现数据不连续还是会回ack 1501而且会连续回几次。发送方连续收到三个相同的ack之后不等超时立刻重传1501-2000这就是快速重传。这跟你去餐厅点餐是一个道理你不确定服务生有没有把你漏掉的菜记进系统但你又喊了三遍“请加一份菜”之后结论就很明确了。快速重传的触发条件之所以是“三次”是因为网络存在正常乱序一两个重复ACK可能只是数据绕了一下远路连续三个才基本可信确实是丢了。从抓包的层面看光凭[Retransmission]标记不能直接判断是超时重传还是快速重传要看重传发生前面跟着几个重复ACK。如果是连续三个[Dup ACK]后立刻出现重传基本是快速重传如果是沉默了很长时间才有重传就是超时重传——后者的代价高得多因为那段时间里发送窗口是停摆的。3.3 现代拥塞控制算法的争议我在生产环境里调过不少次拥塞控制算法reno、cubic、bbr都用过。BBR的思路跟传统算法本质不同经典的基于丢包的算法把丢包当作拥塞信号丢了就缩窗口BBR则去主动测量带宽和延迟的最小值然后保持在“带宽延迟积”附近运行不因为探测到一些丢包就猛砍窗口。实际效果在不同网络环境下差别挺大。在一些跨地域专线上BBR能明显改善吞吐但在公司内网这类低RTT、低丢包的环境里提升就很有限。换算法的时候注意一点拥塞控制算法的选择是两端共同生效的如果一端使用BBR而另一端默认使用CUBIC效果可能没有想象的那么好。而且拥塞控制不是满嘴跑火车的“调参数”它依赖的是持续监控数据的反馈。4. 选型不是二选一现实场景中的TCP与UDP各种折中方案4.1 到底该选谁看的是业务诉求排除了网络原理的距离实际写业务代码时选TCP还是UDP优先看几个具体的点对可靠性和有序性的容忍度文件上传、数据库同步、交易记录这类数据丢一个包都会出大问题基本没有悬念选TCP。对实时性和延迟的敏感度语音通话、在线游戏动作同步、直播推流这类场景丢一帧的代价远小于等一帧的代价选UDP更符合直觉。对发送节奏的控制权UDP把“什么时候发、发多少、丢了怎么办”这些决策全部还给应用层如果你需要非常精细地控制发送策略又不想被TCP的拥塞控制影响UDP是更顺手的基础。连接管理和服务规模TCP的连接管理和状态维护会带来一定的内存和CPU开销而UDP的服务器天然就是无状态的特别适合高强度并发请求。我见过不少团队一开始粗暴地选“打游戏用UDP、传文件用TCP”结果游戏服务器里反而不需要UDP因为玩家登录、背包数据、排行榜全部要求强一致而视频会议系统里某些信令通道选了TCP也没问题因为信令数据量小、又要求百分百到达。选型真的不是二元对立要拆到具体功能模块来定。4.2 折中路线用UDP底层加TCP思维这就是QUIC的思路QUIC目前还是基于UDP的上层协议但它把TCP的可靠性、有序性、流量控制功能全部在用户态重新实现了一遍。为什么要在用户态重做因为TCP的这些机制内嵌在操作系统内核里想要定制封装、减少握手延迟、避免队头阻塞统统要绕开内核协议栈而UDP恰好提供了一个完全自由的用户态起点。对业务开发来说直接面对内核TCP和直接面对QUIC的体验差别很大。TCP连接一旦建立就是一个持续存在的端到端状态换网络比如从Wi-Fi切到移动数据就要断线重连而QUIC的连接标识Connection ID独立于IP地址网络切换之后连接还在。这个特性对移动端应用的影响非常明显我之前维护过一款移动端消息推送服务弱网环境下TCP长连接频繁断连重登后来换到基于QUIC的方案重连次数大幅下降用户感知最明显的就是消息不再“收不到”。QUIC还顺手解决了一个让开发者头疼的老问题TCP队的头阻塞。HTTP/2虽然实现了多路复用但底层TCP按字节流有序处理一个TCP丢包后面所有数据都得等重传才能被上层使用。QUIC在单个连接内部划分了多个独立的流每个流独立传输数据丢包只影响自己那个流其他流照常推送。这个区别让我觉得传输层协议的选择最终拼的还是对业务模型的贴合度而不是简单比较“可靠”和“非可靠”。4.3 如果决定用UDP如何在应用层补上可靠性用UDP的思路写业务时一个常见的误区是“反正UDP丢包了应用层重传一下就好”等到真正实现才发现应用层重传远没有想的那么简单。你需要自己维护序号分配、确认应答、超时重传、乱序重排、流控最麻烦的是实现拥塞控制——如果不管拥塞网络一抖动你的重传风暴会让网络更差。我的建议是不要从零开始造轮子。先看有没有现成协议比如RUDP、UDT、KCP这类基于UDP的可靠传输库能覆盖大部分场景如果业务特色太强也可以直接在成熟实现上做二次开发。有些直播场景对延迟要求极高甚至允许丢失少量帧那就用UDP加FEC前向纠错把丢失的数据直接算出来不用等重传——比如发送10个原始包额外带2个校验包接收方拿到其中10个就能还原全部数据代价是多一部分带宽开销。补一层序列号是必须的。UDP的乱序情况比TCP更常见尤其跨运营商网络时两个包走的路径都可能不同先发的包后到很正常。没有序号就只能靠应用层的时间戳判断新鲜度但是迟到但合法的旧包可能会顶掉新包这是很多UDP应用层协议的隐性Bug。4.4 实测中我自己一定会监控的几个指标无论用TCP还是UDP线上监控不能只看“连接数”或“丢包率”这种笼统指标。比较值得关注的是这几个TCP重传率Retransmission Rate。正常网络环境下低于0.5%如果超过去了基本说明链路开始拥堵或者底层硬件有问题。拿netstat -s可以看全局计数单连接则要通过抓包算。TCPRTT波动。平均RTT高不算可怕可怕的是RTT一会儿50ms一会儿800ms这种抖动对实时交互的伤害远大于单纯的“高延迟”。UDP接收缓冲区丢包数。UDP没有流控如果应用层处理慢内核接收缓冲区满就直接丢包。netstat -s里的packet receive errors和UDP socket buffer overflow都指向这个问题。UDP发送端的发送队列长度。发送队列积压基本意味着应用层生产速度超过了下游处理速度说明架构的消费链路是该扩容了。有一次我在线调试一个游戏对战服玩家普遍反映操作有“卡手感”但TCP链路看起来一切正常连接数稳定、延迟不高。后来单独抓了UDP的包才发现问题出在NAT会话表老化上玩家长时间不动内网映射被回收了下一次UDP包发过去直接超时应用层等重传才恢复。这个案例给我的教训是UDP的“快”是建立在应用层足够勤快的前提上的服务端要自己收发心跳、做映射保活否则一个静默期过去客户端就“失联”了。5. TCP的状态细节TIME_WAIT、全双工关闭和“半关闭”的坑5.1 TIME_WAIT不是垃圾而是TCP自我保护的最后一道防线很多人在高并发短连接场景下看到大量TIME_WAIT就觉得“坏掉了”急着去调内核参数。但TIME_WAIT是一个正确且必要的状态。它的存在解决了整个网络环境中最棘手的两个问题一是“如果最后的ACK丢失怎么办”二是“旧连接的重复包会不会污染新连接”。TIME_WAIT期间那个四元组源IP、源端口、目标IP、目标端口不允许被立即重新使用必须等2MSL保证网络上残留的旧报文有时间被自然销毁。假如没有这个等待立刻复用同一个四元组建立新连接而旧连接的一个迟到的数据包恰好在这个时间到达接收方会因为它属于“旧连接”直接丢弃——但发送方可能认为它已经被新连接接收从而出现数据错乱。有一种完全正常的场景就是服务器主动发起关闭比如HTTP短连接服务。客户端请求完关连接客户端侧出现大量TIME_WAIT服务端响应完关连接服务端侧出现同样的情况。所以看到TIME_WAIT先判断是谁关的、关了多少、是否影响到新连接建立。真正影响业务的不是TIME_WAIT本身的数量而是它占用的端口资源导致新连接无法建立。5.2 半关闭和shutdown()比你想的更常用TCP的“四次挥手”在设计上其实就是让双方各自独立关闭单方向的传输这种机制允许“半关闭”状态的存在一端停止发送数据但仍然可以接收数据。实际代码中很多Bug源自清楚close()和shutdown()的区别。close()会把引用计数减一当计数归零时才真正关闭socket如果这个socket被多个地方引用close()并不会立刻触发FIN。shutdown()则不同它立即关闭连接的一个方向或者两个方向不管还有没有其他引用。举个典型的例子客户端想告诉服务端“我发完了没有更多数据了但我还想收你的响应”。用close()可能把整个连接关掉服务端还在发数据就得到“Connection reset by peer”。正确做法是用shutdown(SHUT_WR)先关闭发送方向服务端看到EOF就会返回响应然后客户端再正常收完数据再close()。这个区别在实际代码里很容易被忽略但造成的故障现象千奇百怪。5.3 握手阶段的安全审视建立连接的核心目标是“互通有无”但从安全角度来说三次握手也有一个被利用多年的问题SYN Flood。攻击者不完成握手只持续发送SYN包服务端要维持半连接队列资源就被拖死。经典缓解手段包括SYN Cookie、缩短SYN超时时间、开启tcp_syncookies等。SYN Cookie的原理是服务端在收到SYN时不分配完整连接资源而是通过一个计算好的Cookie作为初始序号返回等客户端回ACK时再校验Cookie并分配连接。这样即使攻击者不继续握手服务端也不会被半连接耗尽资源。它本质上是把“资源确认后置”不知道你靠不靠谱之前先不给你占位置——这个思路在很多抵御资源耗尽攻击的场景都适用。6. 实践之路自己从零搭一个实验环境体会原理6.1 搭建一个最小验证环境如果只停留在看文章层面还是不够的我鼓励读者动手搭一个最小实验环境来验证协议行为。最简单的方案是本地用Python开两个socket程序一个TCP的echo服务一个UDP的echo服务# TCP echo server import socket s socket.socket(socket.AF_INET, socket.SOCK_STREAM) s.bind((127.0.0.1, 9000)) s.listen(5) while True: conn, addr s.accept() data conn.recv(1024) conn.sendall(data) conn.close()# UDP echo server import socket s socket.socket(socket.AF_INET, socket.SOCK_DGRAM) s.bind((127.0.0.1, 9001)) while True: data, addr s.recvfrom(1024) s.sendto(data, addr)跑起来之后用tcpdump分别抓两个端口的流量你会看到完全不同的画面TCP端口上一堆握手、确认的包UDP端口就只有一问一答。这种视觉上的差异比我写一万字解释都有用。从这两个例子里还能观察到一个非常有意思的对比TCP的recv返回的是连续的流你永远不需要关注“这一包”和“下一包”的边界而UDP的recvfrom每次返回一个完整的数据报多发的数据会被截断丢弃如果缓冲区不够大。这个边界差异在很多协议的编码过程中都会带来微妙的Bug——TCP需要考虑粘包拆包UDP需要考虑报文大小上限和分片。6.2 一个值得亲手做的丢包实验有条件的话可以搭一个简单的网络模拟环境来控制丢包率。Linux下可以用tc命令模拟# 在模拟网卡上添加5%丢包率 sudo tc qdisc add dev eth0 root netem loss 5%然后对TCP和UDP分别传输同样大小的文件观察吞吐差异。TCP在5%丢包率下吞吐会明显下滑因为重传占用了大量带宽UDP的吞吐看起来稳实际上数据少了很多接收端如果不做完整性校验根本发现不了。这个实验最核心的收获是UDP的“稳”是假象数据根本不是到的而是应用层选择性忽略了“快”也只是因为不需要等待确认而已。两个都不可靠区别只在于一个把可靠性放在协议里一个把可靠性交给应用。6.3 抓包时最容易遗漏的细节抓包时我吃过不小的亏这里写几点提醒把-n参数加上否则tcpdump会做反向DNS解析不仅慢还会干扰输出。只看握手包很容易漏掉问题要过滤具体的数据流向比如host 192.168.1.10 and port 9000。本地回环接口抓包时要带上-i lo很多人抓本地流量发现抓不到就是因为忘了指定接口。抓包时如果平台支持尽量用-w保存成pcap文件再用分析工具慢慢看比在终端里直接滚动输出好用得多。我最开始对协议的理解停留在看接口文档、调库函数直到某一次线上问题排查抓了几百MB的pcap文件逐包分析之后才真正意识到“协议头部每一个比特都对应一个为了生存而设计的决策”。也希望大家在做实验的时候能有同样的体会。7. 最后再补充一点我的实践经验文章写了这么多最后聊聊我实际操作中积累的几个感受。第一不管是TCP还是UDP都要从“端到端”的角度去思考而不是只看本地。中间可能有无线路由器、光猫、跨地域专线、云厂商负载均衡每一层都可能改协议行为。我遇到过一次诡异的UDP丢包最后定位是云服务商的负载均衡实例默认关闭了UDP健康检查导致后端直接收不到流量。这种问题看什么指标都没用只能顺着链路逐跳排查。第二调优TCP参数是有代价的。网上一搜能搜到一堆“优化”命令比如把缓冲区调到最大、关闭延迟ACK、放开TIME_WAIT复用但每一个操作都是取舍。盲目堆参数而忽略业务模型可能把一个小问题放大成大故障。改动任何内核参数前先在压测环境模拟真实流量跑一遍对比改动前后的行为再说。第三新协议不会取代旧协议只会让选择变得更丰富。TCP仍然非常适合大多数需要可靠传输的场景而UDP让实时性和控制权重回应用层QUIC则在两者之间提供了一个更现代化的选项。懂得传输层每种机制的适用范围比争论“哪个协议最好”有意义得多。最后分享一个非常实用的小技巧判断某个网络问题跟哪一层相关可以在自己机器上多开几个流量窗口——大文件TCP传输、实时UDP通话、普通网页浏览同时跑观察它们在问题发生时各自的表现。如果TCP文件传输卡顿、网页也打不开而UDP通话还能进行那多半是带宽拥塞TCP的拥塞控制被触发了如果UDP通话也断断续续那才是链路本身质量不行。这种“多协议对照组”的排障思路比盯着单一指标瞎猜靠谱不少。希望这篇文章能帮你在TCP和UDP之间建立起一个立体的认知框架。协议是工具不是信仰。拿到具体场景看清它要什么再做决定就不会再犯“一提到可靠性就选TCP、一提到低延迟就选UDP”这种表面化错误了。