ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

传输层核心机制解析:TCP可靠传输、三次握手与拥塞控制

传输层核心机制解析:TCP可靠传输、三次握手与拥塞控制

1. 传输层到底解决了什么问题

1.1 从“寄快递”理解端到端通信

网络分层里,传输层是承上启下的关键一层。往下,它依赖网络层把数据包送到目标主机;往上,它要给应用层提供一套好用的通信接口。很多人复习到这里会卡住,觉得概念太抽象。我习惯用一个类比:网络层像快递公司的干线运输,只负责把包裹从A城市的集散中心送到B城市的集散中心,至于包裹里装的是什么、收件人到底在不在家、要不要签收,它一概不管。传输层就是那个真正跟收件人打交道的角色,它要确认对方在不在、包裹有没有丢、顺序对不对。

这个类比能解释一个核心问题:为什么网络层已经提供了主机到主机的通信,还需要传输层?因为一台主机上同时跑着浏览器、聊天工具、邮件客户端,网络层把数据送到主机后,根本分不清该交给哪个程序。传输层通过端口号解决了进程到进程的区分问题,通过复用与分用让多个应用共享同一条网络链路。复用就是发送方多个应用进程共用传输层往下发,分用就是接收方传输层根据端口把数据交给正确的应用进程。

复习传输层,脑子里要始终挂着两条主线:一条是可靠传输怎么实现,另一条是效率怎么保证。围绕这两条主线,TCP和UDP走出了完全不同的路。

1.2 TCP与UDP的分工不是“好坏”而是“场景”

初学者容易把TCP当成“好的”,UDP当成“差的”,这是个误区。两者是面向不同需求设计的。TCP提供面向连接的、可靠的、基于字节流的服务;UDP提供无连接的、尽最大努力交付的、基于报文的服务。

对比维度TCPUDP
连接方式面向连接,需三次握手无连接,直接发
可靠性确认、重传、排序、去重不保证可靠
传输单位字节流数据报
首部开销20字节起8字节
流量控制有滑动窗口无
拥塞控制有无
适用场景文件传输、网页、邮件实时音视频、DNS查询

选型逻辑很直接:如果数据一个字节都不能错,比如下载文件、传文档,用TCP;如果追求低延迟、能容忍少量丢包,比如语音通话、直播,用UDP。实时场景里,丢一帧画面比卡顿两秒的体验好得多,重传反而添乱。

注意:UDP并非“不可靠就不能用”,很多实时协议在应用层自己做轻量级的丢包处理,把控制权握在自己手里,而不是交给传输层。

2. TCP可靠传输的三大支柱

2.1 滑动窗口:让发送方“边走边等”

可靠传输最朴素的想法是“发一个,等确认,再发下一个”,这叫停止等待协议。问题是效率太低,链路往返时间被白白浪费。滑动窗口的核心思想是:发送方可以连续发送多个报文段,不必每发一个就停下来等确认。

窗口的本质是一段可以连续发送而无需等待确认的序号范围。发送方维护一个发送窗口,窗口内的序号可以发送,收到确认后窗口向前滑动。这里有个关键点:窗口大小决定了流水线的深度。窗口越大,能同时在途的未确认数据越多,吞吐量越高,但对接收方缓冲和网络承载的要求也越高。

接收方也有一个接收窗口,用来告诉发送方“我还能收多少”。这就是流量控制的来源——接收方通过窗口字段动态调整发送方的发送速率,防止自己缓冲区被撑爆。我复习时总结过一句话:滑动窗口既是可靠传输的机制,也是流量控制的载体,一箭双雕。

2.2 超时重传:时间到底设多长

发送方发出报文后启动计时器,超时未收到确认就重传。难点在于超时时间怎么定。设太短,明明没丢也重传,浪费带宽还加重拥塞;设太长,真丢了要等很久才补,延迟高。

TCP采用自适应重传时间。它先采样每个报文段的往返时间RTT,然后算加权平均往返时间RTTs,再算RTT偏差的加权平均RTTd,最后超时重传时间RTO = RTTs + 4×RTTd。这个公式的直觉是:以平均往返时间为基准,再加上四倍波动幅度作为安全余量。网络抖动越大,RTTd越大,RTO就越保守。

这里有个经典陷阱叫Karn算法:重传的报文段不能用来更新RTT采样值。因为你收到的确认分不清是原始报文还是重传报文的,如果拿它算RTT会把时间算错。所以重传时不更新RTTs,同时每重传一次把RTO翻倍,避免在网络拥塞时火上浇油。

