深入理解 TCP 协议(二):TCP 可靠传输与高效通信机制详解
目录前言一、确认应答机制二、超时重传机制2.1 理解丢包的本质2.2 超时时间如何确定三、滑动窗口机制3.1 深度理解 send/write 和发送缓冲区3.2 理解滑动窗口3.3 理解结合滑动窗口发送报文的流程3.4 滑动窗口机制的进一步理解——发送缓冲区的空间复用3.5 滑动窗口机制的进一步优化——快速重传机制3.6 滑动窗口的大小如何确定四、流量控制机制4.1 零窗口探测4.2 16 位窗口大小的限制与窗口扩大因子五、拥塞控制机制5.1 拥塞窗口5.2 TCP 如何判断网络是否拥塞5.3 慢启动5.4 拥塞避免5.5 发生网络拥塞后怎么办六、延迟应答机制延迟应答与流量控制七、捎带应答机制八、总结前言在上一篇文章中我们介绍了 TCP 协议的基本概念以及 TCP 报头格式对 TCP 报头中的各个字段进行了详细介绍。我们知道TCP 报头中存在许多非常重要的字段例如序号用于标识 TCP 字节流中的数据位置确认序号用于表示接收方已经成功接收的数据范围窗口大小用于告知发送方当前能够接收的数据量标志位用于表示当前 TCP 报文的作用和连接状态。但是如果仅仅知道这些字段分别表示什么实际上还没有真正理解 TCP 是如何工作的。例如我们会产生许多问题客户端发送的数据如果在网络中丢失了TCP 是如何发现数据丢失的如果客户端没有收到服务器的确认是不是就一定意味着数据没有到达服务器客户端连续发送多个 TCP 报文后服务器是如何确认已经收到哪些数据的如果发送方的发送速度远远大于接收方的处理速度TCP 又是如何避免接收缓冲区被大量数据占满的如果网络本身发生拥塞TCP 又是如何判断当前网络状况并调整自己的发送速度的这些问题都不是某一个 TCP 报头字段单独完成的而是由 TCP 中的一系列机制共同完成的。因此在上一篇文章认识了 TCP 报头之后本文将进一步从TCP 的工作机制出发深入理解 TCP 是如何实现可靠、高效的网络通信的。本文主要介绍以下几个重要机制确认应答机制超时重传机制滑动窗口机制流量控制机制拥塞控制机制延迟应答机制捎带应答机制这些机制并不是彼此完全独立的而是相互配合共同构成了 TCP 的核心工作方式。例如上一篇文章中介绍的序号和确认序号在确认应答、超时重传以及滑动窗口等机制中都会发挥重要作用而上一篇文章介绍的窗口大小字段则是 TCP 实现流量控制的重要基础。所以从这一篇开始我们不再只是关注“TCP 报头中的这个字段是什么”而是进一步研究“TCP 为什么需要这个字段以及这些字段是如何协同工作的”接下来我们首先从 TCP 最基础的可靠性机制——确认应答机制开始。一、确认应答机制在上篇文章中我们介绍了确认应答的过程即发送端发送一个 TCP 报文客户端收到它时会给发送端发送一个 ACK 报文。ACK 报文它本质就是一个 TCP 报文只不过 TCP 报头中 ACK 标志位置为 1告诉对端该报文是一个应答报文表明确认序号有效即说明发送 ACK 报文的一端告诉对端 我收到了你发送过来的数据 。在 TCP 协议中TCP 将发送缓冲区中每个字节的数据都进行了编号即序列号。对于发送出去的 TCP 报文它的序号为要发送数据的起始序号。例如上图中主机 A 发送的第一个 TCP 报文它的序号为 1当主机 B 接收到主机 A 发送的 TCP 报文主机 B 会给主机 A 发送一个 ACK 报文。该 ACK 报文的确认序号为接收的 TCP 报文的序号 接收的 TCP 报文的数据长度。再次明确并加深确认序号的定义接收方已经收到指定序号之前的所有数据下一次希望接收的数据序号。TCP 最基础且最重要的机制 ——理解确认应答机制的过程和本质主机 A 向主机 B 发送 TCP 报文时如果主机 A 收到该报文的确认应答那么主机 A 就一定能保证发送的数据被主机 B 已经接收。如果主机 A 没有收到该报文的确认应答那么对于主机 A 来讲主机 B 可能收到也可能没有收到。对于主机 A 来讲为了保证数据传输的可靠性就必须通过某种机制来重新发送。总结一句话单独的确认应答机制不能保证数据最终一定到达但可以保证确认应答报文中确认序号前的数据一定到达。二、超时重传机制在确认应答机制中我们分析到对于发送方如果没有收到应答报文发送方无法保证发送的 TCP 报文是否被接收方接收所以发送方需要重新发送该 TCP 报文而发送方重新发送该 TCP 报文的机制被称为超时重传机制。2.1 理解丢包的本质对于发送方来讲如果收到应答报文就一定保证接收方收到数据。如果没有收到应答报文存在两种丢包情况1. 发送方发送的 TCP 报文在网络传输过程中丢失2. 接收方接收到 TCP 报文接收方发送的 ACK 报文在网络传输过程中丢失这两种情况都导致一种结果发送方无法一定保证接收方接收到 TCP 报文。而 TCP 是可靠的所以对于这两种情况发送方都需要重新发送该 TCP 报文。确认序号功能之一 —— 去重对于情况 2细想的人肯定能发现问题对于接收方它难道要接收已经接收过的报文吗如果它不接收是根据什么来判断报文应不应该接收对于接收方它是不会接收已经接收过的报文它是通过确认序号来判断的。以上图为例发送方第一次发送接收方收到报文后接收方会将确认序号维护起来它的值为 1001对于发送方第二次发送发送方发送的 TCP 报文序号为 1接收方接收到该报文会根据确认序号来判断该报文是否已经收到如果已经收到就直接丢弃。2.2 超时时间如何确定那么TCP 中的超时时间是如何确定的呢理想情况下我们希望找到一个合适的超时时间使得发送方能够在该时间内正常收到接收方的 ACK。但网络环境是不断变化的TCP 报文从发送方到接收方再从接收方返回发送方所需要的时间并不是固定的因此超时时间也不能简单地设置成一个固定值。如果超时时间设置得太长那么即使 TCP 报文已经丢失发送方也需要等待较长时间才能进行重传降低通信效率如果设置得太短又可能导致 ACK 只是正常传输时间较长却被发送方误认为报文丢失从而进行不必要的重传。因此TCP 会根据网络通信过程中测量得到的**往返时间RTT**动态计算合理的超时时间。当发生超时重传后TCP 还会逐渐增加后续的等待时间。例如可以简单理解为第一次超时等待一段时间 ↓ 第二次超时等待更长时间 ↓ 第三次超时继续延长等待时间 ↓ ...这种随着超时重传不断增加等待时间的方式可以避免在网络出现异常或拥塞时频繁进行重传从而减少对网络资源的进一步占用但超时时间并不会一直延长当重传累计到依次次数时TCP 认为网络或者对端主机出现异常不再进行重传而强制关闭连接。具体的超时时间计算以及重传次数限制由 TCP 实现决定这里不再深入展开。三、滑动窗口机制在前面的确认应答机制中我们知道接收方会通过 ACK 对发送方发送的数据进行确认在超时重传机制中我们又知道如果发送方在一定时间内没有收到 ACK就需要重新发送数据。但是如果 TCP 每发送一个 TCP 报文都必须等待接收方的 ACK 到达之后才能继续发送下一个 TCP 报文那么通信效率会非常低。例如发送方会消耗大量的时间在等待 ACK 上。尤其在网络延迟较高的情况下即使发送方和接收方本身都具有很强的处理能力也无法充分利用网络带宽那么 TCP 能不能在等待 ACK 的过程中继续发送后面的数据呢答案是可以TCP 不需要每发送一个 TCP 报文就需要等待对应的 ACK而是允许发送方在没有收到 ACK 的情况下连续发送多个 TCP 报文。即发送方可以同时发送一批数据并通过后续收到的 ACK 确认哪些数据已经被接收。TCP 用于管理这部分“已经发送但没有收到确认的数据”的机制被称为滑动窗口机制。3.1 深度理解 send/write 和发送缓冲区对于每个 TCP Socket 都有一个发送缓冲区用来将应用层要发送的数据发送到对端主机send/write 的本质是将应用层数据拷贝到内核级的发送缓冲区中对于发送缓冲区中的数据什么时候发送到网络一次性发送多少数据数据丢失怎么办全由操作系统基于 TCP 协议决定。而send/write 的作用只是将应用层数据拷贝到内核级发送缓冲区而不是将数据直接发送到网络。为了更容易理解滑动窗口机制我们目前将发送缓冲区想象为一个 char buffer[N] 所以对于发送缓冲区我们可以明确将数组划分为三个部分3.2 理解滑动窗口理解滑动窗口之前我们首先需要理解什么是窗口。对于学过滑动窗口算法的朋友TCP 中的滑动窗口机制的滑动窗口思想与算法中滑动窗口的思想一致。达到某种条件进行出窗口或者入窗口。对于窗口的理解我们可以把它理解为一端连续的子数组例如发送方需要向接收方发送一段数据1 2 3 4 5 6 7 8 9 10假设当前发送窗口大小为4那么发送方一次最多可以发送1 2 3 4即发送的数据量 发送窗口的大小窗口的本质就是要发送总数据中一段连续的数据。在 TCP 协议中窗口就可以理解为发送缓冲区的中间部分可以直接发送暂时不需要确认的数据。那么它为什么被称作滑动窗口呢因为随着发送方不断收到 ACK已发送未确认的数据会不断变为已发送已确认的数据那么对应的窗口左边界就会不断向右移动且右边界也会向右移动。所以对于这种不断滑动的窗口被称为滑动窗口。在 TCP 协议中滑动窗口的本质就是发送缓冲区的一部分这部分数据的特点是可以直接发送暂时不需要确认的数据。对于滑动窗口的进一步理解允许 TCP 在等待 ACK 的过程中继续发送一定范围内的数据进而减少等待时间提高网络通信效率。3.3 理解结合滑动窗口发送报文的流程根据 TCP 中滑动窗口的定义很容易理解知道滑动窗口在上述通信过程中最大值为 4000 字节对于滑动窗口内的数据可以直接发送暂时不需要确认所以呈现出发送方发送 1 ~ 1000、1001 ~ 2000、2001 ~ 30003001 ~ 4000 的数据时不需要任何 ACK而直接发送对于收到第一个 ACK 后滑动窗口向右滑动继续发送第 4001 ~ 5000 的数据依次类推。对于上述发送过程如果丢包了应该怎么解决滑动窗口会不会跳过未接收到的报文进行滑动对于丢失情况我们首先可以分为三类最左侧丢失 中间丢失 最右侧丢失1最左侧丢失在超时重传机制中我们讲到发送方没有收到应答有两种情况数据报文丢失和 ACK 丢失1. 数据报文丢失当滑动窗口的最左侧数据报文丢失时主机 A 滑动窗口内发送的其他数据报文如果被主机 B 收到那么主机 B 会给主机 A 发送 ACK 1 号报文告诉主机 A 1 号之前的报文我已经收到你需要从 1 号开始发送主机 A 通过超时重传机制把 1 号继续发送给主机 B 从而保证报文的可靠性。此时主机 A 的滑动窗口的左边界不会发送改变它需要收到最左侧报文的确认应答才能向右滑动。而对于主机 B 它根据确认序号来判断其他报文是已经收到的报文还是未收到的报文如果是已经收到的报文主机 B 会进行丢弃如果是未收到的报文主机 B 会将其他报文缓存起来没有先交付给上层待最左侧报文到来时按序交付给上层从而完成报文有序到达的可靠性。2. ACK 丢失当最左侧应答报文丢失时对于主机 B 来讲主机 B 会对接收到的所有报文做应答只要主机 B 对报文做了应答无论主机 A 是否收到应答这都不重要因为主机 B 已经收到了数据主机 B 只希望收到新的数据。即主机 B 对主机 A 应答时它的确认序号会一直增大代表主机 B 已经收到了哪些数据。对于主机 A 来讲它未收到最左侧的应答报文但收到了后续报文的应答报文主机 A 通过 ACK 报文的确认序号也可以知道主机 A 应该从哪个数据开始发送。即当最左侧应答报文丢失其他报文的应答报文收到主机 A 明确知道最左侧报文被主机 B 收到即会更新将要发送的序号更新为最大的确认序号。所以对于滑动窗口来讲滑动窗口的左边界会向右滑动来不断发送新的数据。对于2中间报文丢失3最右侧报文丢失这两种情况我们都可以将其转化为1最左侧报文丢失因为当中间报文丢失时滑动窗口的左边界会移动到丢失报文的位置进而转化为最左侧报文丢失对于最右侧报文丢失亦是如此。3.4 滑动窗口机制的进一步理解——发送缓冲区的空间复用我们之前把发送缓冲区想象成一个 char buffer[N] ,那么随着发送报文不断增多滑动窗口可以一直向右滑动吗当时我们把发送缓冲区分成了三部分其实对于已经发送并且得到确认的数据发送方已经不再需要这些数据因此对应的缓冲区空间就可以被释放并重新利用用来存放后续需要发送的新数据。所以我们可以进一步借助环形队列来理解发送缓冲区的空间复用。可以将发送缓冲区抽象成一个环形空间而滑动窗口只是其中当前允许发送的一部分区域。3.5 滑动窗口机制的进一步优化——快速重传机制在 3.3 小节中我们讲到发送方可以根据滑动窗口连续发送多个 TCP 报文不再需要每发送一个报文都等待 ACK从而大幅提高了网络通信效率。但是滑动窗口又带来了一个新的问题如果发送方发送的一个报文丢失而其他报文到达接收方主机接收方发送的 ACK 报文的确认序号只能是最小的那个丢失报文所以发送方滑动窗口的左边界就不能向后滑动那么滑动窗口要想滑动就需要通过超时重传机制重新发送丢失的报文收到应答后才能向后发送数据如果只采用此方式可靠性没有问题但是效率有所损失。所以 TCP 又引入了一个新的机制快速重传机制在上图中发送方发送的 1001 ~ 2000 数据丢失而其他数据到达接收方接收到丢失数据序号后面序号的数据时会不断向发送方发送ACK 1001这些 ACK 被称为重复确认。对于发送方来说如果连续收到多个相同的 ACK就说明接收方一直在等待某一个数据而后面的数据已经到达。因此发送方可以推断这个数据很可能已经在网络传输过程中丢失。那么发送方就不需要等待超时再重发报文而是根据重复 ACK 的数据直接发送报文。即TCP 可以在收到一定数量的重复 ACK 后直接重传可能丢失的数据以致于进一步提升网络通信效率被称为快速重传机制。注快速重传机制属于对超时重传机制的进一步优化是“锦上添花”而不是“雪中送炭”。快速重传可以在检测到数据丢失后提前进行重传减少等待超时的时间但它并不能覆盖所有丢包场景。例如当数据报文丢失后后续没有新的数据到达接收方无法产生足够的重复 ACK此时仍然需要依靠超时重传机制。因此超时重传机制是 TCP 实现可靠传输不可或缺的基础机制而快速重传是在此基础上的性能优化。3.6 滑动窗口的大小如何确定前面我们已经知道滑动窗口允许发送方在没有收到 ACK 的情况下连续发送一定范围的数据。那么问题来了滑动窗口的大小应该为多大让我们思考一下窗口太小会有什么后果窗口太大会有什么后果窗口如果太小发送方能够连续发送的数据就比较少发送方仍然需要频繁等待 ACK网络带宽无法得到充分利用影响效率。窗口如果太大发送方能够连续发送的数据量就比较多可能导致接收方来不及处理数据使数据大量堆积在接收方的接收缓冲区中甚至当缓冲区满时出现接收方丢弃发送方重传的现象造成效率降低和网络资源的浪费。对于窗口如果太大的情况我们在上一篇文章中知道TCP 报头中存在一个字段16 位窗口大小它表示接收方当前允许发送方发送的数据量即 TCP 接收缓冲区中剩余空间的大小。我们目前就可以得出一个结论TCP滑动窗口的大小取决于对方的接收能力即滑动窗口的大小 16 位窗口大小因此TCP 滑动窗口的大小是需要根据通信双方的实际情况动态变化的。但 TCP 协议不仅仅考虑了双方通信的问题还考虑了网络环境的因素即接收方能够接收多少数据rwnd接收窗口和网络当前能够承受多少数据cwnd拥塞窗口发送方实际能够发送的数据量即滑动窗口的大小不能超过这两个窗口所允许的范围因此滑动窗口的大小 min(rwnd, cwnd)rwndReceive Window接收窗口接收方将自己的接收缓冲区剩余空间的大小告诉发送方用于进行流量控制。cwndCongestion Window拥塞窗口由发送方根据网络拥塞情况进行调整用于进行拥塞控制。四、流量控制机制对于流量控制机制的理解在上一篇文章中深入理解TCP协议一TCP报文格式详解 讲述 TCP 报头字段中的16 位窗口大小时已经详细说明了流量控制是什么、为什么需要流量控制以及 TCP 如何通过窗口大小实现流量控制。因此本章节不再重复介绍这些基础概念而是进一步理解 TCP 流量控制是如何工作的。前面我们知道 TCP 报头中的 16 位窗口大小实际上表示的是接收方接收缓冲区剩余空间的大小接收方会将自己的接收能力通过 TCP 报文中的窗口大小字段告知发送方发送方根据这个窗口大小控制自己的发送量。对于双方的接收能力的交互在三次握手的过程中就已经明确了而后续双发的接收能力会不断通过 ACK 报文告知对方进而动态调整窗口大小进行流量控制。但很多人就会想到一个问题如果接收方接收缓冲区满了就会将 16 位窗口大小设置为 0这时发送方收到 ACK 后会将滑动窗口大小设置为 0不再发送数据。那么当接收方将接收缓冲区中的数据交付给应用层处理后接收缓冲区重新出现了空闲空间发送方又是如何知道接收方已经有能力继续接收数据的呢4.1 零窗口探测当发送方发现接收方通告的窗口大小为0时并不会永远处于等待状态而是会启动一个持续计时器经过一段时间后发送方会主动发送一个的 TCP 报文不携带数据询问接收方当前的窗口大小。接收方收到这个报文后可以通过 ACK 报文告知当前的窗口大小。注零窗口检测并不是为了传输正常的数据而是避免发送方因为接收方通知零窗口而永久陷入等待状态。4.2 16 位窗口大小的限制与窗口扩大因子那么问题来了TCP 报头中的窗口大小字段只有 16 位最大只能表示 65535那么 TCP 的窗口最大是不是只有 65535 字节呢实际上并不是。在 TCP 报头的选项字段中存在一个窗口扩大因子Window Scale用于扩大 16 位窗口大小字段所能表示的范围。实际窗口大小可以简单理解为实际窗口大小 窗口字段值 × 2^M其中 M 就是窗口扩大因子。例如窗口字段 10000M 2实际窗口大小 10000 × 2² 40000 字节通过窗口扩大因子TCP 就可以突破 16 位窗口字段 65535 字节从而支持更大的接收窗口。窗口扩大因子会在TCP 建立连接的过程中进行协商连接建立后双方按照协商的扩大因子解释窗口大小。所以TCP 报头中的 16 位窗口大小并不代表 TCP 实际窗口的最大值只有 65535 字节窗口扩大因子可以进一步扩大实际窗口。五、拥塞控制机制在前面的流量控制机制中TCP 解决了一个问题如果发送方发送数据的速度太快而接收方处理数据的速度较慢该怎么办但是流量控制只能解决接收方的处理能力问题。假设现在接收方拥有足够大的接收缓冲区应用程序处理数据的速度也足够快那么是不是发送方就可以不断提高发送速度呢答案显然是否定的。因为发送方和接收方之间还存在着一个非常重要的因素网络本身的承载能力我们首先要建立一个正确的认知对于当今的网络在同一个网络环境中少量发送方一般不会轻易造成严重的网络拥塞。但是当大量主机同时进行网络通信时共享的网络带宽和网络设备处理能力就可能成为瓶颈。现实生活中也存在类似的情况。例如当我们外出旅游时在旅游景点的网速往往会明显下降。其本质就是在同一网络环境中同时进行网络通信的用户数量增加网络资源被大量占用从而导致网络拥塞。对于网络拥塞这种情况发送方发送的 TCP 报文可能会发生大量丢失并且无法在超时时间内收到应答。如果发送方对此置之不理仍然按照原来的速度不断发送甚至在发生丢包后继续进行大量重传就会进一步增加网络压力使网络中的数据包不断堆积从而进一步降低网络通信效率。因此TCP 必须考虑网络拥塞的问题如何根据网络当前的状况动态调整发送方的发送速度这就是 TCP 中的拥塞控制机制。5.1 拥塞窗口那么问题来了TCP 是如何根据网络当前的状况动态调整发送方的发送速度的呢TCP 中存在一个非常重要的概念拥塞窗口Congestion Windowcwnd。前面在讲流量控制时我们知道TCP 通过接收方通告的窗口大小来限制发送方的发送量。这个窗口主要考虑的是接收方还能接收多少数据。而拥塞窗口考虑的是当前网络能够承受多少数据。因此发送方实际能够发送的数据量同时受到这两个因素的限制接收窗口 rwnd ↓ 接收方能接收多少数据 拥塞窗口 cwnd ↓ 网络能承受多少数据 实际发送滑动窗口可以简单理解为 实际发送滑动窗口 min(rwnd, cwnd)例如rwnd 10000cwnd 4000虽然接收方还能够接收10000字节的数据但是 TCP 判断当前网络只能承受4000字节因此发送方最多只能发送4000字节。反过来如果rwnd 4000cwnd 10000那么发送方同样只能发送4000字节因为此时限制发送速度的是接收方。所以可以简单理解为流量控制关注的是接收方拥塞控制关注的是网络。5.2 TCP 如何判断网络是否拥塞那么问题又来了发送方又无法直接看到整个网络的状态它是如何知道网络是否发生拥塞的呢实际上TCP 并不能直接知道网络内部发生了什么而是通过数据传输过程中表现出来的现象来判断网络状况。最典型的情况就是数据包发生丢失。此时TCP 会认为当前网络可能已经无法承受之前的发送速度。因此需要降低拥塞窗口 cwnd降低发送速度。反过来如果发送方持续发送数据并且数据都能够正常得到确认那么 TCP 就可以认为当前网络状况较好逐渐增大 cwnd尝试提高发送速度。于是TCP 就形成了一个不断试探网络承载能力的过程网络状况良好 ↓ 增大 cwnd ↓ 提高发送速度 ↓ 继续观察网络 ↓ 发生大量丢包 ↓ 减小 cwnd ↓ 降低发送速度 ↓ 重新尝试增加 cwnd 这就是 TCP 拥塞控制的核心思想。5.3 慢启动既然 TCP 需要不断试探网络的承载能力那么在 TCP 刚开始发送数据的时候应该设置多大的cwnd 呢如果一开始就设置一个非常大的拥塞窗口cwnd 很大然后立即向网络发送大量数据那么很可能一开始就造成网络拥塞。因此TCP 在刚开始进行数据传输时会从一个较小的拥塞窗口开始逐渐增加拥塞窗口。这个过程被称为慢启动Slow Start这里的“慢”并不是说 TCP 的传输速度很慢而是指TCP 不会一开始就以非常大的发送量冲击网络而是逐渐增加发送量探索当前网络能够承受的范围。在慢启动阶段拥塞窗口会快速增长可以简单理解为cwnd1 - 2 - 4 - 8 - 16 - 32也就是说随着数据不断得到确认 cwnd 会快速增加整体上呈现出指数增长的趋势。5.4 拥塞避免但是如果一直按照这种速度增长1 → 2 → 4 → 8 → 16 → 32 → 64 → 128 → ...那么 cwnd 很快就会变得非常大最终可能超过网络的承载能力。因此TCP 不会让慢启动无限进行。当 cwnd 增长到一定程度后TCP 会进入拥塞避免Congestion Avoidance在拥塞避免阶段TCP 会更加谨慎地增加 cwnd不再像慢启动阶段一样快速增长。简单理解就是慢启动1 → 2 → 4 → 8 → 16 → 32 快速增长拥塞避免32 → 33 → 34 → 35 → 36 → ... 缓慢增长所以可以把这两个阶段理解为慢启动负责快速探索网络的承载能力拥塞避免负责在接近网络承载能力后更加谨慎地提高发送速度。5.5 发生网络拥塞后怎么办如果 TCP 在不断增加 cwnd 的过程中发现了丢包那么说明当前的发送速度可能已经超过了网络的承载能力。此时 TCP 就需要降低 cwnd。而 TCP 前面已经学习过两种发现丢包的方式超时重传和快速重传如果发送方长时间没有收到 ACK最终发生超时说明网络可能出现了比较严重的问题此时 TCP 会大幅降低发送速度并重新进入慢启动。而如果发送方通过重复 ACK发现数据丢失那么可以通过快速重传提前进行重传不需要等待超时。这种情况下TCP 对拥塞窗口的调整不会像超时那样激进而是进行相对温和的调整。这里我们不需要深入具体的窗口调整公式只需要理解发生丢包 → TCP 判断网络可能拥塞 → 减小拥塞窗口 → 降低发送速度。之后如果网络逐渐恢复正常TCP 又会重新增加 cwnd继续探测网络的承载能力。因此TCP 的拥塞控制实际上是一个不断循环的过程增加发送速度 ↓ 探测网络能力 ↓ 网络是否正常 ↙ ↘ 正常 拥塞 ↓ ↓ 继续增加 cwnd 减小 cwnd ↓ ↓ 重新探测六、延迟应答机制在前面的确认应答机制中我们讲过接收方每收到一个 TCP 数据报文就会向发送方返回一个 ACK。这种方式虽然能够保证数据可靠传输但是如果每收到一个 TCP 报文就立即发送一个 ACK那么网络中就会产生大量 ACK 报文增加网络通信的额外开销。因此TCP 对确认应答进行了进一步优化延迟应答机制。延迟应答并不是收到数据后立即发送 ACK而是暂时等待一小段时间。在等待期间如果又收到了新的数据那么接收方可以利用一个 ACK 对多个数据进行确认从而减少 ACK 报文的数量。延迟应答与流量控制除此之外延迟应答还可能对流量控制产生一定的帮助。接收方收到 TCP 数据后数据会被放入 TCP 接收缓冲区。如果接收方稍微等待一段时间在这段时间内应用层可能读取了接收缓冲区中的数据那么接收缓冲区就会出现更多空闲空间。此时接收方再发送 ACK就可以在 ACK 中携带更新后的窗口大小告诉发送方“我现在还能接收更多数据。”因此在一定情况下延迟应答不仅可以减少 ACK 报文数量还可能让接收方在发送 ACK 时通告一个更大的接收窗口从而提高数据传输效率不过需要注意延迟应答的核心目的仍然是减少 ACK 的数量而不是专门为了扩大窗口大小。七、捎带应答机制捎带应答的核心思想就是在发送 ACK 的同时如果本机恰好也有数据需要发送那么可以将 ACK 和数据放在同一个 TCP 报文中发送从而减少单独 ACK 报文带来的网络开销。对于捎带应答机制的过程理解在上一篇文章深入理解TCP协议一TCP报文格式详解 中我们已经结合 TCP 报头中的序号和确认序号进行了详细介绍这里不再重复说明。本章节主要介绍 TCP 为了提高网络通信效率而采用的相关机制因此这里将捎带应答机制作为补充保证本章节内容的完整性。八、总结到这里TCP 的数据传输相关机制就介绍完了。从上一篇文章对 TCP 报头的介绍开始我们了解了 TCP 的基本结构以及各个字段的作用在本篇文章中我们进一步从数据传输的角度深入理解 TCP 是如何保证数据可靠传输并尽可能提高网络通信效率的。TCP 并不是依靠某一个单独的机制来实现可靠通信而是通过多个机制相互配合确认应答机制 - 确认数据是否接收 超时重传机制 - 数据丢失后重新发送 滑动窗口 - 允许连续发送多个数据提高传输效率 快速重传 -尽快发现丢失的数据 流量控制 - 防止发送方发送速度超过接收方的处理能力 拥塞控制 - 防止发送速度超过网络的承载能力 延迟应答 捎带应答 - 进一步减少网络通信开销通过这些机制TCP 在可靠性、传输效率、接收方处理能力以及网络承载能力之间进行协调使数据能够更加可靠、高效地在网络中传输。但是到目前为止我们讨论的主要都是TCP 建立连接之后如何进行数据传输。一个新的问题随之而来TCP 的连接究竟是如何建立的TCP 是面向连接的协议在真正进行数据传输之前通信双方需要先建立连接当数据传输完成之后也需要按照一定的规则关闭连接。那么TCP 为什么需要建立连接为什么建立 TCP 连接需要三次握手三次握手分别做了什么TCP 如何维护连接状态为什么 TCP 关闭连接需要四次挥手为什么关闭连接后还需要经历TIME_WAITTCP 连接建立和关闭的过程中双方的状态又是如何变化的这些问题属于 TCP 的连接管理机制。因此下一篇文章将从 TCP 的连接管理入手详细介绍三次握手、四次挥手、TCP 状态转换以及连接建立与关闭过程中的相关机制。同时也会对 TCP 中一些其他重要但本篇尚未展开的话题进行介绍。至此TCP 数据传输相关机制告一段落下一篇我们继续深入理解 TCP 的连接管理机制。