前言:TCP学习的综述
在学习TCP/IP协议的大头:TCP协议 的过程中,遇到了很多机制和知识点,详解中更是用了足足8章的内容介绍它。
TCP协议作为 应用层 和 网络层 中间的 传输层协议,既要为下面的网络层协议保证连接的可靠性(IP协议)弥补不足,又要作为 应用层进程向网络层发送数据的中转站(作为多路复用/解复用器)。
这就使得我们在审视TCP这个协议的过程中,需要横向和纵向地看待TCP连接:
- 横向的连接:client 的 TCP 与 server 的 TCP。三握四挥,是 reliable 的体现。
- 纵向的连接:网络层 和 应用层 之间联系的纽带,协助 应用层 向网络层 转递数据。
与此同时,在TCP的学习中,我们还需要解答TCP的相关问题:
reliable
TCP是如何保障连接的可靠性的?-超时重传的内容
Control
TCP是如何解决网络拥塞的?-各种机制:慢启动,拥塞避免,经受时延的ACK···
TCP是如何解决流量控制(目的:提高网络的利用率)的?-窗口机制:拥塞窗口,通告窗口···
我想,正是在 reliable 和 control 两部分,TCP下足了功夫。
保证 reliable 的目的是为了解决网络层协议的不足之处:解决数据的丢失重传问题,使得传输数据的连接变得更加可靠;而 control 的目的则是想办法使得传输的效率更高。两者相互影响:
比如我重发数据报的时候会不会造成网络的拥塞?我控制网络拥塞的时候会不会导致传输效率的低下?我要什么时候控制网络拥塞而不会过度控制 导致我的一些重要信息发送缓慢?我要什么时候发送数据报才不会造成更加严重的网络拥塞?
正是为了解决这些十分纠结的问题,TCP诞生了。正是它的伟大和不足,以及为了解决它的不足提出的各种各样的方法,使得它具有迷人的魅力。
本文不会过多的抠细节,是一篇对 TCP各部分的小结,试图阐述清楚它们之间的关系。
建议参考文章:TCP协议疑难杂症全景解析
第一部分:TCP是怎么建立连接的?-三握四挥
众所周知,TCP是通过 三次握手 和 四次挥手 来建立/终止一个 client-server TCP连接的。
三次握手 四次挥手
三次握手 建立起 TCP连接 的 reliable,分配初始序列号和资源,在相互确认之后开始数据的传输。有 主动打开(一般是client) 和 被动打开(一般是server)。
四次挥手,因为TCP连接是全双工的,数据可以在两个方向上进行传递,因此在关闭的时候需要必须单独终止两个方向的数据传输。有 主动关闭(一般是client) 和 被动关闭(一般是server)。
状态转移
既然有三握四挥的机制,那么在它进行的过程中,client端 和 server端 就有不同的状态,在SYN报文,FIN报文或者是ack的发送/接收,都会导致状态的转移。也就有了TCP状态转移图:
研究清楚这幅图是学习 TCP连接的建立与终止 的关键。
需要特别注意的是,在结束一个TCP连接的时候,client端有三种状态(FIN_WAIT1,FIN_WAIT2,TIME_WAIT),server端有两种状态(CLOSE_WAIT,LAST_ACK).
client端 的 TIME_WAIT状态(2MSL状态)
当发送端 接收到 接收端的 FIN,并发送最后一个ack之后,发送端从 FIN_WAIT2状态 进入 TIME_WAIT状态。
TCP数据报有一个 报文段最大生存时间MSL,它是任何报文段被丢弃前在网络内的最长时间。
设置2MSL状态的目的是为了防止以下状态:发送端发送的最后一个ack丢失。设置时间最长为 2MSL 的等待状态,保证接收端的定时器超时重传FIN,使得发送端重新发送最后一个ack。
如果说不这么做呢?
假如没有2MSL状态,服务器没有收到最后一个ack,向发送端重发一个FIN,此时发送端已经关闭,这个FIN有可能被丢失,有可能迟到。倘若发送端和接收端 重新使用同一个套接字/插口对(socket):目的端IP地址,源IP地址,目的端端口号,源端口号组成的组合 所确定的连接,那么如果说FIN迟到了,并到达发送端,有可能异常终止这条连接。
所以我们要求,在2MSL等待状态的时候,确定这条连接的插口对(socket)不能被使用,也就是说,不能用于建立新的连接。
复位
三种情况发送复位报文段:
- 对方端口不存在
- 异常关闭
- 检查半打开连接
需要注意的是 第三种情况:一方异常关闭但是另外一方还不知道,这在之后的保活定时器(keep-alive)有提到。
第二部分:TCP是如何保证可靠性的?-TCP的超时机制与重传
TCP是如何保证数据传输的可靠性的?答曰重传定时器。
重传定时器,保证了发送方在定时器超时溢出且还没有收到对数据的确认的时候,重新发送数据报,并启动一些机制(数据报丢失很可能是因为网络拥塞,为了减缓网络拥塞,TCP提供了许多解决网络拥塞的方法,这里提到的是慢启动/拥塞避免)。
关于定时器时间的计算,采用了估计 往返时间RTT 和 重传时间RTO 的策略。
(1)如果超时没有接收到数据报,RTO 采用 指数退避 的方法更新。
(2)如果接收到了数据报,定时器首先跟踪往返时间RTT,然后根据公式来计算 RTO。公式利用了 均值偏差 和 RTT估计器 来减小计算RTO时因为网络时延等原因带来的误差。
重传定时器的设计有两个原则:一是 发送完如何一个报文,并长期收不到它的确认的时候,必须超时;二是 不能过早的超时:即超时的时间设置 不能和 测量的RTT 差太远。
对于定时器,RFC有以下四则规则:
(1)发送TCP分段时,如果还没有重传定时器开启,那么开启它。
(2)发送TCP分段时,如果已经有重传定时器开启,不再开启它。
(3)收到一个非冗余ACK时,如果有数据在传输中,重新开启重传定时器。
(4)收到一个非冗余ACK时,如果没有数据在传输中,则关闭重传定时器。
其中规则3是用于避免过早的重传。
也就是说,当发送端一次性发送多个数据报的时候,比如一次性发送 A B C D 四个数据报(暂且不考虑拥塞窗口和通告窗口),当 发送A 的时候,启动重传定时器,在收到 A的确认 的时候,重置 重传定时器。这样保证不会出现这样的情况:一次性发送完以上的四个数据报之后,发送端等待的是 对D数据报的确认,但是在定时器超时之前,除了比较早发送的 A B 发送方接收到了对它们的确认之外,并没有收到 C 和 D 的确认,这就导致了 不必要的重传。
大多数情况下,一个数据报测得的 往返时间RTT 约等于 重传时间。
第三部分:TCP是如何保证传输数据的 效率 ?-流量控制 避免网络拥塞
本节将看到,TCP对网络的流量控制,和避免网络拥塞的众多机制:
经受时延的确认(接收方) Nagle算法(发送方) 通告窗口(接收方) 拥塞窗口(发送方) 滑动窗口机制(接收方 & 发送方) 慢启动(发送方) 拥塞避免算法(发送方) 快速重传和快速恢复(发送方) 糊涂窗口综合症的解决方法(接收方 & 发送方)
一个死循环:网络拥塞 -> 数据报丢失 -> 重传 -> 网络拥塞 ···
TCP是怎么样解决这个死循环,并提高网络的效率的呢?
角度一:接收方 与 发送方
角度一:接收方TCP独有的 流量控制 和 避免网络拥塞 的机制与措施
- (1)避免过多的ack造成 低速网络(如广域网) 的网络拥塞:经受时延的确认。
联系:Nagle算法,避免糊涂窗口综合症的措施(防止小包)。
- (2)避免接收方处理过慢,导致接收队列溢出数据丢失:通告窗口。
联系:拥塞窗口,滑动窗口机制。
概要:
经受时延的确认/数据捎带ACK:通常TCP在收到数据的时候,并不马上发送对该数据的确认,相反,它推迟发送,以便将ACK与需要沿该方向发送的数据一起发送。绝大多数的时延为200ms。
在慢速网络 比如广域网上,过多的小包会造成一定的网络拥塞,从而导致数据报的丢失,效率低下。而如果 接收方TCP 并不采用此机制,即一接收到数据报就发送对它的确认ACK,无疑会造成小包数量的剧增,再加上服务器本来就要向发送方发送的数据,造成网络拥塞也就并不奇怪了。
通告窗口:接收方以一个比较慢的速率来处理接收的数据,而发送方以一个比较快的速率来发送数据,如果没有一个合理的机制来控制的话,势必会造成接收方接收队列的溢出,网络的拥塞以及数据的丢失。接收方提供的是 通告窗口,与 经受时延的ACK 或者是 数据 一起发送往发送方。
角度一:发送方TCP独有的 流量控制 和 避免网络拥塞 的机制与措施
- (1)慢启动 ssthresh 拥塞避免
联系:滑动窗口机制,通告窗口。
- (2)拥塞窗口 -慢启动提供:概要中与(1)一起
联系:滑动窗口机制,通告窗口。
- (3)快速重传,快速恢复算法
联系:慢启动,拥塞避免等。
- (4)Nagle算法 防止小包
联系:经受时延的ACK,避免糊涂窗口综合症的措施。
概要:注意慢启动和拥塞避免维持的两个变量 ssthresh 与 cwnd
慢启动:如果发送方一开始就向接收方发送多个报文段,直到达到接收方的通告窗口为止,很容易造成中间路由器的缓存溢出,耗尽存储启动空间,从而造成网络拥塞。
慢启动的工作方法是观察到 新分组进入网络的速率 = 另外一端确认分组的速率 来进行工作的。
慢启动为发送方增添了:拥塞窗口,初始值为1,每接收到一个ack就增加1。发送数据报的数量取决于 min{拥塞窗口,通告窗口}。
但是,慢启动一点也不慢,它的拥塞窗口的增长方式是 指数型的。这样到了后期 拥塞窗口 必然会超过 通告窗口,也就达不到控制的效果了。
于是乎,我们引入了 拥塞避免算法。
拥塞避免算法:当拥塞窗口增大到一定程度的时候,我们采用拥塞避免算法,而不是慢启动。慢启动和拥塞避免算法之间的界限 我们称之为 ssthresh(16个报文段,也就是65535字节,拥塞窗口的大小)。
拥塞窗口算法的实现:每收到一个ack,拥塞窗口增加1/cwnd,这是一种加性增长,放缓了慢启动的指数增长。
慢启动和拥塞避免的工作过程:教材P235.
慢启动与拥塞避免的可视化描述:
快速重传,快速恢复算法:我们并不知道 一个重复的ACK是由一个丢失报文段引起的,还是由于仅仅出现了几个报文段的重新排序,因此我们必须等待少量的重复ACK的到来。如果一连串收到三个重复的ACK,那么基本可以确定是数据报丢失引起的,那么此时网络很有可能已经拥塞,需要采取 慢启动或者是拥塞避免的措施 来控制网络拥塞。
在连续收到三个重复的ACK之后,我们不用等到定时器溢出,直接进行重传,这就是快速重传算法。接下来执行的 并不是 慢启动 而是 拥塞避免,这就是快速恢复算法。
我们不想进入慢启动的原因是因为,在收发两端仍然有流动的数据,我们并不想使用慢启动,从而造成数据流的突然减少。
快速重传 快速恢复算法的工作过程:教材P237.
Nagle算法:发送方用于防止小包的措施,具体原因与 接收方TCP的 经受时延的ACK 一样:避免慢速网络的网络拥塞。
该算法要求一个TCP连接上 最多只能有一个 未被确认的小分组,这导致了发送方数据的积累:在确认到达之前不允许发送小分组,相反TCP积累这些小分组,并在确认到达的时候“一股脑儿”以一个数据报的形式发送出去。
优越之处在于:确认到达的越快,数据发送的也就越快。它是自适应的。
发送方与接收方 共有的 防止网络拥塞 和 控制流量 的措施
- 滑动窗口机制:发送方TCP 的 拥塞窗口 与 接收方TCP 的 通告窗口 的共同作用。作用于发送端。
- 糊涂窗口综合症 与 发送方TCP 的 Nagle算法 和 接收方TCP 的 经受时延的ACK 有直接的关系。
概要:
首先谈谈 滑动窗口机制,它的可视化表示如下图:
接收方 鉴于自身接收数据的快慢,向发送方提供 通告窗口,以实现对发送端发送流量的控制,这个通告窗口就是上图中的 提供的窗口。
发送方 根据接收到数据报的情况,基于避免网络拥塞的慢启动算法,提供了拥塞窗口,即上图中 发送但未被确认。
当发送方发送的数据 被成功确认的时候,滑动窗口的左沿向右移动;同时根据接收方新发送的通告窗口,右沿也向右移动。移动的距离是由 返回的确认号 与 发送端缓存的 目前第一个尚未被确认的数据报的初始序号 的差值所决定。
滑动窗口机制,即体现了 接收方根据自身情况对发送方的流量控制(通过通告窗口),避免了发送方一次性发送过多的数据导致读取数据缓慢的接收方缓存溢出;又体现了 发送方TCP对网络拥塞的监视,以及避免拥塞的策略(通过慢启动算法 提供的 拥塞窗口)。
但是,接收方对发送方的流量控制 同时也会带来一些不好的东西,就是我们接下来要引述的 糊涂窗口综合症 了。
由于接收方处理数据十分缓慢,往往在一个数据报往返时间RTT内处理不了多少数据,这就导致了接收端经常会通告一些小窗口,这是我们不希望看到的:在前面有提到,过多的小包会加剧慢速网络的网络拥塞程度。这就是 糊涂窗口综合症。
由于接收方可以通告一个小的窗口,发送方也可以发送小的数据,因此 避免糊涂窗口综合症的对策也是从 对发送方 和 对接收方 两个方面着手的:
(1)对发送方(在满足以下条件之一的情况下发送数据)
- 可以发送一个满长度的数据报,即一个MSS长度的数据报。
- 可以发送至少是通告窗口一半的数据
- 可以发送任何数据但是不希望接收ACK(前面还有未确认的报文) 或者该连接上不能使用Nagle算法。
(2)对接收方
- 接收方不通告小的窗口(特例:教材P249,防止滑动窗口右沿左移),并且一般不通告比目前的接收状况 即窗口 更大的大小。除非 接收方应用进程接收了MSS大小的数据,或者处理了接收方一半缓存大小的数据。
糊涂窗口综合症的解决方法,与发送方TCP的Nagle算法,和接收方的经受时延的ack,是TCP用于避免慢速网络(WAN)网络拥塞的策略。
角度小结:How to control?
通过角度一对于发送方和接收方各自以及共有的流量控制及避免网络拥塞的策略进行了一个归纳。
有我们开头的四个问题:
- TCP是怎么样知道发生了数据报丢失?
- TCP怎么样处理网络拥塞?
- TCP怎么样控制重传的时间 从而不会引起过度的不必要的重传?
- TCP怎么样均衡 控制网络拥塞 以及 传输效率?
以及这个死循环:网络拥塞 -> 数据报丢失 -> 重传
这个死循环很大程度的导致了传输数据效率的低下,于是乎,TCP必然要采取一定的措施来制止这个死循环的发生。
那么我们把这四个问题 和 这个死循环结合起来,根据上文的内容来解答他们。
(1)网络拥塞,既然TCP知道可能会出现网络拥塞,那么接收方和发送方的TCP肯定要想办法避免它和解决它。
避免它:慢启动算法 和 拥塞避免算法,慢启动所提供的拥塞窗口;针对慢速网络:Nagle算法,经受时延的ack,避免糊涂窗口综合症的措施。
解决它:当拥塞发生的时候,我们希望降低分组进入网络的传输速率,拥塞避免算法起到了很好的处理丢失分组的方法,在教材的P235详细说明了拥塞避免算法在拥塞发生的时候所采取的措施(启用慢启动,设置ssthresh等)。
(2)数据报丢失,网络拥塞造成了数据报丢失,发送方怎么样知道数据报丢失?
处理数据报丢失的方法是 (3)重传,那么发送方什么时候重传不会造成重传风暴 导致网络拥塞?
TCP 在 发送数据的效率 和 减缓网络拥塞 是怎么样均衡的?
发送方是如何知道数据报丢失的?
-重传定时器超时,或者是发送方接收到了三个重复的ack;前者重发数据报并利用指数退避更新RTO,后者立即重发数据报,进入快速重传和快速恢复。
准确的来说,重传定时器超时所估计的网络拥塞比接受到的三个重复的ack所估计的网络拥塞更加严重:收到三个重复的ack有可能是因为数据报的错序导致的,说明后面的数据报接收方有收到,就进入快速重传快速恢复状态;而重传定时器超时估计起来就严重的多了,网络拥塞程度已经严重到丢失大量数据的阶段了,因此必须使发送方重新进入调整网络拥塞的状态:慢启动或者拥塞避免。
什么时候重传 不会造成进一步的拥塞?
-控制重传时间的 是重传定时器,TCP利用对往返时间RTT的测量跟踪路由器和网络流量的变化,利用往返时间RTT的估计器以及均值偏差来确定重传的时间,最大程度的降低了估计的误差,并且利用RFC定义的重传定时器的四个原则保证了它不会过早的重传,从而造成不必要的重传。
TCP是怎么样均衡控制网络拥塞 和 传输效率的?
-利用滑动窗口机制,以及慢启动对拥塞窗口的“指数型增长” 和 拥塞避免对拥塞窗口的“加性增长”,尽可能的利用带宽,同时也减小网络拥塞发生的可能。
滑动窗口机制的拥塞窗口是发送方根据其对网络拥塞的估计(利用RTT) 避免中间路由缓存的溢出 所采取的流量控制措施;而通告窗口则是 接收方 根据自身进程接收数据的快慢,以及在该连接上可用缓存的大小,采取的流量控制措施。
与此同时,避免网络拥塞的 慢启动 和 拥塞避免 也提供了利用网络带宽 提高网络效率的方法:每接收到一个ack,就增加拥塞窗口的大小。
这样,均衡地控制了网络拥塞 也保证了一定的传输效率。
慢启动和拥塞避免 是避免网络拥塞,解决网络拥塞的方法,它们作用于拥塞窗口,使在不同的网络条件下(拥塞或者不拥塞)尽可能的利用带宽。
这就是TCP中的control,内容既复杂又相互联系,但是需要抓住本质的两点来分析:1)控制流量,避免网络拥塞 2)控制传输数据的效率