2.3 选择确认与快速重传:别傻等超时

超时重传有个天然缺陷:必须等到计时器到期。如果发送方连续发了1、2、3、4、5,中间2丢了,接收方收到3、4、5会一直重复确认“我要2”。发送方收到三个重复确认,就知道2大概率丢了,不必等超时,立刻重传2,这叫快速重传。

更进一步,接收方可以告诉发送方“我收到了哪些不连续的块”,这叫选择确认SACK。有了SACK,发送方只重传真正丢失的那部分,而不是把丢失点之后的所有数据全部重传。这在窗口很大、丢包零散时能省下大量带宽。

实操心得:复习时把“累计确认”和“选择确认”对比着记。累计确认说“到这个序号为止我全收到了”,选择确认说“除了这几块,其他我都收到了”。前者简单但可能重传多余数据,后者精细但首部开销更大。

3. 三次握手与四次挥手,别只背流程

3.1 三次握手为什么不是两次或四次

三次握手的流程大家都背过:客户端发SYN,服务端回SYN+ACK,客户端再发ACK。但面试和考试爱问“为什么是三次”。

两次不够,是因为服务端无法确认客户端的接收能力。客户端发SYN,服务端回SYN+ACK,如果到此为止,服务端不知道客户端到底收没收到自己的SYN。客户端再发一个ACK,服务端才能确认“对方既能发也能收”。本质上是双方都要确认自己和对方的收发能力都正常,这需要至少三次交互。

四次没必要,是因为服务端的SYN和ACK可以合并成一个报文发出去,省一次交互。所以三次是理论最少且够用的次数。

三次握手还顺带完成了序号同步。双方各自随机选一个初始序号,通过握手交换并确认,为后续可靠传输打好基础。初始序号随机化是为了避免旧连接的残留报文干扰新连接。

3.2 四次挥手与TIME_WAIT的存在意义

挥手比握手多一次,因为TCP连接是全双工的,两个方向要分别关闭。主动关闭方发FIN,对方回ACK,此时一个方向关了;对方数据发完后也发FIN,主动方回ACK,连接才彻底关闭。中间那次ACK和FIN不能合并,因为被动关闭方可能还有数据要发。

主动关闭方在发完最后一个ACK后,要进入TIME_WAIT状态,等待2倍报文最大生存时间。这个等待有两个目的:一是确保最后一个ACK能到达对方,如果丢了对方会重发FIN,自己还能再回一次ACK;二是让本次连接的所有报文在网络中彻底消失,避免干扰使用相同端口的新连接。

注意:TIME_WAIT是主动关闭方才有,服务端如果主动关闭大量连接,会积累很多TIME_WAIT,占用端口资源。这是高并发服务端要特别关注的调优点。

3.3 握手挥手中的状态迁移速记

复习状态机时,我建议按“谁主动”来分。客户端主动打开:CLOSED→SYN_SENT→ESTABLISHED。服务端被动打开:CLOSED→LISTEN→SYN_RCVD→ESTABLISHED。主动关闭:ESTABLISHED→FIN_WAIT_1→FIN_WAIT_2→TIME_WAIT→CLOSED。被动关闭:ESTABLISHED→CLOSE_WAIT→LAST_ACK→CLOSED。

其中CLOSE_WAIT是个高频考点,也是实战排障重点。如果服务端出现大量CLOSE_WAIT,说明对方已经关了,但自己迟迟没调用关闭,通常是应用层代码没正确释放连接。TIME_WAIT多则通常是主动关闭方,属于正常现象,但数量过大要考虑连接复用。

4. 拥塞控制:网络堵车时怎么办

4.1 慢启动与拥塞避免的配合

流量控制是照顾接收方,拥塞控制是照顾整个网络。TCP拥塞控制的核心变量是拥塞窗口cwnd,发送方实际能发的数据量取拥塞窗口和接收窗口的较小值。

慢启动阶段,cwnd从1个报文段开始,每收到一个确认就翻倍,呈指数增长。名字叫“慢启动”其实增长一点不慢,只是起点低。增长到慢启动阈值ssthresh后,进入拥塞避免阶段,改为每个往返时间加1,呈线性增长。这样设计是为了先快速探到网络容量的大致位置,再小心逼近。

4.2 超时与快重传下的不同反应

一旦检测到拥塞,TCP要降速。但降速方式取决于怎么发现的拥塞。

如果是超时,说明网络可能严重拥塞,反应要激烈:ssthresh设为当前cwnd的一半,cwnd直接降到1,重新慢启动。

