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

资讯详情

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

TCP传输层详解:从三次握手到拥塞控制,抓包实践指南

TCP传输层详解:从三次握手到拥塞控制,抓包实践指南 说实话计算机网络学到第三篇才算真正进入“动脑子”的阶段。前两篇里物理层、数据链路层那些事说难不难但顶多算把线缆、MAC地址、交换机这些“硬件层面的规矩”盘清楚。可一进传输层问题性质立刻就变了你要开始面对“两个进程之间怎么保证数据不丢、不乱、不重复”这背后全是协议设计层面的权衡。这篇文章不是教材的复读机也不是单纯把谢希仁第八版里传输层的章节重新排列一遍而是把我复习、实操、排查问题过程中踩过的坑和想明白的弯子掰开揉碎地讲给还在跟TCP较劲的同学听。先说清楚它适合谁。如果你正在准备408考研、期末复习或者已经学完一遍觉得“TCP好像懂了但一做题就错”那这篇大概率对你有用。如果你是个正在面试路上背八股文的兄弟这篇也能帮你把三次握手、四次挥手、拥塞控制这些高频考点背后的逻辑串起来而不是死记硬背。文中我会带一个完整的Wireshark抓包过程让你亲眼看看课堂上讲的那些状态转换是怎么在真实网络里发生的。总之这篇不谈虚的全是能落地的理解。1. 先把你脑子里那根“分层”的弦再拧紧一遍1.1 网络层管“路”传输层管“货”很多人学传输层最大的问题是没搞清它和网络层的分工导致一堆概念混在一起。我打个比方网络层就像一个大城市的道路系统负责规划从A小区到B小区走哪条路它关心的是“路通不通”而传输层就像快递公司的分拣中心负责把你寄出的每个包裹打包、贴单、跟踪、确认签收它关心的是“货到没到、对不对”。这个类比能帮你理清很多细节。IP地址解决的是“在哪里”的问题端口号解决的是“交给谁”的问题。虽然IP封包在网络上传输时确实要靠路由器这个“道路调度员”一站一站转发但只有到了目的主机传输层才会把数据从内核缓冲区里捞出来根据端口号投递给对应的应用程序。所以严格说端到端的“可靠”是传输层给的不是网络层给的。1.2 为什么“可靠”两个字如此烧钱TCP最大的特点就是“可靠传输”但可靠从来不是白来的。它靠的是编号、确认、重传、排序、去重这一整套机制。你可以把TCP理解成一个极度较真的项目助理每发出一份文件都要抄录一个编号然后等着对方签字回执如果规定时间内没收到回执哪怕文件可能早就到了他也必须再发一份。这个“可能早就到了”很关键因为网络层不保证不重复、不保证顺序传输层只能用这套笨办法把网络层捅出来的娄子一个个堵上。正因为这样TCP的头开销比UDP大得多连接建立要握手断开要挥手传输过程中还有一堆计时器和状态机在后台运转。而UDP就是那种“文件扔出去就不管了”的甩手掌柜速度快、开销小适合视频通话、游戏实时交互这种丢了就丢了、下一帧很快补上的场景。两者没有谁更高级只有合不合适。1.3 抛开考前突击思维去理解状态机我在复习的时候有一个很深的体会传输层这一章如果靠着考前狂背状态迁移图考完三天基本忘光。但如果你理解了每个状态产生的原因比如为什么客户端的TIME_WAIT要等2MSL那这张图不用背也能推导出来。后面我会专门用抓包来看这些状态你就明白它们不是协议设计者拍脑袋发明的而是每一个都对应一个真实的网络困境。2. 三次握手与四次挥手你以为是仪式感其实全是血泪教训2.1 三次握手的每一步在防什么三次握手的标准答案谁都会背客户端先SYN服务器回SYNACK客户端再发ACK。但很少有人追问为什么不是两次为什么非要交换SYN归根结底两封信要想确认双方收发都正常需要四个信息我能发、你能收你能发、我能收。TCP把“我能发你能收”的证明放在SYN里把“你能发我能收”的证明放在ACK里。如果只握手两次服务器就没办法确认客户端的接收能力是否正常。更重要的是在不可靠的网络里旧的、延迟的连接请求可能迟到如果只有两次握手服务器收到一个迟到的SYN就会建立一条半吊子连接白白占着资源。加上第三次握手服务器只有在收到客户端回复的ACK之后才正式进入ESTABLISHED这样即使迟到的SYN骗到了服务器的SYNACK也骗不到最后的那个ACK连接终究建不起来。我记得第一次理解这个设计的时候脑子里蹦出来一句话三次握手不是礼貌是双方证明“我发的东西你能收到你发的东西我能收到”的最小代价。2.2 四次挥手是谁先松手的问题断开连接比建立连接更微妙因为TCP连接是双向的一条连接其实承载着两个单向的数据流。所以每一方都要单独关闭自己的那半边。客户端说“我不再发了”这算是FIN服务器可能还有数据要发先把剩下的发完再回一个FIN一来一回加上各自的ACK确认就成了四次挥手。这里有一个特别容易在考试和面试里栽跟头的细节服务器收到FIN之后如果飞快地回了ACK并紧接着也发了FIN抓包时可能会看到三次挥手。但这不代表协议错了只是两个方向的数据恰好都发完了服务器把ACK和FIN合并到同一个报文段里捎带出去了。2.3 TIME_WAIT的2MSL到底在等什么这是我见过最容易被背答案但完全没读懂的知识点。主动关闭连接的一方在发送最后一个ACK之后要进入TIME_WAIT状态并且等待2MSL两倍的报文最大生存时间才能彻底关闭。一句话解释就是我得给最后一个ACK留出“万一丢了还能重传”的时间还得确保这条连接上所有迷路的旧报文都在网络里彻底死掉不会干扰下一份使用相同端口的新连接。细品一下你就会发现这其实是一种对现实物理世界的投降TCP可以在逻辑上做到可靠但网络硬件决定了报文会迟到、会复制、会乱序协议设计者只能把“旧报文完全消失”的时间上限估成2MSL并且强制主动关闭方在这段时间里继续占着端口。所以MySQL、Nginx这类服务大量出现TIME_WAIT状态根本就是正常现象真正需要处理的是TIME_WAIT堆积到把端口耗光而不是看到它就慌。2.4 状态迁移表应该怎么记我不建议死记状态图。你把三次握手和四次挥手里每个角色的动作拆开按时间线画一遍客户端发出SYN之后是SYN_SENT收到SYNACK后是ESTABLISHED服务器从LISTEN被动收到SYN回复SYNACK后变成SYN_RCVD再收到ACK变成ESTABLISHED。挥手侧同理FIN_WAIT_1、FIN_WAIT_2、TIME_WAIT服务器那边是CLOSE_WAIT、LAST_ACK。把状态跟“发出去没、收到没、还在等谁”对应起来就再也不会弄混了。3. 可靠传输机制TCP的“保障体系”是怎么运转的3.1 停止等待协议最笨但最基础的保证讲可靠传输教材一般先讲停止等待协议Stop-and-Wait ARQ。每次发一个分组必须等收到确认才发下一个。这当然是可靠的但效率低得让人抓狂。想想你寄一封信要等对方回信才寄下一封路上时间全浪费了。这个协议的意义在于它引出了“确认”“超时重传”“序号”这些最根本的概念。带序号其实比大家想象中重要得多。没有序号接收方根本分不清来的是新报文还是重传的旧报文。有了序号哪怕同一份报文在网络里复制出了好几份接收方也能靠查重扔掉多余的。这是后面一切可靠机制的地基。3.2 滑动窗口既保证可靠又填满管道TCP没停在停止等待这么原始的水平。它搞了一个“滑动窗口”允许发送方一口气发好几个报文出去不必等每一个的确认回来这个窗口大小最多可以有多少数据在飞行取决于接收方的通告窗口rwnd和网络的拥塞窗口cwnd。滑动窗口的工作方式有点像流水线发送方有一个窗口左侧是已经确认的右侧是还没允许发送的每收到一个ACK窗口就往前滑一格新数据就能进入。这样网络链路始终有数据在跑不会像停止等待那样发一下就闲置半天。接收方也不必对每个包单独确认它可以把这窗口里连续收到的多个包攒起来用一个累计ACK一并确认这就是累计确认。这个机制省掉了大量反馈开销也简化了重传逻辑发送方只要知道“序号n之前全部收到”就只管从n开始补。3.3 超时重传和快重传两种补车胎的方式超时重传很好理解发出去之后开启计时器到点没等到确认重发。但它的缺陷也明显如果一个报文只是被网络延迟了发送方却已经等不及重传了一份接收方收到两个一模一样的包虽然能靠查重丢掉但网络浪费已经发生了。更尴尬的是重传超时RTO算短了容易造成大量重复包算长了又会让数据传输卡顿。所以RTT往返时间的测量和估算在TCP实现里是一门学问不能拿上次的值直接用得做加权平均。快重传机制就聪明一些。接收方一旦发现某个期望的序号没来但后面又连续收到了几个数据包它会立刻反复发送对那个缺失序号之前的ACK也就是所谓的三次重复ACK。发送方收到三个重复ACK基本上可以断定该序号真的丢了不用等超时马上重传。这比干等超时快得多尤其在高带宽长距离的链路上超时等待的浪费非常致命。3.4 流量控制与拥塞控制一个管手一个管路这两个概念是八股文最爱也是最容易混淆的。流量控制是接收方管发送方我缓冲区快满了你慢点发。它靠的是TCP头里的窗口字段接收方每次发ACK都会带上当前还能接收多少字节rwnd。如果rwnd变成0发送方就得停下来甚至还要定期发窗口探测包问问什么时候能继续。拥塞控制是网络环境管发送方前面链路堵了你自觉一点别把数据一股脑往里灌。这里的拥塞窗口cwnd是发送方自己维护的虚拟阀门配合一套算法慢启动、拥塞避免、快重传、快恢复。我记得最容易记忆的方式是慢启动阶段窗口指数增长因为刚建立连接时不知道网络深浅先一点一点摸路到阈值ssthresh后转入拥塞避免变为线性增长因为越接近网络容量越要谨慎一旦发生超时重传说明堵死了阈值直接减半窗口重新回1如果只是三次重复ACK说明还没彻底堵死阈值也减半但窗口从阈值附近继续快速恢复这就是快恢复。这套机制的巧妙之处在于TCP竟然能完全靠端到端的信号猜测网络内部的拥堵程度而不需要路由器主动发什么“堵车通知”。正因为它没有中间节点的显式反馈很多判断都是启发式的所以各种拥塞控制算法的变种——Reno、Cubic、BBR——才会层出不穷面试官特别爱从这里问深入一层。4. 实操用Wireshark亲眼看一次TCP连接4.1 搭建最小实验环境纸上谈兵没意思我建议你自己抓一次包。不需要什么高端器材一台能联网的Windows或Linux电脑装好Wireshark就行。抓包的时候为了干净尽量不要边浏览网页边抓否则会话太多很难聚焦。我先讲一下过滤器的用法。 TCP连接的三次握手和挥手可以用过滤器 tcp.flags.syn1 或 tcp.port8080 来筛选。如果你想连一个刚好会长时间不动的HTTP连接使用 curl 命令最方便curl -v http://example.com这个命令会立刻建立一个TCP连接发HTTP请求拿到响应后马上关闭。一整个过程对抓包来说非常紧凑。4.2 抓包解读从SYN到ESTABLISHED在Wireshark里选中和example.com通信的那几条报文你会看到排列非常整齐的三条记录。第一条是客户端发出的SYN报文Seq序号是一个随机的初始值ISN比如 3633394328。第二条是服务器的SYNACK它的Ack字段等于客户的ISN加1表示“你的SYN我收到了我的初始序号是这个”。第三条是客户端的ACKSeq等于服务器的ISN加1Ack等于服务器引导的序号加1。我重点提醒一个细节Wireshark默认开启了“相对序号”显示就是把初始序号显示成0和1这样对阅读友好但如果你在抓包时对不上号记得去View菜单里把相对序号关掉看真实的绝对序号。这道坎我当年卡了很久总觉得教材上的序号跟抓包里的对不上其实只是显示方式的差异。4.3 如何观察重传、乱序和重复ACK要让重传现出原形最简单的办法是在防火墙层面对某个端口做丢包模拟或者直接用Linux内核的 netem 模块tc qdisc add dev eth0 root netem loss 20%这行命令会让eth0接口丢20%的包。你再用大文件下载或者多线程请求制造大量流量Wireshark的分组列表里就会出现红底标记的TCP Retransmission以及它前面跟着的TCP Dup ACK。如果丢包发生在连接的中后段你能清晰看到接收方连续发了几个Dup ACK之后发送方马上补发了那个缺失的包。这种直观感受比看十遍书上图都好用。观察完之后别忘了用如下命令把队列恢复tc qdisc del dev eth0 root4.4 抓包看三次握手里的SYN重传还有一个值得做的实验故意抓一个到“黑洞”地址的TCP连接请求比如抓包访问一个防火墙后面不存在的主机。客户端会连续重发SYN并且由于超时退避间隔会从1秒、2秒、4秒指数增长重发的次数会受系统 tcp_syn_retries 参数控制。这个现象看起来好像只是单纯的“失败”但它直观地说明了TCP计时器和重传机制是怎么工作的。你抓到的每一次SYN重传都对应一次“等不到对端确认就再来一次”的坚持。5. 高频问题排查与期末/面试实战5.1 TIME_WAIT堆积到端口耗尽线上环境里高并发的短连接服务比如Nginx作为代理最容易遇到TIME_WAIT堆积。如果你用 netstat 看到几万个TIME_WAIT状态不要慌先判断是不是正常现象。短连接本来就必然产生TIME_WAIT关键要看端口是否够用。缓解方法常见有三种一是打开时间戳选项tcp_timestamps让TIME_WAIT可以被安全回收二是开启 tcp_tw_reuse在出站连接上复用TIME_WAIT状态的端口三是调小 tcp_max_tw_buckets但要小心这只是让系统尽早释放套接字不等2MSL结束安全性会有一定下降。我个人的建议是优先理解业务形态能改成连接池长连接最好这比在系统参数里东调西调根治得多。5.2 收到“网络中存在异常流量”提示的排查思路很多人遇到过打开某个网站时弹出“您的网络中存在异常流量请稍后重试”这类提示一时摸不着头脑。这往往不是本机真的中毒而是服务器端的安全网关根据源IP的并发连接数、请求频率、TCP行为特征等自动判定为有自动化程序在批量请求。如果你在公司、学校、NAT出口后面整段出口IP的用户共享同一个公网地址其中某一台机器出了问题连带所有人都会出现这个提示。从TCP层面去查通常是三个方向第一并发连接数异常用 netstat -an | grep ESTABLISHED | wc -l 看看当前建连数是否远超往常第二出站重传率过高说明本机到目标站点的网络路径质量差或者有程序在疯狂向不存在的端口连接第三是否有后台进程在跑高频轮询比如某些P2P下载器、网页爬虫、云同步客户端都会制造大量周期性TCP连接。关掉可疑进程后稍等几分钟再试绝大多数情况下提示就会消失。5.3 期末复习的最高效顺序如果你时间紧我建议按“分层但不平均发力”的方式复习物理层和数据链路层靠总结题解速刷网络层重点抓IP编址和路由协议传输层则要花大力气把可靠传输、滑动窗口、拥塞控制这三板斧吃透应用层只需要关注HTTP、DNS、DHCP几个常用协议。传输层在408考试和期末考里都是拉开分差的地方尤其是滑动窗口和拥塞窗口的数值计算题一定要亲手做几道把窗口单位是字节还是报文段搞清楚很多扣分点都是单位换算问题。如果你用谢希仁第八版教材重点看第五章如果跟着王道考研书复习第4章传输层那部分的课后题难度和真题方向很接近。湖科大教书匠的视频把滑动窗口画得很直观适合第一遍建立图像记忆但看完一定得自己上手做题。5.4 面试高频八股清单给你整理一张我自己面试准备时的清单标注了我认为容易翻车的地方主题最容易答错的点一句话正解三次握手为什么不是两次回答“为了确认双方收发能力”不够深入还要补充防旧SYN建立无效连接、交换初始序号四次挥手为什么比握手多一次只背流程不会解释一条连接是两个单向流各自独立关闭TIME_WAIT为什么是2MSL只背“保证最后ACK到达”还要说确保旧报文在网络中过期防止串接到新连接流量控制vs拥塞控制混为一谈一个管接受能力一个管网络负载快重传多少次触发误以为连续收到2个重复ACKTCP标准是收到3个重复ACK才触发快重传累计确认的作用只答“效率高”确认序号n表示n以前全部正确接收这张表不是让你背答案而是帮你检查自己到底哪一处还没真正想通。面试官问得很深的时候不怕你说不知道最怕你给一个听起来对但经不起追问的答案。6. 一个扩展UDP的低延迟与可靠化的边界6.1 UDP适合的场景以及“伪可靠UDP”聊完TCP还是要给UDP说几句公道话。视频会议、在线游戏、VoIP这些应用延迟和抖动比丢包可怕得多。如果你用TCP传音视频一旦网络抖动触发重传画面就会卡成PPT。所以这类应用宁肯丢几帧也要保证下一帧立刻送到只有UDP这种不带任何确认和重传的协议才能满足。现在很多高性能中间件会在UDP之上自己实现可靠性。比如QUIC协议就运行在UDP之上自己搞一套连接建立、加密、重传和流量控制。这其实是一个很值得思考的信号TCP为了兼容几十年前的老网络背负了太多历史包袱比如队头阻塞、握手延迟。新一代协议宁愿底层不保证把可靠性拿到应用层量身定制反而能做更精细的控制。6.2 面试加分从“会背”到“会设计”如果你能在面试里聊到这层建议顺手提一句如果要你自己设计一个可靠UDP你会怎么处理包的编号、确认、超时重传和乱序排序。这比单纯复述TCP机制更能展示分布式系统的思维。内核提供的TFOTCP Fast Open这些机制本质上都是在保留TCP可靠性的前提下尽量减少握手开销这再一次说明协议设计没有银弹每一个选择都是在可用性、开销、复杂度之间做权衡。7. 给再学一遍的人的几句话我没打算把这篇写成“从入门到精通”的大全因为传输层值得写的内容实在太多一篇根本塞不下。我更希望的是你读完之后能改变一个思维习惯不要把RFC和教材上的机制当作不可置疑的规定去背而是试着去想“如果让我设计我会遇到什么困难我会怎么解”。然后再回头去看那些机制你会发现它们每一个都像是巨人在跟你分享当年踩坑后的悔过书。我自己的学习路径也是这样走过来的。第一遍看完三次握手只记住了流程第二遍为了应付考试背下了状态图第三遍对着Wireshark亲眼看到那些报文才真正觉得通了。后来的某一天当线上一个服务的连接数突然翻倍我第一反应不是去翻文档而是直接抓包看一眼果然是一堆半开连接卡在握手阶段。那一刻我对这位老朋友就只剩下尊敬了。最后给你留一个小实验用 nc -l 8000 在A机器起一个监听端口用B机器 nc 连上去在A机器 CtrlC 断开看看B机器这边会不会一直卡住过很久才报错。然后想一下这个过程经历了什么状态。把这个问题想明白你对TCP的认知就又上了一个台阶。
返回列表