TCP/IP 这十来年一直是面试里的常客,也是很多人一提起就心里发怵的部分。其实它没那么高深,只是被厚厚的协议文档和背题式的复习方式给吓住了。你去看网上那些高频面试题,翻来覆去就是握手挥手、滑动窗口、拥塞控制、粘包拆包这些,本质上都是同一个问题:一个不靠谱的网络,如何托起一个靠谱的数据传输服务。
这篇文章不打算给你画一堆分层大图,也不做名词堆砌。我会用“灵魂十问”的方式,把 TCP/IP 里那些真正值得搞懂的核心机制拆开揉碎,讲清楚每个机制到底在解决什么问题、面试官为什么爱问、实际排查时怎么用。无论你是要准备面试,还是刚上手做网络开发想补基础,这套内容应该都能帮你把碎片知识串成体系。
1. 为什么 TCP/IP 躲不掉——十问背后的主线
1.1 面试官真正在问什么
很多人在面试前喜欢背“TCP 三次握手过程”,仿佛能默写出 SYN、SYN+ACK、ACK 就算过关。但面试官心里其实有一套更深的问题线:你怎么证明自己理解“可靠传输”这件事?频繁被问到的握手、挥手、重传、窗口、拥塞控制,都是“可靠传输”这个总目标下的分支。
如果把 TCP/IP 核心机制排一个优先级,我会这么排:
- 连接管理:三次握手、四次挥手、状态迁移。这是协议的骨架,也是面试第一道菜。
- 可靠传输:序列号、确认号、超时重传、快速重传。这是 TCP 的立身之本。
- 流量控制:滑动窗口、零窗口探测。这是避免“发送方太快淹没接收方”。
- 拥塞控制:慢启动、拥塞避免、快速恢复。这是避免“太多主机一起塞爆网络”。
- 字节流特性:粘包、拆包、消息边界。这是应用层开发者最容易踩的坑。
你把这五块串起来,就能发现它们环环相扣。握手阶段要协商初始序列号,序列号是可靠传输的基石;可靠传输依赖确认与重传,重传策略又和滑动窗口耦合;滑动窗口既要控制接收方缓冲区,又要在拥塞时动态收缩。面试官问任何一个点,最后都可能顺藤摸瓜牵出整个 TCP 状态机。
所以说,不要孤立地背答案,要按“为什么需要这个机制”来组织知识。当你能用自己的话把逻辑链条讲通,面试官才觉得你是真懂,而不是背了八股文。
1.2 先建立自己的协议地图
我建议你动手画一张“TCP 生命周期图”,比直接看教科书上的全状态图更好用。左端是连接建立,中间是数据传输,右端是连接关闭。把三次握手、数据传输、四次挥手分别对应到这张图里,再把每个阶段涉及的关键字段标在旁边。
这张图的关键不在于画得漂亮,而在于你能否在图上回答这几个问题:
- 握手为什么需要三次,而不是两次或四次?
- 挥手为什么需要四次,第二次和第三次能不能合并?
- 主动关闭方为什么要等两个 MSL?
- 数据传输出错时,TCP 靠什么发现并补救?
这其实就是“十问”的骨架。你不需要把协议文档里每一条都背下来,但你必须能对着这张图解释清楚每个决策的出发点。等到实际抓包排查时,你会发现脑中的地图和抓包工具的时序图能一一对上,定位问题就快很多。
我见过有些同学把大半时间花在记忆 TCP 头部每个字段的二进制位偏移上,这其实投入产出比很低。头部字段当然要有基本了解,但更重要的是一字节序列号、一字节确认号如何在交互中发挥作用。你理解机制,字段自然就记住了。本末倒置的人往往背得快、忘得也快。
2. 连接的本质——握手与挥手必须抠到字节级
2.1 三次握手:不只是一个“你好”“你好”“好的”的仪式
面试官问三次握手时,你光说“客户端发 SYN,服务端回 SYN+ACK,客户端再回 ACK”是不够的。他要听的是这三个包分别确认了什么、协商了什么、防止了什么。
第一次握手,客户端发送 SYN,并携带一个初始序列号 ISN(记为 x)。这个序列号不是从 0 开始的,而是随机生成的。为什么?如果每次都用固定值,老连接残留的延迟报文很容易被当成新连接的合法数据,造成串包。随机化之后,即便网络中有一个旧 SYN 延迟到达,接收方判断序列号不在合法窗口内,也会直接丢弃,极大降低串扰概率。
第二次握手,服务端收到 SYN 后回复 SYN+ACK。这里除了确认客户端的 ISN(把确认号设为 x+1,表示“我已经收到你的 SYN,期待下一个字节是 x+1”),还要带上自己的初始序列号 y。服务端的确认号 x+1 非常重要,它表示服务端已经记住客户端的起始位置,之后收到的数据要以这个为基准排序。
第三次握手,客户端发送 ACK,确认号设为 y+1。这一步的主要作用是让服务端确认“客户端确实收到了我的 SYN+ACK”。如果只有两次握手,服务端无法区分“客户端收到了但我没收到它的确认”和“客户端根本没收到我的应答”,半开连接就会囤积。三次握手保证双方都确认了对方的收发能力,TCP 连接才能进入稳定状态。
每次抓包时,我会特别看第三条 ACK 的载荷长度。正常情况下,第三次握手是可以携带数据的(TCP Fast Open 的变体姑且不论),但传统握手里它往往不带数据。面试时你可以补充一句:从第三次握手开始,连接进入 ESTABLISHED,双方可以正式传数据,但严格说客户端在发送第三次 ACK 后就可以带上应用数据,这和是否启用延迟确认策略有关。这样回答会显得你现场思考过,而不是背过程。
2.2 四次挥手:TIME_WAIT 为什么必须存在
挥手比握手复杂,核心原因是 TCP 是双工通道,每一方向都需要单独关闭。四次挥手严格说是四个包:主动关闭方发 FIN,被动方回 ACK,被动方再发 FIN,主动方回 ACK。被动方先回 ACK、后发 FIN,中间可能隔一段时间,因为被动方可能还有数据没传完,所以不能把第二次和第三次合并,这是挥手需要四次的根本原因。
面试最常追问的是:为什么主动关闭方最后要等 2MSL(两倍最大报文段生存时间)才彻底关闭?
原因有两个层面。第一,保证最后的 ACK 能到达被动方。如果这个 ACK 丢了,被动方会超时重发 FIN,主动方如果在收到重发 FIN 后才能重新回 ACK。如果主动方立刻关闭,重发的 FIN 到达时已经没有进程在响应,被动方永远等不到 ACK,只能一直处于 LAST_ACK 状态。第二,让本连接内所有旧报文在网络中自然消亡。一个 MSL 是报文从发出到消失的最长时间,2MSL 足够覆盖一个报文去和回的最长时间,避免旧连接的延迟报文混入后续复用同一四元组的新连接。
我在实际运维里对 TIME_WAIT 印象很深。高并发短连接场景下,主动关闭方会堆积大量 TIME_WAIT 连接,系统默认的端口和连接表项都可能吃紧。有人为了省事把 TIME_WAIT 重用直接打开,这在某些场景能降低端口占用,但如果没有真正理解“旧报文可能污染新连接”的风险,往往会埋下数据错乱的坑。我的习惯是先排查为什么会有这么多短连接,而不是急着改内核参数。
2.3 怎么把状态迁移讲得既清楚又不啰嗦
回答连接管理类问题时,建议用“半区记忆法”。客户端视角只关注 SYN_SENT、ESTABLISHED、FIN_WAIT_1、FIN_WAIT_2、TIME_WAIT、CLOSED;服务端视角关注 LISTEN、SYN_RCVD、ESTABLISHED、CLOSE_WAIT、LAST_ACK、CLOSED。你先把各自主动发起的动作标出来,再把“收到对方什么包后跳转到什么状态”补上去,这张状态表就是你的答题地图。
需要特别留意两个状态:CLOSE_WAIT 和 TIME_WAIT。
- TIME_WAIT 是主动关闭方发送最后一个 ACK 后停留的状态,重在“等待旧报文消亡”。
- CLOSE_WAIT 是被动方收到 FIN 但还没调用 close 时的状态。如果服务端代码处理不当,比如没有关闭连接,CLOSE_WAIT 会越积越多,最终把文件描述符耗尽。这属于非常典型的线上故障,面试官一听到你能主动讲出 CLOSE_WAIT 堆积的排查思路,基本就知道你有实战经验。
我在一次模拟故障里就遇到过:某个接口响应很慢,连接数飙升,用系统命令一看,大量连接停在 CLOSE_WAIT。顺手看了进程堆栈和日志,发现是业务代码里发起外部调用后没有在异常分支关闭连接。问题不在协议层,而在应用层忘了释放资源。这类经验比背状态迁移表更有说服力。
3. 可靠性是真本事——重传、确认与滑动窗口
3.1 序列号机制:TCP 所有可靠性的地基
TCP 把应用层数据看成连续的字节流,每个字节都有唯一的序列号。接收方用确认号告诉发送方“我下一个想收到的字节编号是多少”,这比“我收到了几个包”更精确。你发了一批数据,里面第 3 个包丢了,接收方的 ACK 会重复声明期望的序列号,发送方据此判断丢包点。
这里面有一个新手容易忽略的细节:序列号初始值不是 0。前面说了 ISN 是随机的,而且 TCP 规定双方在握手时各自声明初始序号。这样做除了防止串包,还有一个好处:让数据包的排序、去重、重组有了统一基准。抓包工具里看到的 seq=1、seq=101 实际是相对值,是抓包工具减去了 ISN 后的展示,别被这个“1”误导。
面试如果追问“序列号用完了怎么办”,你要答出回绕处理。TCP 序列号是 32 位无符号数,最大 4GB 左右,高速传输下完全可能回绕。传统判断用“序列号 A 是否在 B 之前”不能简单比较大小,要配合窗口范围做无符号回绕比较。Linux 内核里专门有序列号比较宏,核心思路是把差值转成有符号数再判断正负。能讲到这一层,说明你确实了解协议实现,而不是只看了概念图。
3.2 滑动窗口:控制流速,也要处理丢包
滑动窗口解决的是“发送方能不能一口气发一堆数据”的问题。接收方会在自己的 ACK 里带上窗口大小,告诉发送方“我的接收缓冲区最多还能收这么多”。发送方维护发送窗口,窗口左边界是已确认的字节,右边界是还能发的最大序号。每收到一个 ACK,左边界右移;每发送一个新包,已用窗口增加。
我见过不少人把“窗口”误解成“一次最多发多少包”。准确说是“一次允许在途未确认的字节数”。同一个连接里,数据包大小不一,所以按字节数算才是严谨的。理解了这一点,你就知道为什么 TCP 要谨慎对待“零窗口”:接收方缓冲区满了,窗口为 0,发送方必须停止发送并定期发窗口探测,直到接收方恢复。
丢包处理也是窗口机制的一部分。当发送方连续收到 3 个重复 ACK,大概率是某个包丢了,这时触发快速重传,不必傻等超时。再到快速恢复,拥塞窗口减半而不是降到 1。为什么要这样设计?因为既然还能收到重复 ACK,说明网络链路没有完全断开,只是出现局部拥塞,直接回到慢启动代价太大。这套机制的目标是“有错就纠,但别矫枉过正”。
排障时,我会在抓包里重点看两点:一是 ACK 号是否在原地踏步,二是窗口字段是否有剧烈收缩,这两点能很快暴露是丢包还是接收端处理不过来。有一次线上服务吞吐量上不去,抓包看到大量重复 ACK 和一个很小的窗口通告,顺藤摸瓜发现是网卡中断没有均摊到多核 CPU,单核处理不过来导致缓冲区占满。协议栈没问题,硬件和内核参数背了锅。
3.3 拥塞控制:网络瓶颈下的集体自觉
流量控制是“你太快了我接不住”,拥塞控制则是“大家都太快了,网络中间的交换机路由器先扛不住了”。TCP 是端到端协议,它看不见网络内部的队列状态,只能靠丢包和延迟来猜测。经典拥塞控制分四个阶段:
- 慢启动:刚开始拥塞窗口从 1 或 2 个 MSS 开始,每收到一个 ACK 就指数增长。不是一直慢,是“试探性加速”。
- 拥塞避免:到达慢启动阈值后,每经过一个往返时间 RTT 窗口加 1 个 MSS,转为线性增长。
- 快速重传:连续收到 3 个重复 ACK,立即重传疑似丢失的包,不等超时。
- 快速恢复:把拥塞窗口减半,阈值同步更新,然后继续线性增长。
面试时,我会建议你顺便提一嘴“为什么慢启动叫慢启动却是指数增长”。因为指数增长是在每个 RTT 内翻倍,看似“快”,但它初始值小,目的是快速探测可用带宽上限,一旦接近阈值就转保守。如果从一开头就线性增长,在高带宽长肥网络里要很久才能填满链路。
再往下聊,可以说说现代拥塞控制算法。Linux 默认的 Cubic 在高带宽场景表现很好,BBR 则靠测量瓶颈带宽和最小 RTT 来避免丢包触发式降速。你不一定每个算法都深挖,但至少要知道 TCP 的拥塞控制已经从“丢包作为唯一信号”演进到“延迟和带宽同时建模”的阶段。能谈到 BBR,会让面试官觉得你技术涉猎广、愿意持续跟进。
4. 边界场景才是分水岭——粘包、半关闭、保活与超时
4.1 粘包拆包:TCP 是字节流,不是消息流
日常开发里最让人头疼的不是三次握手,而是“明明我 send 了两条消息,对端 recv 一次全收到,或者一条消息拆成了两次”。初学者常把 TCP 当成消息边界协议,这是误会。
TCP 只负责把字节可靠地从一端搬到另一端,它根本不关心你业务上的“一条消息”从哪里开始、到哪里结束。多个小数据包可能被合并成一个段发送(Nagle 算法和接收端聚合都会造成这种效果),一个大消息也可能因为分段和 IP 分片分成多段到达。应用层必须自己设计边界规则。
常见的拆包方案有三种:固定长度、分隔符、长度字段前缀。
- 固定长度最简单,每条消息固定 N 字节,收满 N 字节就解析一条。缺点是空间浪费,灵活性差。
- 分隔符适合文本协议,比如按换行符切分。缺点是要处理转义,且消息内容不能随意包含分隔符。
- 长度前缀最通用,先读 4 字节表示消息体长度,再读对应长度的消息体。几乎所有现代二进制 RPC 都是类似思路。
我写网络服务时会专门维护一个接收缓冲区,把 recv 到的数据先追加进去,再循环检查缓冲区里是否已经包含一个完整消息。注意一个细节:长度字段本身也要防止半包。你第一次可能只读到了长度字段的前 2 个字节,不能直接歇着,要等后续字节凑齐。很多线上奇奇怪怪的乱码问题,都是因为解析代码没处理“长度字段只读到一半”的情况。
面试答粘包时,一定要先抛出一句“TCP 是面向字节流的协议,本身没有消息边界”,然后再说应用层怎么解决。这句话就是答题的重点,也是很多人的盲区。
4.2 半关闭与 keepalive:连接何时才真正结束
半关闭指的是某一端已经停止发送数据但还继续接收数据,用 shutdown 而不是 close 可以实现。为什么需要半关闭?最典型的场景是 HTTP 旧版那种“请求响应”模式:客户端发完请求后关闭发送方向,但还等着接收服务端的响应正文。如果直接 close,接收方向也会关闭,响应可能就收不到了。
再说 keepalive。TCP 内置的 keepalive 默认是关闭的,就算开启,默认也是两小时才探测一次。它解决的问题是“连接看起来还 ESTABLISHED,但对方早就没响应了”。很多开发者以为 TCP 连接稳定,挂了也能被发现,真到上线才发现:只要双方不主动发数据,TCP 根本不会去验证对方死活,中间设备把连接静默回收,应用层还浑然不知。
所以应用层心跳几乎是必选项。我一般会把心跳设计成“业务层 ping/pong 消息 + 超时判定”:客户端定期发一个轻量心跳包,服务端收到后回 pong;如果连续 N 个周期没收到响应,就判定连接不可用并主动回收。这个 N 要结合业务容忍度来定,太短会误杀正常慢请求,太长又会让故障发现延迟变高。实践里 3 到 5 个周期是比较常见的折中,具体数值还是要压测。
4.3 超时重传:RTO 怎么算才不坑
超时重传里最怕两件事:超时时间设太长,丢包后迟钝;设太短,可能丢包还没回执就重传,造成无谓重复。TCP 不是拿一个固定时间去死等,它会动态估算往返时间 RTT,再结合波动程度算 RTO(重传超时时间)。
经典算法是加权移动平均:RTT 变化大时,RTO 也要放宽;RTT 很稳定时,RTO 可以紧跟真实值。后来发现重传本身会污染 RTT 采样,你发了一个包,超时后重传,然后收到 ACK,这个 ACK 到底是对哪个包的确认?Karn 算法规定:发生重传后,这次 RTT 样本不计入估算,直到后续没有重传的包确认后才恢复采样。这避免了模糊测量把 RTO 越算越离谱。
研发一般不需要自己实现 RTO 算法,但排查时要有概念。如果线上出现大量无意义重传,先看 RTT 波动:是链路抖动,还是因为某些时刻拥塞导致排队延迟变大。TCP 的 RTO 对波动很敏感,动态调整跟不上时就会表现为“一会儿不重传,一会儿疯狂重传”。调整内核参数前,先用数据确认问题到底出在链路的哪个环节。
5. 高频面试题速答——把十问拆成一张检查表
5.1 TCP 与 UDP 的第一层对比
这是开场必问。我的回答框架是:面向连接 vs 无连接;可靠 vs 尽力而为;字节流 vs 报文;有拥塞控制 vs 无拥塞控制;还有头部开销、排序能力、广播多播支持这些次要维度。
但别停在这里。我会紧接着说一句:正是因为 UDP 无连接、无状态,它天然适合 DNS 查询、音视频实时传输、游戏位置同步这类“低延迟优先、单包可丢”的场景。而 TCP 适合需要完整可靠传输的文件、网页、交易请求。若要可靠又不想用 TCP,可以在 UDP 之上自己做重传和排序,很多游戏引擎的可靠 UDP 协议就是这么干的。能举出这个例子,说明你不是在背概念表。
进阶考点是“TCP 一定比 UDP 慢吗”。答案是否定的。TCP 慢主要慢在握手和拥塞控制收敛过程,但如果是持续的大块数据传输,TCP 用满带宽后不会比 UDP 差太多。UDP 只是没有那些机制,真要自己实现可靠性和流控,成本可能比 TCP 更高。面试官想听的其实是“你知道每个协议为什么这么设计”。
5.2 十个高频追问与一句话破题
| 问题 | 核心考点 | 一句话回答方向 |
|---|---|---|
| 为什么三次握手 | 连接建立的可靠性 | 双方确认彼此的收发能力,并对初始序列号达成一致,防止旧报文干扰 |
| 为什么四次挥手 | 双工关闭的必然性 | 两个方向各自关闭,被动方回完 ACK 后还要等应用层数据发完再发 FIN |
| 为什么 TIME_WAIT 等 2MSL | 可靠关闭与报文消亡 | 让最后的 ACK 有重传余地,同时确保旧报文在网络中自然消失 |
| SYN Flood 怎么防 | 握手漏洞与资源耗尽 | 半连接队列会被伪造 SYN 塞满,可用 SYN Cookie、限速、队列调整缓解 |
| 序列号回绕怎么办 | 32 位序号的比较陷阱 | 利用无符号回绕比较,不能简单按数值大小判定前后关系 |
| 粘包怎么处理 | 字节流与应用层边界 | 应用层自行定义消息边界,常用长度前缀、固定长度、分隔符 |
| TCP 怎么保证可靠 | 确认与重传体系 | 序列号确认 + 超时重传 + 快速重传 + 滑动窗口 + 拥塞控制协同作用 |
| 慢启动为什么是“慢”的 | 拥塞窗口增长策略 | 初始窗口小,指数增长试探带宽,阈值附近转线性避免拥塞 |
| 端口耗尽怎么办 | 四元组与连接数瓶颈 | 调大临时端口范围、复用 TIME_WAIT、改造长连接减少频繁建连 |
| 抓包看到大量重传 | 丢包或异常延迟 | 看 RTT 和重复 ACK,区分网络丢包、接收端迟钝还是中间设备限速 |
答这些题的时候有个通用技巧:先给结论,再给原因,最后补一个实际影响或排查案例。比如“TIME_WAIT 等 2MSL 是为了保证最后的 ACK 能让对方收到,以及让旧包消亡;不做这一步,新连接可能收到旧连接迟到报文,导致数据被错误判定为有效”。你只要能把“机制——目的——反例”讲完整,不论考点怎么变都能应对。
5.3 状态机速记与代码侧排查思路
面试不免会考具体状态。我会建议你自己建两张小表,一张客户端状态表、一张服务端状态表,把“收什么包、发什么包、跳到什么状态”列出来。不要试图一次记住所有状态,从 CLOSED 起步,先记建立连接的五六个状态,再用抓包去验证。
服务端特别注意 LISTEN 到 SYN_RCVD 再到 ESTABLISHED。客户端特别注意 SYN_SENT 到 ESTABLISHED。关闭阶段,服务端容易卡在 CLOSE_WAIT,客户端容易卡在 TIME_WAIT。你只要能独立解释这两个“问题状态”,线上排查就已经赢了一半。
我记得有一次帮同事排查偶发连接失败,系统里 SYN_SENT 一大堆,但 TCP 三次握手的第一个包发出去后完全没回应。抓包确认 SYN 确实到了对端网卡,但回包就是不出来。最后发现是服务端满载,全连接队列溢出了。队列调大加改善业务处理速度,问题解决。这类问题用状态机配合抓包,半小时内能定位,纯粹靠猜可能要搞几天。
6. 真实排障中的 TCP/IP——把知识变成现场能力
6.1 两个必会工具:连接状态查看与抓包分析
纸上谈兵聊再多,不如动手抓一次包。我排查 TCP 问题时常用的套路是:先用命令看连接状态和统计信息,再决定要不要抓包。
连接状态主要看 LISTEN、ESTABLISHED、TIME_WAIT、CLOSE_WAIT 的数量分布。如果 CLOSE_WAIT 暴涨,业务层连接没释放;如果 SYN_SENT 很多,可能对端不可达或端口被防火墙拦了;如果 TIME_WAIT 很多,先看是否是短连接并发太高。
抓包工具我常用 tcpdump 或 Wireshark。tcpdump 适合在服务器上直接抓,抓完导出文件再放到 Wireshark 里看时序。抓包时的两个小习惯:一是明确过滤条件,别一股脑抓全量,比如抓 80 端口、某客户端 IP;二是要保证抓到完整握手和挥手,所以要在连接建立之前就开始抓。
看包时优先开启“分析序列号”和“相对序列号”显示,这样更容易对齐逻辑关系。三个人手一个包,客户端、服务端各抓一份,再比对同一时间点双方看到的窗口、确认号是否一致,很多诡异问题立刻现形:比如某条路径上中间设备偷偷改写了 TCP 选项,或者防火墙拦截了特定长度的包。
6.2 一次连接异常排查的思路复盘
用一个虚构案例来串一遍排查流程。某服务出现偶发性请求超时,监控显示 TCP 重传率升高,连接数波动大。
先看连接状态,发现 ESTABLISHED 数量在超时前后暴增又快速回落,说明连接被大量建连。接着抓包,看到三次握手正常,但数据传输阶段出现重复 ACK,且请求发出去之后隔了很久服务端才回数据。重点看时间戳和 TCP 选项,发现服务端通告窗口在变小,随后客户端暂停发送,等待窗口恢复。
这时候问题已经清晰:服务端接收窗口被占满,说明应用读取速度跟不上网络传输速度。继续往下查,发现服务端用的是同步阻塞读取,单线程处理,遇到一个慢请求就把其他请求也堵住了。最终优化是改异步处理,并调整接收缓冲区和线程模型,重传率立刻降下来。
这个例子里协议机制都是正常的,问题是应用层消费数据太慢。TCP 会通过流量控制把压力反馈给发送方,表现为窗口变小、速度变慢。如果你不懂窗口机制,可能只会觉得“网络卡”,完全不知道怎么定位。协议知识在这里的价值就是帮你把“网络卡”翻译成“对端处理不过来”。
6.3 排障习惯:少凭感觉,多量数据
见过太多次“网络抖动”背锅事件,最后发现是配置变更、中间设备老化或者应用层代码 bug。我现在的排障流程基本是固定的:
- 先确认现象范围和持续时间。
- 用连接统计、接口监控、错误日志圈定大致方向。
- 抓包前先想清楚“我要验证哪个假设”,别盲目抓。
- 抓包后按 TCP 状态和关键字段梳理时间线。
- 得出协议层结论后,再往应用层和部署环境延伸。
另外一个容易被忽视的点:抓包不只是看异常时刻,也要对比正常时刻。没有对照组,很难判断“重传几次算异常”。我会常态化保留一些正常业务流量样本,出问题时可以直接对照,节省大量时间。
TCP/IP 的东西看起来又多又杂,其实真正高频使用的核心不超过十来个机制。你如果能亲手抓几次包,把握手挥手和状态迁移印证一遍,再把滑动窗口和重传行为对应到具体报文上,那些抽象的概念就会一下子落在实处。我在实际项目中常有一种感觉:很多所谓疑难问题,到最后都不是协议层面多玄妙,而是基础机制没吃透、排查路径没理顺。把这十问背后的逻辑链条好好过一遍,不管你是面试还是做开发,都能比以前从容不少。