尧图网站设计 尧图网站设计YAOTU DESIGN
ARTICLE DETAIL

资讯详情

深耕网站设计与一线实操的经验洞察。

TCP与UDP核心机制详解:握手、重传、粘包及工程选型实战

TCP与UDP核心机制详解:握手、重传、粘包及工程选型实战 搞网络通信的人聊来聊去绕不开两个词TCP和UDP。我最早把这两个协议彻底搞明白是在实验室里用Wireshark抓包的时候——一边看握手包一边对着RFC文档翻翻了整整一个下午。那次之后我才意识到学校课本上那几页讲不清楚的东西实战中每天都有人在踩坑。这一篇我尽量用大白话把TCP和UDP的核心机制、工程用法、排障思路讲透该给结论的地方给结论该给命令的地方给命令你可以当成一篇随时能翻的实战笔记来用。TCP和UDP是传输层协议它们解决的问题是“数据从一台机器的网卡出去怎么到达另一台机器的某个程序”。IP协议负责把数据送到正确的机器但机器上有那么多进程在跑数据到机器之后给谁这就要靠端口号而TCP和UDP就是这个层面上的两大主力。只要你在写网络程序、排查连接故障、或者做系统架构选型这两个协议就是绕不开的地基。这篇文章适合刚入门的学生、写业务代码的工程师、做网络维护的运维同学。我会从设计思路讲起再深入到具体机制和踩坑实录争取让你读完就能直接用在排查和设计中。1. 先搞清楚TCP和UDP到底在解决什么问题1.1 从IP层说起数据传输为什么还需要传输层很多人一开始常犯一个迷糊我会用ping也会写socket但IP层和传输层的边界在哪里其实网络分层里IP层负责主机到主机的寻址它能把一个数据包从192.168.1.100这一台机器送到192.168.1.200那一台机器。但送到之后呢这台机器上可能开着Web服务监听80端口、SSH服务监听22端口、数据库监听3306端口网卡收到的数据到底交给谁IP层不管。传输层就是在IP之上加了两样东西端口号和数据传递规则。端口号解决“给哪个应用”规则解决“怎么保证数据正确到达”。TCP和UDP走的是两条完全不同的路线TCP像一个教养很好的人在正式通话前先寒暄确认身份通话中每说一句都要确认对方听清了UDP则像街头扔传单扔出去就不管了至于有没有人捡到、顺序有没有乱没有下文。说句题外话应用层协议五花八门HTTP、HTTPS、MQTT、Modbus TCP、RTSP这些名字听起来很高大上但往下拆开看绝大多数都跑在TCP或者UDP之上。网络层和传输层是骨架应用层只是骨架上的血肉。你把TCP和UDP理解了上层协议里那些奇奇怪怪的现象一半以上都能找到底层原因。1.2 两种不同的设计哲学打电话 vs 寄明信片我在给同事讲这两个协议时最喜欢用两个生活例子。TCP是打电话你先拨号响铃对方接起来说“喂”你说“听得见吗”对方说“听得见”双方确认链路建立然后你一句我一句聊天。中途如果对方没听清会说“你刚才说什么”这就是重传机制。最后说“先这样吧拜拜”挂断谁也不会怀疑电话已经结束。UDP是寄明信片你把想写的话写上去贴上邮票丢进邮筒。明信片到不到、什么时候到、到的时候有没有被折角破损邮局不给承诺收件人如果没收到也不会自动重发一次。对于某些场景这个特性太合适了比如实时视频通话里丢掉一帧画面下一帧就来了与其花时间重传旧画面不如赶紧传新的。我把两个协议的核心差异用一张表拉一下方便后续展开维度TCPUDP连接状态有连接需要三次握手建立四次挥手释放无连接发数据报时不需要预先建立链路传输单位字节流应用层数据被拆成报文段分段发送数据报每个报文独立传输可靠性确认、超时重传、排序去重保证字节不丢不乱不保证送达不保证顺序不保证不重复流量与拥塞控制有滑动窗口、拥塞控制算法没有发送速度由应用自己决定头部开销20字节左右字段多8字节定长非常精简典型场景文件传输、Web访问、数据库连接音视频流、游戏同步、DNS查询、DHCP这张表看下来你就明白TCP和UDP不存在谁比谁高级它们的取舍逻辑完全不同。TCP用复杂机制换可靠性UDP用极简设计换速度和低延迟。下面我深入到TCP内部把三次握手、四次挥手、重传机制、粘包问题逐个拆开这些知识点在面试里高频出现工程里也常常就是排查断点。2. TCP核心机制拆解连接、可靠以及你一定会踩的坑2.1 TCP三次握手为什么非得是三次TCP建立连接的过程叫三次握手具体是这样客户端发一个SYN报文标志位SYN1里面带着客户端的初始序号seqx。服务端收到SYN后回一个SYNACK报文SYN1ACK1服务端自己的序号seqy同时确认号ackx1。客户端收到SYNACK再发一个ACK报文确认号acky1。三次握手完成连接建立。整个过程看着简单但每个字段的设计都对应一个问题。为什么服务端不直接读了SYN就算连上了因为有太多意外情况。比如客户端发的SYN在网络中滞留了很久服务端才收到并回复如果服务端不确认客户端的接收能力就认为连接可用然后傻等客户端发数据双方信息就不对称了。三次握手的本质是让双方各自确认两件事我的发送能力正常你的接收能力正常。第一次SYN到达服务端知道客户端的发送能力和自己的接收能力正常。第二次SYNACK返回客户端知道自己的发送和接收、服务端的发送和接收都正常。第三次ACK到达服务端才确认客户端的接收能力正常这样双方才敢放心传数据。还有一点值得说握手阶段不带应用数据。为什么这样设计因为如果握手包本身就能携带数据可能会把连接尚未建立的半开状态和真实业务数据混在一起网络层一旦重放或乱序应用层处理起来就非常麻烦。所以建立连接这个动作本身要轻数据等到状态稳定后再开始流动。我在实际抓包中见过不少三次握手出问题的场景最常见的是客户端一直发SYN、服务端不回SYNACK。原因无非几种服务端端口没监听、防火墙把SYN丢了、服务端的半连接队列满了。最后一种在高并发下很典型服务端accept不出来Linux内核里保存半连接的队列syn队列被塞满新连接一律应答不了。排查思路通常是看ss -ant里有大量SYN_RECV同时看netstat -s里的SynDrops计数然后再调内核参数net.ipv4.tcp_max_syn_backlog或者net.core.somaxconn。2.2 TCP四次挥手以及TIME_WAIT这个老冤家断开一个TCP连接流程比建连多一步称为四次挥手。假设客户端主动关闭客户端发FIN表示“我的数据发完了准备关”。服务端回ACK表示“知道了你关闭但我这边可能还没发完”。服务端把自己剩余的数据发完再发FIN表示“我也准备关了”。客户端回ACK确认服务端也关闭。为什么关比开多一次因为TCP连接是全双工的每个方向的通道都要单独关闭。客户端说关闭只是关闭了自己的发送方向服务端可能还在往客户端传数据所以服务端先回ACK确认收到关闭请求但数据继续传传完了再发FIN。这四个包中任何一个被丢掉连接关闭就会延迟甚至留下异常状态的socket。四次挥手里最出名的状态叫TIME_WAIT而且它只出现在主动关闭的一方。客户端发完最后那个ACK之后不会立刻进入关闭状态而是进入TIME_WAIT持续2个MSL的时间。MSL是报文最大生存时间RFC建议2分钟实际Linux上通常配置为30秒到1分钟TIME_WAIT时间就是2MSL。这个状态让很多人困惑甚至把它当成单纯浪费资源。但它存在的两个原因非常关键。第一客户端最后的ACK可能丢失服务端等不到ACK会重新发FIN如果客户端已经彻底关闭无法响应服务端会一直收不到确认。TIME_WAIT状态允许客户端在2MSL里应对这个重试。第二防止旧连接的迟到报文污染新连接。如果客户端立刻用同样的四元组建立新连接而旧连接里有一个报文迟迟没到达网络新连接可能收到脏数据。等待2MSL确保属于旧连接的所有报文要么已经到达、要么在网络中消亡。工程上TIME_WAIT带来的典型问题出现在频繁短连接的客户端上。我处理过一个Java客户端的报错连接代码没问题但每次断开后迅速重连就抛“Address already in use”。仔细排查发现客户端代码里绑定了固定的本地端口连接关闭后本地端口进入TIME_WAIT短时间内没法复用。解决办法有两个方向一是在客户端socket上设置SO_REUSEADDR让本地端口可以复用二是不要手动绑定本地端口交给操作系统从动态端口范围随机分配。很多时候问题的根源是代码非要给自己找一个固定端口这是完全没必要的。2.3 确认、超时重传与Dup ACK机制TCP保证可靠靠的是三件套序号、确认号、重传。每个TCP段里的seq表示这个段第一个字节在字节流中的位置ack表示“我已经正确收到了期望的字节序号”。发送方发出数据后启动一个计时器如果超时还没等到确认就重传。问题在于超时时间设多少。理想情况下重传计时器要略大于往返时延RTT。Linux内核里有一个动态的RTO估算根据历史的RTT采样不断调整。RTO太短会导致大量没必要的重传浪费带宽太长则会让真实丢包迟迟得不到恢复用户感受到明显卡顿。这也能解释为什么跨地域传输数据时网络一抖TCP会话就要重新收敛因为RTT变大之后RTO也会跟着变大。还有一种比超时快得多的重传机制叫快速重传。接收方收到乱序报文时会针对期望的序号回复重复确认Dup ACK。比如接收方期望seq100但先收到了seq105它不能确认于是再回一个ack100之后每收到一个乱序数据都重复ack100。发送方连续收到三个相同的Dup ACK就会判断中间数据丢了不等超时直接重传。这里有个细节值得琢磨为什么收到一个或者两个重复确认时不触发重传因为网络报文乱序也会导致重复确认比如seq101的报文本该先到结果seq102先到接收方回Dup ACK是正常现象只说明顺序轻微颠簸不代表丢了。等到三个重复确认出现乱序是解释不通的大概率是中间确实丢了包这时候就该快速重传了。所以Dup ACK机制的频率既是网络状况的风向标也是拥塞控制算法判断丢包的重要依据。2.4 一定会遇到的TCP粘包与拆包问一个写过网络程序的人很少有人没被粘包恶心过。其实TCP本身没有“包”的概念它只提供字节流。应用往socket里写入两段数据操作系统完全可能把这作为一段数据交给对端这就是粘包反过来如果一段应用消息比较大TCP网络层也可能分几次才送完这就是拆包。粘包怎么产生的主要三个方向发送端开启Nagle算法把小数据合并成一段后再发接收端读socket不及时多个写操作的数据在缓冲区里攒到一起或者数据大小本身小于接收缓冲区一次性被读走多段。粘包的“锅”是传输层的特性决定的应用层如果不定义好边界数据边界就没了。解决粘包的思路归根结底是应用层自己加“消息边界”。实际生产里主流的做法有三种第一固定消息长度每个消息固定N字节不足补位简单但浪费空间第二使用分隔符比如以换行符或者特殊字节序列终结一条消息适合文本类协议HTTP早期就接近这种思路第三长度前缀法这也是我推荐的最通用方案每个消息前面加一个固定长度的整型字段先读这个字段再按长度读消息体。我写代码时通常这么处理。发送方用struct.pack(I, len(payload)) payload拼包接收方先读4字节得到长度再读对应长度字节循环直到读完一个完整消息。这里需要注意一个坑网络读不一定一次读完不能简单用一次recv拿到的数据来判断长度要自己在内存里维护一个缓冲区反复读取解析。举一个不完整的示意import socket import struct def recv_exact(conn: socket.socket, n: int) - bytes: buf b while len(buf) n: chunk conn.recv(n - len(buf)) if not chunk: raise ConnectionError(socket closed) buf chunk return buf def recv_message(conn: socket.socket) - bytes: # 4字节大端整数作为长度前缀 length struct.unpack(I, recv_exact(conn, 4))[0] # 再按长度读消息体避免拆包问题 return recv_exact(conn, length)这里反复调recv是基本功写一次之后你会发现TCP编程的复杂度实际上不在收发而在怎么把字节流切出正确的业务消息。UDP没有这个烦恼因为UDP一次recvfrom收到的就是一个完整数据报但是UDP给你的是更大的烦恼它可能丢。2.5 滑动窗口与拥塞控制TCP可靠之外还要兼顾效率两个核心机制控制它的速度滑动窗口和拥塞控制。滑动窗口解决的是“对方能收多少”的问题。接收方会在TCP头部的Window字段里通告自己当前还能接收的字节数发送方发数据不能超过这个窗口大小这个设计本质上是端到端的流控防止发送方太快把接收方的缓冲区撑爆。而拥塞控制解决的是“网络上能不能扛住这么多数据”的问题。它不关心接收方而是看网络中间设备的处理能力。TCP想发多少数据得先探测网络的容量。经典的四部分慢启动、拥塞避免、快速重传、快速恢复。发送方以一个很小的窗口开始如果没有丢包窗口指数增长这叫慢启动窗口增长到阈值后转为线性增长进入拥塞避免一旦检测到丢包立即调低窗口进入快速恢复。正是这个机制让TCP在拥塞的网络里主动“自我减速”避免网络被无节制的数据彻底塞死。这个机制带来的直接观感就是TCP连接的带宽利用率不是恒定的而有明显的爬坡和回落过程。如果你从一台机器往另一台机器持续传一个大文件观察每秒的速度会看到开始阶段速度快速上升然后就稳定在一个水平上偶尔因为丢包突然降一截再爬回去。理解这个动态过程对定位“为什么内网传文件一开始快后来慢”这类问题很有帮助——不一定是硬盘或网线的问题而是TCP拥塞控制收敛到了某个平衡点。3. UDP的工作原理与典型应用场景3.1 UDP为什么快它快在哪里UDP快不是因为里面用了什么神奇的加速算法而是因为省了大量步骤。没有握手数据说发就发没有连接状态内核不需要维护连接表没有确认和重传省掉了大量来回的控制报文没有滑动窗口和拥塞控制发送速率完全由应用决定。它在内核协议栈里的处理路径非常短也不需要关心对方有没有接收能力每个数据报独立成了“发完即走”的最小单元。有一种理解方式很直观TCP的每个字节都可能影响全局状态序列号、窗口大小、拥塞窗口交织在一起内核处理一个TCP包要查多张表而UDP呢拿到一个数据报查一下端口映射把数据扔到socket接收队列就完事了。CPU开销低延迟低抖动小这就是UDP能承载实时音视频和游戏底层流量的根本原因。但你必须清醒地认识到UDP“快”的代价是它把可靠性责任交给了应用层。UDP不保证消息一定会到不保证顺序也不保证不重复。当你说“我这个业务用UDP就行”的时候意味着你已经在业务层面想好了丢数据怎么办。3.2 UDP的典型战场DNS、游戏、音视频、传感器有几个经典场景选UDP几乎是理所当然的。先看DNS。DNS查询就是这么个模式客户端发一个解析请求服务端回一个回答一问一答如果请求丢了客户端超时后重发一次就行。用TCP要做三次握手成本反而高。不过DNS也保留了TCP模式遇到响应特别大必须走TCP来保证完整传输但这属于特殊情况。再看在线游戏。MOBA游戏里玩家位置、血量等状态同步的频率很高每秒少说几十次。如果某个状态包丢了重传旧包没有任何意义因为服务器早就有更新的状态了。这个时候接收端只关心“最新一帧”旧数据直接丢掉才好。UDP在这里丢弃旧数据反而是优点。音视频通话更是如此。RTP协议承载媒体数据跑在UDP上WebRTC的全套音视频方案也是基于UDP的。视频里丢了一帧下一帧承载的信息往往能补上感知上的空档。如果返回去重传一帧等它到达整个画面就卡在那里体验反而更差。丢包率在一定范围内可以被音视频算法掩盖但延迟一上去就彻底没法看了。IoT传感器上报也经常用UDP。很多环境监测设备上报频率高、单条数据短而且传感器数据本身是周期性的连续量丢一条下一条还有同样的趋势业务上完全可以容忍。这种情况下用TCP维护一大票长连接反而是资源浪费用UDP上报网关收到多少算多少数据落库时按时间序列自然补齐成本低得多。3.3 用UDP不等于放弃可靠性应用层怎么补很多人一听说UDP不可靠就绕道走但你得看具体场景。“不可靠”三个字说的是传输层的努力程度不代表业务一定要接受丢数据。工程上最常见的做法是在应用层自建可靠性机制。举几个常见手段。序列号是必须的每个数据报加一个递增序号接收方可以检测丢包和乱序。确认与重传可以做但要注意粒度不是每个包都要确认而是按业务周期批量确认。比如游戏引擎里每个逻辑帧带一个序列号接收端记录最高连续序列号发现缺口就通知发送端“你重传一下某些包”但是重传优先级永远低于最新状态。这背后的核心思想是确认机制的目标是让丢包率可控而不是保证每个字节都到达。业务逻辑自己决定“哪些包必须到达哪些包可以丢掉”。站在协议选型角度QUIC是近年非常有价值的混血方案。QUIC跑在UDP之上但它提供了类似TCP的连接建立、加密、可靠传输、多路复用能力。为什么谷歌要这么设计因为传统TCP如果要在传输层加加密或者改头部需要动内核、挨个升级设备周期极长基于UDP应用层自己做握手和可靠控制绕开了操作系统协议栈的改动成本。如果你在一个新项目里做低延迟且需要可靠性的通信优先调研一下QUIC它可能是比“自己写UDP重传协议”更稳的路。4. 工程排障与实操从抓包到命令帮你把坑填上4.1 用Wireshark和tcpdump还原TCP/UDP会话现场排障第一步永远是抓包看数据别猜。我处理过的很多连接问题单看日志完全对不上一抓包立刻真相大白。Linux服务器上用tcpdump是最标准的路子tcpdump -i eth0 tcp and port 8080 -nn -w /tmp/tcp.cap这里-i指定网卡-nn表示不反解域名和端口-w把原始包存下来。抓到之后把文件拖回本地用Wireshark打开。要看三次握手和四次挥手用Wireshark的统计菜单里流量图功能一眼就能看清SYN、ACK、FIN的时序。如果被UDP干扰注意力就把过滤条件设成tcp或者udp。确认重传和Dup ACK是TCP排障里的重点。Wireshark的Expert Information面板会把“Malformed Packet”“TCP Retransmission”“Duplicate ACK”“Fast Retransmission”这类异常事件全部标出来用颜色分严重级别。看到一个连接里出现大量重传优先怀疑丢包链路。如果重传集中在某个IP或者某个端口段回头检查交换机的端口统计、防火墙的丢包日志。UDP抓包相对简单因为UDP没有握手也没有状态一个包一个案。抓下来看数据报大小和到达间隔能直接反映发送节奏。如果客户端收到大量乱序的UDP包大概率是中间走了多条路径或者网络设备做了负载均衡应用层只做UDP不做序列处理就很容易出现数据错乱。4.2 常用工具速成ss、netstat、iperf3、nc排查TCP连接时ss是首选命令比netstat信息更全更快。查看实时连接状态ss -ant输出里有一列State常见的值有LISTEN、ESTABLISHED、SYN_SENT、SYN_RECV、CLOSE_WAIT、TIME_WAIT。SYN_SENT表示本端发出SYN后没收到回复说明对端不可达、防火墙拦截、或者对端根本没监听。SYN_RECV在生产上大量出现说明半连接队列可能满了连接在被accept之前就积压。CLOSE_WAIT堆了一堆则指向业务代码里没调用close对端关闭了连接本端却一直保持着半开的socket。这是典型的代码bug通常出现在异常分支里没有释放连接的地方。iperf3是测吞吐和打流的标准工具。服务端跑iperf3 -s客户端打流iperf3 -c 服务端IP -u -b 100M -t 30-u表示UDP模式-b指定目标带宽100Mbps-t持续30秒。命令跑完会输出实际速率和丢包统计。重点看两个数据Jitter代表抖动Lost/Total Datagrams的比值是丢包率。UDP打流测的是链路在不拥塞控制下的极限承载能力如果丢包率很高先想清楚链路带宽是否够再想中间设备是不是缓存太小、防火墙的UDP会话超时时间是不是太短。nc也是个万能工具。测TCP端口通不通用nc -vz IP port测UDP端口则要小心。UDP没有握手nc -z -u并不能真正确定对端是否在收只能通过ICMP端口不可达来间接判断。要验证UDP端口是否真的连通最有效的方式还是直接发送业务数据看服务端有没有响应。比如有的设备自定义了一套简单UDP协议你用一个十六进制格式的数据报发过去服务端回了特定应答这才能证明链路真的通了。4.3 端口、文件描述符以及“连接数”的上限谜团TCP连接的四元组是源IP、源端口、目的IP、目的端口。这意味着并发连接数不是简单用65535个端口来限制的。一个客户端可以同时与同一台服务器的不同端口建立连接四元组不同就是不同的连接。但有一种情况会让端口成为瓶颈同一个客户端IP、同一个源端口段全部连到同一台服务器的同一个目标端口上。这时候可用的源端口数量就决定了并发连接数上限。Linux默认的动态端口范围通常写在/proc/sys/net/ipv4/ip_local_port_range里我见过的默认值一般是32768到60999可用端口大约2.8万个。这直接解释了为什么一个客户端想向同一个服务端建立超过小几万个连接时会出现异常。如果你确实要突破可以调这个范围但注意调大后本地端口池占用的内存会上升。nginx作为TCP反向代理时很多人问最大连接数是多少。这个问题的答案不是nginx配置里的worker_connections那么简单它同时受Linux文件描述符限制影响。每个TCP连接在服务器上至少占一个fd查看当前进程的fd限制用ulimit -n服务要支持高并发通常要把这个值调高同时内核里几个队列参数也要跟着调。频繁变高并发场景下我习惯把net.core.somaxconn和net.ipv4.tcp_max_syn_backlog一起调大否则并发请求一涨拥塞的连接请求在SYN阶段就开始排队连接建立速度会非常慢。4.4 高频问题速查表整理一份我在日常维护和答疑里最常碰到的问题方便你遇到对应症状的时候直接按照表格里的思路走。症状常见原因快速检查常用处理TCP连接一直连不上客户端状态SYN_SENT对端端口没监听、防火墙丢SYNss -antn查看状态抓包看SYN是否有响应检查服务监听、防火墙规则、路由CPU不高但连接建立很慢半连接队列/全连接队列满ss -lnt看Recv-Q积压调大somaxconn检查accept速度客户端重连报“地址已在使用”本地端口处于TIME_WAIT且未设置SO_REUSEADDRnetstat或ss统计TIME_WAIT数量设置SO_REUSEADDR避免绑定固定本地端口CLOSE_WAIT大量堆积业务代码没有close连接ss -ant统计CLOSE_WAIT数检查异常路径是否释放连接超时加入口TCP重传率持续偏高链路丢包、带宽打满Wireshark看重传包iperf3测链路排查中间设备降速或扩容链路UDP端口测试失败但网络正常UDP无应答机制nc探测不可靠直接发业务数据看响应用真实协议报文测试检查防火墙UDP策略TCP打流带宽上不去拥塞控制自动降速、窗口受限检查窗口大小、确认RTT、丢包率调大接收缓冲使用HighSpeed变体算法时谨慎评估这张表不是教科书式的“答案”是我自己在各种事故现场攒下来的排查起点遇到没遇到的问题先按表格方向试省很多时间。5. 选型建议TCP还是UDP别拍脑袋5.1 业务优先协议跟着业务需求走选择TCP还是UDP核心不是“哪个协议更厉害”而是“业务能接受什么程度的损失”。如果业务要求每个字节都必须完整到达、顺序不能乱比如文件上传、数据库事务、SSH远程操作、HTTP网页加载那TCP是唯一稳妥的选项。这类业务如果强行用UDP等于把“传输正确性”这个难题全盘抛给应用层实现成本和排查成本都会成倍上涨。反之如果业务对实时性要求远高于对完整性的要求比如在线竞技游戏的操作信令、音视频帧数据、设备状态量周期性上报这些场景下丢了一个旧包重传旧包反而是添乱UDP反而更合适。这类业务要做的不是“防止丢包”而是“让最新数据以最快速度到达”并在应用层设计好丢包后的降级策略。还有一个容易犯的错误把TCP当成电信诈骗式奇迹好像任何消息都必须确认。实际上很多物联网场景里单个数据包一周才发一次不可靠性带来的损失很小但你为维护大量TCP连接付出的心跳、重连、状态管理成本是持续性的如果数据价值不高用UDP反而简洁。5.2 混合使用同一个系统里TCP和UDP联手很多实用系统不是非此即彼而是TCP和UDP各用一段。比如一个视频监控平台控制信令用TCP保证指令可靠媒体流走UDP或者RTP保证画面低延迟。工业控制里也常见类似设计网关与设备之间的配置下发走TCP实时采集的数据走UDP既保证关键操作可靠又让高频数据不通阻塞。这种混合架构在复杂系统里很常见选型阶段就要把业务流量按“控制流量”和“数据流量”分开来定协议。判别标准很简单这个数据丢了用户立刻感知吗如果感知考虑TCP或者可靠的UDP方案如果感知不到比如统计量、探针数据、周期重复项UDP就够。这条线画得越清晰架构就越不容易两头受气。5.3 写在最后的一些个人体会我见过太多工程师一谈到UDP就害怕好像不用TCP就显得不专业其实这种心态主要来自没建立正确的失败模型。我的习惯是动手写通信程序之前先列出“这条消息丢失了会发生什么”如果答案是“丢就丢了”用UDP如果答案是“丢了对账不好做、状态会乱”用TCP或QUIC。看起来是个简单问题但逼着你想清楚了业务的真实底线。另一个经验是协议栈里你踩过的坑几乎都写在内核源码和RFC的边边角角里。比如TIME_WAIT为什么存在、Dup ACK为什么三个才触发快速重传、syn队列满了会有什么表现。遇到问题别急着改代码先抓包再翻文档最后再动手。抓包是最快接近真相的方式它不会骗你只会让你面对数据数据告诉你一切。这一套TCP和UDP的知识用到今天仍然是我做网络排障和系统设计的第一道关。希望这篇偏实战的记录能帮你少走一些弯路。
返回列表