资讯动态

TCP核心原理与排障实战:从握手到抓包定位

发布时间:2026/10/17 11:31:05 来源:尧图企业网站定制
我一直觉得TCP 是网络知识里最被“背烂”、又最没被真正理解的一块。面试场合大家都在复述三次握手、四次挥手可到了线上问题面前什么Connection reset、超时、重传率高、时延不稳定不少人还是只能靠猜。这一篇我按自己的理解把TCP的核心原理和实战经验串一遍尽量不用教科书式的讲法讲清楚它为什么要这样设计以及排查时怎么用它来定位问题。适合谁看刚学完计算机网络、想真正把TCP串起来的同学写网络服务的后端开发做运维和SRE的同行甚至要对网络参数做调优的这篇都能用得上。我自己最早也是在某个售后系统压测时被客户端报错Connection reset by peer搞得焦头烂额然后才开始把TCP的机制一点点抠透的。希望你读完之后至少再遇到这类问题能有一个清晰的排查路径。1. 先搞清楚 TCP 到底解决什么问题1.1 IP 只负责“尽力送达”活干得并不漂亮要理解TCP先得知道它站在IP的肩膀上而IP的这个肩膀并不靠谱。IP协议做的事情很简单把数据包从一台机器送到另一台机器中间经过路由、交换机它只承诺“我尽量送”却不承诺一定送到、按顺序送到、只送一次。这就像一个快递公司说“我尽力送但如果件丢了、中途碎了、甚至送了两遍你别怪我这是正常的”。现实网络里丢包、乱序、重复、延迟这四件事是常态尤其是在跨运营商、跨国、无线网络这种环境里丢包率哪怕只有百分之一对应用的伤害也是放大的。TCP存在的意义就是在IP这种“尽力而为”的底座上帮上层应用把这些问题全部兜住。你在应用层写socket时根本不用关心底层有没有丢包因为TCP已经在上层给你提供了一条看起来完全可靠的字节流通道。1.2 字节流而不是数据报TCP 给应用的抽象UDP面对的是一个一个的数据报发一个是一个边界非常清楚。TCP不一样它给应用层提供的是一个没有边界的字节流。你调用send()写1000字节再调用send()写1000字节对端read()出来不一定是2000字节可能是500字节也可能是3000字节——中间被内核重新切分、粘合过。这个设计其实很聪明。TCP内部会把字节流按MSS最大分段大小切片成一个个数据段再交给IP层发送。因为网络路径的MTU最大传输单元在变化TCP可以根据探测到的路径MTU动态调整切片大小如果TCP像UDP一样硬性保持消息边界这个灵活性就没了。字节流抽象也简化了可靠传输的实现给每一个字节一个序号确认的时候只需要告诉对方“我收到了到某个序号为止的所有字节”逻辑非常清爽。1.3 面向连接、全双工、点对点TCP还有一个特点连接是点对点的、全双工的。也就是说一条TCP连接只存在于两个端点之间数据可以双向同时流动不存在“一个发送端多个接收端”的广播场景。你也许会觉得这是废话但很多实际设计问题恰恰源于这里——比如有人想在多个接收端之间共享一条TCP连接做分发这本身就违反TCP的语义正确做法应该起多条独立连接或者用UDP做应用层封装。“面向连接”也不是说物理上真的拉了一根线而是两端内核各自维护一套状态协商好初始序号、窗口大小、各种选项然后共同维持这套状态直到连接关闭。所以TCP连接是逻辑上的“虚拟链路”这条链路的建立、维护、拆除就是我们熟悉的握手、保活、挥手。很多调优参数改的其实就是这几个阶段相关的内核状态和行为。2. 三次握手与四次挥手别只背状态机2.1 为什么握手必须是三次而不是两次或四次三次握手最容易被忽略的一个问题是它不只是为了确认双方“都在线”更关键的是同步初始序号ISN。序列号是TCP可靠传输的地基双方必须知道对方的起始序号后续的确认和重传才有参照物。如果你只发一次SYN然后等对方回复也就是“两次握手”会有什么问题考虑一个经典场景一条延迟很大的链路上客户端A发了一个SYN因为网络卡顿这个SYN在路上滞留了很久A等不到回复就超时重发了一个SYN第二次成功建立了连接并传输完数据关闭了。结果第一那个“迟到的SYN”又飘到服务端B——B收到这个SYN以为A要新建连接就会傻乎乎地分配资源、进入等待状态但A根本不知道这个连接的存在。于是B这边白白占用资源甚至会产生数据错乱的风险。所以第三次握手非常重要由发起方A对B的SYN-ACK再做一次确认告诉B“我之前那个SYN确实是当前这个连接要用的我确认你的初始序号了”。反过来如果这个确认迟到或丢了B收不到ACK会重发SYN-ACKA收到一个重复SYN-ACK后会重新ACK这样双方始终能围绕同一套连接状态对齐。三次握手本质上是在“不可靠的IP之上可靠地同步两个端点的初始状态”。抓包的时候你会看到SYN报文里除了SYN标志还会带一个Seq0但这只是相对序号Wireshark默认显示相对值。实际ISN是一个随机生成的值防止被预测攻击这也是早期TCP协议被人诟病的一个安全性漏洞。2.2 握手队列与半连接、全连接服务端收到SYN后并不会立刻完成握手而是先把连接放进一个半连接队列SYN队列当收到最后一个ACK连接才从SYN队列挪到全连接队列Accept队列等待应用层调用accept()取走。如果应用层处理慢Accept队列会堆积满了之后内核开始丢弃连接客户端表现就是握手超时、连接不上。排查这个特别适合用ss -lnt看Recv-Q和Send-Q的值。Recv-Q表示Accept队列当前堆积的连接数Send-Q是队列上限。两者相等且不为0说明队列满了应用进程没来得及accept。性能调优时这三个参数值得关注net.core.somaxconn 4096 # Accept队列上限的帽子 net.ipv4.tcp_max_syn_backlog 1024 # SYN队列上限改了somaxconn还不够应用层listen()的第二参数backlog也要跟着放大否则内核队列上限设置了也白搭。有些框架内部还会自己限制accept队列长度需要一起调。2.3 四次挥手FIN 和 ACK 为什么要拆开关闭连接时为什么是四次因为TCP是双工的每一方向对另一方向的关闭都是独立的。一方关闭只代表它不再发数据但可能还要继续收数据直到自己也发FIN。也就是说关闭需要双方各自声明一次“我不再发了”所以至少两次FIN-ACK交换看起来就是四次。但实际抓包里如果你看到服务端收到FIN后马上回复FIN第2次和第3次挥手会合并成一个包抓包看起来就只看到三次。这种“半关闭”状态值得留意对端收到的FIN只代表对方不再发送不代表你能立刻放弃读缓冲区否则可能丢尾包。2.4 TIME_WAIT主动关闭方绕不开的坎四次挥手里最容易被低估的是TIME_WAIT状态。主动关闭的一方在发送最后一个ACK之后不会立刻释放连接而是要等2个MSLMaximum Segment Lifetime报文最大生存时间才真正关闭。为什么要等这么久两个原因第一防止最后一个ACK丢失。如果被动关闭方没收到最后这个ACK它会重发FIN主动关闭方需要还在TIME_WAIT状态里才能重新响应ACK。第二让这条连接上迟到的旧报文在网络里自然消亡避免它们出现在一个同IP同端口的新连接里造成数据污染。TIME_WAIT会带来一个实际问题高并发短连接场景下比如大量HTTP请求主动关闭方会积压一大片TIME_WAIT连接。有人图省事直接开net.ipv4.tcp_tw_reuse但这要非常谨慎。tcp_tw_reuse的作用是允许客户端复用一个TIME_WAIT状态的连接作为新的对外连接前提是开启时间戳选项且新连接的起始时间戳更大。它只对主动发起连接的一方有效不能解决服务端TIME_WAIT堆积而且如果后端有多台机器、NAT环境里时间戳不统一开启它可能导致序列号混乱。另一个曾经存在的tcp_tw_recycle参数因为有同样的时间戳问题在后续内核版本里已经移除了——遇到老文章里无脑推荐这个参数的可以直接跳过。我的建议是优先调整应用层减少不必要的短连接实在要复用从业务上做连接池而不是依赖内核参数走捷径。TIME_WAIT本身并不可怕只要数量不导致端口耗尽或者内存明显上涨多让它待一会儿反而是网络安全的保障。3. 可靠传输靠什么序号、确认与重传3.1 每一个字节都有自己的编号TCP的序号是字节序号不是报文序号。这是个特别容易混淆的点。比如发送方要发1000字节数据MSS是1000那么它发第1个段Seq1长度1000下一个段的Seq就是1001。接收方回复的ACK号表示“我已经连续收到第N个字节之前的所有数据下一个我期待接收的是SeqN的数据”。ACK是累计的。这个“累计确认”是TCP一个核心设计接收方不需要对每一段单独确认只要连续收到了就回一个最大的连续序号即可。好处是极大减少了确认报文的数量坏处是一旦中间丢了一段后面即使连续收到好几段接收方也只能不断重复确认那个丢失序号之前的ACK无法告诉发送方“后面那些其实我也收到了”。这就是经典的重传问题。发送方收到多个重复ACK时第一反应是“中间丢了包”于是触发重传机制。基础做法的效率瓶颈非常明显如果一次丢了一大片数据接收方只能重复确认丢点之后的所有数据而发送方也会重传丢失之后整段窗口数据造成大量浪费。3.2 超时重传与 RTO 的估算逻辑最朴实的重传方式是“等一段超时时间没收到ACK就重发”。这段等待时间叫RTORetransmission Timeout。RTO不能拍脑袋定死因为网络时延动态变化。局域网里1毫秒就能收到ACK跨洋链路可能要一两百毫秒如果RTO定死了要么等太久、要么频繁误重传。TCP的做法是持续采样往返时间RTT然后用一个加权平均去估计当前应该用的RTO。早期算法很简单SRTT (α × SRTT) ((1-α) × RTT)再乘一个余量。后面RFC 6298把公式优化得更复杂还会考虑RTT抖动。说到底它是在“等待有把握”和“尽快重发”之间找到平衡。超时重传有个问题一旦真的发生超时发送方对网络状况的预估往往也失效了。所以出现RTO超时后TCP会做两件事第一把拥塞窗口降到很低减轻网络负担第二把后续重传的RTO按指数退避给网络更长的恢复时间。你在抓包里如果看到一连串Retransmission包间隔呈指数增长那就是标准退避。3.3 快速重传与 SACK超时重传太被动了。实际上大部分丢包不至于让ACK完全消失接收方还会继续发送重复ACK。TCP用“收到3个重复ACK”作为一个提示该段大概丢了别等超时立刻重传。这就是快速重传。后来为了让发送方知道重复ACK之外“到底收了多少后续数据”协议族里加了一个SACK选项。接收方可以在ACK包里附加一段范围我除了这个序号没收到后面从X到Y、从Z到W这些我都收到了。发送方根据SACK就能精确地进行选择性重传只补丢掉的缺口而不是重传整个窗口这对高丢包率、大窗口的网络帮助非常明显。如果你用Wireshark看包会看到握手阶段的SYN报文里往往带着“SACK permitted”选项。中间的包偶尔会有“TCP SACK”字段。如果你的网络里大量丢包检查一下连接两端是否都启用了SACK现代Linux默认是开的但有些老系统或某些网络设备可能会把它关掉。3.4 乱序与重复包被忽略的另一面很多人只关注丢包忽略了乱序。IP层的路径不是固定的同一个连接的不同报文可能走了不同路径先发的反而后到。TCP靠序号发现乱序后会把后到的数据先放进乱序队列等缺少的段到了再交给应用层。问题在于如果乱序程度比较大接收方会回复重复ACK发送方可能误判为丢包并快速重传白白浪费带宽。重复包的处理则相对简单接收方收到序号小于等于已确认序号的段会直接丢弃并且重申一遍当前期待序号。这其实也是一个“正常丢包”的信号刷屏的重复ACK通常意味着网络里确实有某种程度的传输不稳定。4. 流量控制与拥塞控制别把两者混为一谈4.1 流量控制收不下就别硬发很多初学者把流量控制和拥塞控制混在一起其实动机完全不同。流量控制是“接收方能吃多少发送方就发多少”解决的是端到端的缓冲区溢出问题。接收方会在每个ACK里带一个窗口字段rwnd接收窗口告诉发送方“我的接收缓冲区还剩多少空间你最多再发这么多”。生活化的类比是食堂打饭窗口里告诉我“我现在就剩3份菜了”我就只端3份绝不一次性往窗口里塞20份否则食堂窗口堆不下。发送方收到rwnd0时就进入等待状态但不能傻等——长效机制是周期性地发送窗口探测报文问接收方“窗口开了没”。这个探测机制也意味着如果接收进程一直不读数据连接可能长期停在这个状态看起来像是“卡死”了。这里就引出一个后端调优的经典问题socket接收缓冲区太小会导致接收窗口经常变成0明明网络带宽巨大应用层吞吐上不去。Linux里可以调整net.core.rmem_max 16777216 net.core.wmem_max 16777216 net.ipv4.tcp_rmem 4096 87380 16777216 net.ipv4.tcp_wmem 4096 16384 16777216中间那一项是默认值。这个默认值如果设置得过小即使在高速局域网内传输大文件也跑不满带宽因为窗口不够发送方被流量控制掐住了脖子。4.2 拥塞控制网络撑不住了发送方自觉降速拥塞控制解决的是另一个问题接收方缓冲区很充裕但中间网络已经堵死了继续猛发只会加剧丢包。TCP把发送方自己的窗口叫cwnd拥塞窗口实际能发的数据量是min(rwnd, cwnd)。流量控制管rwnd拥塞控制管cwnd。拥塞控制的基础流程是慢启动、拥塞避免、拥塞发生时快速恢复。慢启动名字很有迷惑性实际增长快得吓人初始cwnd很小通常只有10个MSS但每收到一个ACK窗口就加1个MSS相当于每个RTT窗口翻倍。刚建立连接时cwnd呈指数增长直到触碰到慢启动阈值ssthresh。之后进入拥塞避免阶段每个RTT只增加1个MSS线性上升。一旦检测到丢包超时或重复ACK就把ssthresh降到当前窗口的一半如果是超时则cwnd直接降到1开始新一轮慢启动。这套机制的背后是经典加法增大、乘法减小的算法思想。早期的Reno算法处理单包丢包表现还可以但多包丢包和大带宽高延迟链路上效果很差。后来Linux默认的CUBIC算法把增长逻辑改成了三次函数对高带宽环境友好很多。再后面出现的BBR算法更是换了一套思路不再以丢包作为拥塞信号而是持续估计瓶颈带宽和最小RTT用这个估算值来约束发送速率实测在长肥网络和无线网络里效果常常优于CUBIC。4.3 为什么说 Nagle 算法和延迟确认是天敌这是实践里非常常见、又特别隐蔽的一种坑。Nagle算法的出发点是避免发送大量小报文如果一个TCP连接上还有未确认的数据那么新产生的小数据会被“合并”到同一个数据段里等前一段的ACK回来了再一起发送。这对减少网络中小包数量很有帮助但对交互性强的应用就是灾难会导致一个请求要等上一个包的ACK才发出去多出一整轮RTT的延迟。延迟确认则是接收方的策略不会收到数据立刻回ACK而是稍微等一会儿看能不能把ACK捎带在响应数据里一起发或者合并多个数据段的确认。Linux下延迟确认默认是40ms的尺度。两者一叠加就会出现一个非常委屈的场景客户端发一个请求过去因为Nagle算法在等待之前数据的ACK服务端收到请求后因为延迟确认本能地等一会儿希望响应数据能直接携带ACK。于是双方都在等对方先动白白多出40ms甚至更高延迟。这种问题在SQL客户端、Redis这种高频小请求场景里特别要命——明明每次操作只要零点几毫秒总耗时却偏高。解法很简单也比较粗暴如果确认自己的业务是交互性的小包请求关闭Nagle算法也就是把TCP_NODELAY打开。Redis、很多数据库驱动默认就是这么干的。延迟确认做不做妥协要谨慎因为它是接收端全局行为很难针对单条连接修改所以我通常建议优先打开TCP_NODELAY实测延迟改善立竿见影。4.4 窗口缩放和现代高速网络早期TCP的窗口字段只有16位最大只能表示65535字节。这个上限在百兆网时代没问题到了千兆、万兆环境就完全不够看。既然窗口不够数据链路再宽也跑不起来——这就是著名的“长肥网络问题”。后来TCP加上了Window Scale选项在握手阶段两边协商一个缩放因子把窗口表示范围扩大到千兆级以上。抓包里看到Window Scale7、Window64240这样的组合说明实际窗口是64240左移7位大约是8兆字节。排查吞吐率上不去时一定要先确认握手包里是否带了Window Scale以及双方实际协商的最大窗口是多少。有些老设备和老系统不支持这个选项结果就是带宽利用率只能到几十兆看起来网络正常但传输速度怎么都不对。顺着这个思路很多“带宽上不去”的问题其实不是带宽本身而是窗口模型跟不上。5. 实操抓包验证 TCP 核心机制5.1 用 tcpdump 完整看一次三次握手纸上谈兵太多抓个包比什么都直观。假设你要访问一台服务器可以用如下命令抓本机到目标端口的流量sudo tcpdump -i eth0 -nn tcp and host 10.0.0.5 and port 443 -c 20加上-nn表示不解析主机名和端口速度快且输出干净。抓到的关键行大概是10.0.0.1.53740 10.0.0.5.443: Flags [S], seq 1219876554, win 64240, options [mss 1460,sackOK,TS val ...], length 0 10.0.0.5.443 10.0.0.1.53740: Flags [S.], seq 1946982134, ack 1219876555, win 65160, options [mss 1460,sackOK,TS val ...], length 0 10.0.0.1.53740 10.0.0.5.443: Flags [.], ack 1946982135, win 64240, length 0第一次握手SYN只有Seq第二次握手SYN-ACK同时带自己的Seq并把对端的Seq1作为Ack第三 次握手ACK带上两个序列号确认双方都准备好了。这里面seq全是绝对序号看着很大很吓人但Ack正好是对方Seq1。注意ack1219876555说明接收方期待的是1219876554之后的字节。win 64240是通告的接收窗口。两个包之间的窗口在变化是因为双方各自启用了Window Scale选项。如果没开这个选项而且在高速网络里看到win始终卡在65535那就要怀疑内核或设备限制。5.2 用 Wireshark 看重传和乱序tcpdump对快速排查够用但要看细节我更推荐把包存成pcap文件再用Wireshark打开sudo tcpdump -i eth0 -nn -w /tmp/tcp.pcap host 10.0.0.5 and port 80Wireshark里分析重传极其方便。打开后直接在上方过滤栏输入tcp.analysis.retransmission即刻把所有重传包筛出来。同样的诊断里还会有tcp.analysis.fast_retransmission和tcp.analysis.out_of_order分别对应快速重传和乱序。看到这些信息就去关注对端的丢包和网络抖动。你说网络好不好与其看路由器丢包率不如看抓包里的重传次数来得准。实际操作里我在某个文件传输系统上就遇到过这种场景从客户端抓包发现某个数据段被重复确认了三次发送端立刻执行快速重传但即使重传了对端依然只确认到原来的位置。后来一看问题出在中间防火墙把一个带SACK的包拦截了导致接收方的SACK信息根本传不出去发送端一直在盲猜要重传哪些段。5.3 动手制造一次丢包看看TCP怎么反应如果你想在可控环境下观察丢包重传可以用Linux自带的tc工具给某条链路加一点丢包率sudo tc qdisc add dev eth0 root netem loss 10%然后对某个服务发起流量并抓包。你会发现虽然丢包率高达10%应用层数据几乎不受影响但包的数量明显增多里面大量是重复ACK和重传段。这时候把拥塞控制算法切换成不同版本对比你还能看到窗口和发送速率的变化差异。看完实验记得取消sudo tc qdisc del dev eth0 root否则所有流量都带着10%丢包走线上环境容易出事。我踩过这个坑实验完忘了清理第二天业务部门反馈网络不稳定排查半天才发现是netem还在生效。5.4 用 ss 快速确认连接状态和重传统计抓包不一定总有条件上。线上排查时我习惯先用ss看连接状态分布ss -s ss -lnt ss -tiss -ti输出里藏着很丰富的信息rto当前重传超时、rtt最近往返时间、cwnd拥塞窗口、ssthresh、retrans总次数。如果看到某个连接的retrans在持续增长基本可以断定这条路质量有问题。ss -s还会汇总当前系统的TCP状态计数一眼能看出SYN队列积压、TIME_WAIT过多和内存占用过高的情况。如果再配合netstat -s看协议级统计能够得到SYN重传、丢包、停滞的全局视角。先看宏观统计、再看单条连接细节、最后定向抓包这套三连排查法比在大海一样的数据里盲目翻包高效得多。6. 常见问题与排坑实录6.1 Connection timed out 与 Connection refused 到底差在哪两者经常被混淆但含义完全不同。Connection refused说明目标端口上根本没有进程监听操作系统收到SYN后直接回了一个RST包客户端立刻收到错误。Connection timed out则说明SYN包发出去了但一直没收到任何响应这一般意味着对方不可达或者被防火墙静默丢弃或者服务端SYN队列满到没有能力响应。排查时先看端口监听ss -lnt里有对应端口说明是服务端队列或防火墙问题没有则说明服务根本没启动。生产环境里“连接超时但有监听”更常见这时去查SYN队列是否溢出、防火墙是否丢包观察netstat -s里的SYNs to LISTEN sockets dropped计数值是否在涨。6.2 Connection reset by peer 到底是什么信号RST不是谈判是“单方面撕毁协议”。当一端收到一个在它看来完全不合法的TCP段时它不会礼貌地回复一个错误而是直接发送RST终止连接。常见触发场景包括往一个已经关闭的连接上发数据应用层bug或超时后依然读写。服务端程序崩溃、进程退出之前建立的连接被内核直接RST。有中间设备防火墙、负载均衡空闲超时把连接杀掉之后两侧还有数据流动其中一侧发现“这个连接不存在了”于是RST。如果你在客户端看到“Connection reset by peer”最值得怀疑的就是服务端进程最近是否异常退出或中间设备的空闲超时设置太短。抓包时看RST前后的上下文能找到真正的原因。如果RST来自服务端且之前服务端刚刚发过FIN那多半是应用层主动关闭后又有读写操作如果RST来自一个从来没有SYN-ACK关系的IP那就要警惕中间设备作妖了。6.3 TIME_WAIT 堆积到六位数到底慌不慌高并发的短连接场景下主动关闭方TIME_WAIT连接上万甚至上十万都出现过。很多人一看到十万级TIME_WAIT就慌其实它本身只要不耗尽本地端口、不让内存暴涨对系统没有致命影响。真正要担心的是两种衍生问题端口耗尽以及某些内核或设备对四元组数量有限制。对主动连接方来说本地端口范围是有限的默认大约28000多个端口可用。如果TIME_WAIT堆积且不放量复用很快就会出现“Address already in use”。当时我所在的系统就是这种状况面对大量短连接出站请求端口用完是迟早的事。处理顺序建议先调大端口范围echo net.ipv4.ip_local_port_range1024 65000 /etc/sysctl.conf再开tcp_tw_reuse让主动连接方安全复用TIME_WAIT连接但务必确认所有相关机器时间戳一致、时间同步正常。服务端作为被动方一般少见TIME_WAIT堆积更多的是CLOSE_WAIT堆积——那才是需要警惕的CLOSE_WAIT意味着对端已关闭连接但本地应用进程迟迟没有close()通常就是应用代码漏关了socket。看到大量CLOSE_WAIT先查应用日志里哪个连接池没有被正确回收。6.4 TCP keepalive 不是万能心跳TCP协议自带一个keepalive机制定期探测连接是否仍然存活。但它的默认参数非常保守Linux默认7200秒2小时才启动第一次探测之后隔75秒探测一次最多9次。也就是说一条连接彻底“死亡”可能要等两小时才会被发现这对大多数在线业务来说完全没有实用意义。很多新手把宝压在TCP keepalive上结果服务一端宕机另一端用户还傻等着。工程上正确的做法是应用层心跳自定义一种轻量消息每几秒或十几秒发一次超过若干个周期没收到就算连接挂了主动重连。TCP keepalive可以调低作为辅助手段但不能作为唯一的存活检测因为它只能证明“内核协议栈还活着”不能证明“服务进程和业务逻辑还活着”。如果服务进程死循环了TCP连接看起来依然“健康”但应用层早就没反应了。6.5 最终排查的几条经验TCP这个问题越深入越发现它把“可靠性”四个字做得极度精巧。但我实际工作中的最大体会是不要试图背住所有状态和参数而是记住一个思维路径——先确认物理链路通不通再确认端口和服务进程活不活再确认握手和挥手状态正不正常最后看抓包找丢包和重传。分享一个我反复用的口诀超时看链路拒绝看监听重置看进程CLOSE_WAIT看代码TIME_WAIT看端口。这一套走下来绝大多数TCP相关故障都能定位到一个比较窄的范围了。对后端开发者来说应用层的心跳、连接池、超时设置往往比内核参数更能决定体验。内核参数是最后的手段而不是第一选择因为网络环境千差万别调错一个参数的影响范围经常远超预期。我自己就在某次调优中把接收缓冲区调得过大结果高并发场景下内存暴涨引发了更大规模的服务不可用。最后再讲一个心得看书理解协议原理是一层抓包看真实流量是一层真正动手做一次丢包实验又是一层。这三层都走一遍之后TCP对你来说就不再是面试题而是一个真正趁手的排查工具。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