如果是收到三个重复确认(快重传),说明网络还能送回来一些报文,拥塞没那么严重,反应温和些:ssthresh和cwnd都设为当前cwnd的一半,然后进入快恢复,cwnd从这个值开始线性增长,不回到1。

拥塞信号ssthreshcwnd后续阶段
超时当前cwnd一半降为1慢启动
三个重复ACK当前cwnd一半降为一半快恢复

这个对比表我复习时反复看,因为考试特别爱考两种情况的区别。

4.3 从Tahoe到Reno再到现代算法

早期TCP Tahoe版本无论超时还是快重传都把cwnd降到1。后来Reno版本引入快恢复,区分了两种情况。再往后有NewReno、SACK、CUBIC等改进。CUBIC用三次函数代替线性增长,在高带宽长延迟网络里表现更好,也是很多现代系统的默认选择。

复习时不必深挖每个算法的公式,但要理解演进逻辑:网络带宽越来越大、延迟越来越高,老算法增长太慢,填不满管道。新算法都在解决“如何更快地探到并稳定在合适的发送速率”这个问题。

5. 那些容易混淆的细节

5.1 端口号与套接字的区别

端口号是传输层用来标识进程的16位数字,范围0到65535。其中0到1023是熟知端口,1024到49151是登记端口,49152到65535是临时端口。套接字是IP地址加端口号的组合,唯一标识网络中的一个通信端点。一条TCP连接由四元组唯一确定:源IP、源端口、目的IP、目的端口。

这里有个易错点:同一个端口可以出现在多条连接里,只要四元组不同就行。比如服务端80端口可以同时服务成千上万个客户端,因为每个客户端的IP和端口不同。

5.2 报文段、数据报、分组别混用

不同层的数据单元叫法不同。应用层叫报文,传输层TCP叫报文段,UDP叫用户数据报,网络层叫分组或数据报,数据链路层叫帧,物理层叫比特。复习时用词准确能避免很多概念混淆。

5.3 为什么UDP首部只有8字节

UDP首部只有源端口、目的端口、长度、校验和四个字段,各2字节。它不做可靠传输、不做流量控制、不做拥塞控制,所以不需要序号、确认号、窗口这些字段。首部小意味着开销低、处理快,这正是实时应用看重的。

校验和是UDP唯一的差错检测手段,而且它是可选的。IPv4里可以不算,IPv6里强制要算。校验和覆盖首部和数据,还加了一个伪首部,包含源IP、目的IP、协议号等,目的是验证数据确实送到了正确的主机和协议。

6. 复习方法与实战排查

6.1 用抓包工具把抽象变具体

光看书很难真正理解握手挥手。我建议用抓包工具实际抓一次访问网页的过程,亲眼看SYN、SYN+ACK、ACK三个包,看序号和确认号怎么变化。再抓一次关闭过程,看FIN和ACK的交互,观察TIME_WAIT状态持续多久。

抓包时重点看几个字段:序号、确认号、窗口大小、标志位。把抓到的包和书本流程一一对应,抽象概念立刻落地。这比背十遍流程图都管用。

6.2 常见问题速查表

现象可能原因排查方向
大量CLOSE_WAIT应用未关闭连接检查代码连接释放逻辑
大量TIME_WAIT主动关闭过多启用连接复用或调整内核参数
连接建立慢握手丢包或重传抓包看是否有SYN重传
吞吐量上不去窗口太小或拥塞检查窗口字段和丢包率
偶发连接失败端口耗尽查看临时端口使用情况

6.3 复习节奏建议

传输层知识点密集,我自己的复习节奏是:第一遍通读建立框架,知道有哪几块;第二遍逐个攻克,把滑动窗口、拥塞控制这些硬骨头画图推导;第三遍做真题,用题目检验哪里还有漏洞;第四遍合上书自己复述整个可靠传输的机制链条。

推导题一定要动手算。比如给一组RTT采样值,自己算一遍RTTs、RTTd和RTO,算过一遍比看十遍公式印象深。拥塞窗口的变化过程也要自己画时间轴,标出每个阶段cwnd怎么变。

我个人在实际操作中的体会是,传输层最忌讳死记硬背。把“为什么需要这个机制”“不用它会怎样”这两个问题想清楚,大部分知识点都能自己推出来。比如想清楚为什么需要滑动窗口,就自然理解了流量控制;想清楚为什么需要拥塞控制,就自然理解了慢启动和快恢复的分工。复习到能给别人讲明白的程度,这一章才算真正拿下。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进