[网络]——传输控制协议TCP你必须知道的事

TCP协议

关于TCP协议的介绍的相关书籍已经太多太多,而且在搜索引擎中输入TCP关键字,也会有数以千计的结构展现你面前。但是笔者觉得没有经历过自己整理的东西永远都是别人的,虽然我们整理时可能只是把别人非常完善东西搬过来罢了,但是你要记住一句话:我们要站在巨人的肩膀上眺望远方。写这篇博客目的是让自己加深对TCP协议的理解,也便于自己日后再复习。

TCP主要特点

TCP协议全称传输控制协议,是传输层中设计非常复杂的一个协议,这里介绍以下TCP的主要特点,然后我们后面的详细介绍就以这些特点展开:

在这里插入图片描述

详解TCP报头

ps:图片来源于谢希仁老师的《计算机网络第五版》
在这里插入图片描述

  • 源端口和目的端口:各占两个字节,分别写入源端口和目的端口,目的是为了精确的交付给某个进程
  • 序号:占4个字节。序号的范围是0~2^32,也就意味着有4294967296个序号,当序号比这个数字大之后就又要回到零开始编号。因为TCP传输的每一个字节都需要按顺序编号,所以此字段值是指本报文段所发送数据的第一个字节的序号。
  • 确认号:因为TCP协议中设有确认应答的机制,所以当我们对发送端的数据进行确定时就要填充此字段,此字段的值是期望下一次发送端发送数据第一个数据字节的序号。发送端如果收到了接收端的应答,也就意味着接收端已经收到了确认序列-1之前的数据全部都收到了。
  • 数据偏移:有的书中也会把此字段称为头部长度,实际上他们是没有区别的,我们从图中发现TCP报头固定字段的长度为20字节,而此字段只占4位,也就是最大能表示的数字是15 < 20。事实上此字段的单位是4字节,从这儿看出TCP报头最长为60字节。
  • 保留:当前没有使用,但是不代表以后不使用
  • URG:当URG=1时,表示紧急指针字段有效,与后面的紧急指针是配合在一起使用的。
  • ACK:仅当此字段为1时确认号字段才有效,TCP规定,在建立连接后的所有传送的报文段此字段都必须置为1
  • PSH:我们知道TCP是有接收缓冲区的,这也就意味着数据将被先送往缓冲区,如果报文中设置了PSH字段,那么就表示接收方需要尽快的取走缓冲区中的数据,并不用等到缓冲区满时才取走。当俩个应用进程在通信时,一端进程在键入命令后希望对方理解收到命令,此时就会设置PSH字段
  • RST:RST复位标识,当RST为一时,表明TCP连接中出现了严重的差错,可能由于主机奔溃或者其他原因,必须释放连接,然后再重新建立连接。RST置为1时还用来拒绝一个非法报文段或者拒绝打开一个连接。
  • SYN:同步SYN全称SYNchronization,当SYN等于1而ACK等于0时,表示这是一个连接请求报文段。对方如果已经同意建立连接,那么将返回SYN=1 ACK=1的报文
  • FIN:这个字段全称为FINis,意思是完或者终的意思,用来释放一个连接。当一端向另一端发送FIN=1的报文时表示发送方的数据已经发送完毕,要求释放运输连接。
  • 窗口大小:这个字段将被填充本主机接收缓冲区剩余空间的大小,是为了流量控制而设置的字段,此字段我们在后面详细讲解。
  • 检验和:占两个字节,检验和检验TCP数据报报头和数据的所有数据,与UDP的检验方式相同。
  • 紧急指针:占俩个字节,需要与URG字段配合使用,当且仅当URG=1时此字段才有意义,他指出本数据报中紧急数据的字节数,紧急数据之后就是普通数据。同样他指出了紧急数据所在的位置,当紧急数据处理完时,TCP就告诉程序恢复到正常的操作。为什么需要紧急数据:已经发送了一个很长的程序要在远地主机上运行,但是突然发现了一些问题,所以当你发送终止命令时你的终止数据处于缓存末尾,如果没有紧急数据,那么只有处理了之前的程序数据才会处理紧急数据,就浪费了很多的时间。
  • 选项:这个字段中的选项会在与服务器商量好,所以这个字段中的选项并不是固定的,我们这里先介绍比较常用的几种,等到具体的场景时我们在为大家详细介绍。下面的字段都是选项中的字段
  • 最大报文长度(MSS):TCP最初只规定了一种选项,也就是这里我们所讲的最大报文长度,MSS全称Maximum Segment Size,注意这里的名词,他指的是每个TCP报文中数据字段的最大长度,所以MSS是数据报总长度减去报头长度。那么我们为什么需要有MSS呢?这里其实并不是考虑接收缓存中放不下TCP报文中的数据,MSS与接收窗口值没有任何关系,他的出现主要是为了限制ip数据报的大小,因为如果MSS太小,那么对网络的利用率就会大大降低。而如果MSS太长,到达ip层时又需要进行数据分片,当到达对端如果发生了数据报丢失,结果导致整个数据都需要重传。所以MSS从某种程度上解决了效率问题,不过值得说明的是MSS非常的难设置,因为每一条路径的情况不同,所以一般MSS设置为536~1460之间。
  • 选择确认SACK:协商了此字段的客户端和服务器之间如果发生了一批数据报中间的某个数据报丢失,不需要重传丢失后的所有数据报,只需要重传丢失的那个即可,我们后面还会更细致的讲解。
  • 窗口扩大选项:我们上面说过窗口大小是我们接收缓存区的大小,占16位,上限为64k。选项中有一个窗口扩大选项占3个字节,其中一个字节表示窗口移位值S,最多可以将窗口扩大14位,也就是说我们的接收缓存最大能扩大为2^30个字节。
  • 时间戳和时间戳回送:这俩个选项共占10个字节,时间戳是发送方发送时填入字段的时间,时间戳回送是接收端确认该报文时将时间戳复制到回送时间戳,当发送方拿到这俩个选项就可以计算出RTT(往返时间),RTT具体的应用我们也会在后面讲解。

我们这里介绍了TCP的报头,没错,让人感觉到枯燥无味,但是这却是TCP各种复杂机制实现的基础,对于TCP的报头信息读者们一定要做到烂熟于心,最好能自己画出来,因为当你看到某一个选项时,你就会想起TCP的某个机制。

TCP如何实现可靠传输

1.停止等待协议和超时重传协议

我们这里将停止等待协议和超时重传协议放在一起介绍,因为他们本身都是非常简单的机制,并且放在一起我们通过不同的场景能让大家更好的理解,这里我们要清楚的是停止等待协议其实是一种确认应该的机制:就是说,发送端发送一个数据后就停止发送进行等待,直到收到接收端的确认才发送下一个数据,如下图。虽然TCP协议是全双工的,但是这里我们只讨论单个方向上的数据传输,这样能帮助我们加快理解且不那么复杂:

下图是未出现差错的情况下两台主机进行数据传输的场景,纵轴是时间线,可以看出只要在收到上一个数据的应答发送方才会发送下一个数据,这就是我们所说的确认应答机制。
在这里插入图片描述
上图中我们是在一种理想化的情况下讨论的,但是我们都知道由于网络情况的不相同,数据在发送的过程中有可能丢失也有可能出现错误,如果出现检验错误,那么接收端也会直接丢弃这个数据并不给予发送端应答,那么为了实现可靠传输我们就要引入我们的超时重传机制。

如下图,不管是由于数据错误还是丢失,发送端都没有接收到ACK确认,如果发送端过了一段一段时间没有收到应答,那么就会重传数据,这种机制叫做超时重传机制。没发送一个数据报就会设置一个超时计时器,如果在计时器过期之前收到应答,那么就撤销已经设置的计时器。
所以因为超时重传的机制我们应该注意以下几点,发送端发送完一个数据之后并不可以了立即将这个数据从缓冲区中删除,因为可能后面要进行超时重传。每个数据使用序号编号是必要的,因为这样你才能知道你需要重传的数据是哪个。

在这里插入图片描述
这里除了数据丢失,还会出现应答丢失的场景,而且有时候应答并不是丢了,而是阻塞在网络中迟到了,那么TCP又是怎么处理的呢?

  • 情况一:如下图左图,这里应答丢失,所以经过一段时间后发送方进行重传,但是问题是我们收到了重复了数据包,还记得我们的序号选项么,如果出现了相同的序号,那么TCP就会通过序号去重,这样就保证了数据不会重复出现。
  • 情况二:如下同右图,这里实际上只是应答的数据报被阻塞在路上了,但是由于在规定时间内发送没有收到应答,所以进行超时重传,过了一会之后,上一个应该才到发送端,但是此时接收端会收下报文什么也不做直接丢弃。这种情况下去重也是必要的
    在这里插入图片描述

我们使用上面的俩个简单协议就实现了在不可靠网络上的可靠传输,上述的这种可靠传输协议称为自动重传协议ARQ,意思是说重传的请求是自动进行的,接收方不需要主动请求发送端重传某个出错的分组。
我们上述的俩种协议中提到了超时重传计时器,很多同非常好奇,那么网络情况这样的变化莫测,如果将时间设置的太长会导致网络空闲的时间增大,降低传输效率。但是如果设置的时间太短有可能产生很多不必要的重传,使网络负载增大。那么这个超时计时器的时间应该设置为多久呢?一起来看看

我们讲TCP头部时间戳选项时提到了一个RTT的概念,他指的是报段传输的往返时间,而我们计算超时时间是应当收集不同的RTT并求他们的加权平均RTTS,RTTS也被称为平滑的往返时间,每当我们计算新的RTTS就需要使用到下面的公式:
在这里插入图片描述
公式中的a通常取值为1/8,也就是0.125,通过这个公式我们就得到了新的RTTS,但是这还没完。我们还需要计算一个RTTD,他指的是RTT和RTTS差值的加权平均值,第一次RTTD取RTT的一半,之后的RTTD按如下公式计算:
在这里插入图片描述
这里的b取值为1/4,也就是0.25,我们现在得到了RTTD和RTTS后,最后一起来看看超时计时器时间RTO的公式:
在这里插入图片描述
但是这里有一个很大问题:因为由于存在重传机制,你就无法判断同一个报文的应答是否是经过重传的,如果经过重传的报文那么就一定会导致RTTS变大,所以超时时间RTO也就会变大,相反,如果把一个未经过重传的报文当作重传的报文的计算,这样又会导致RTO变小,那么这个问题怎么解决呢?
TCP规定,在计算平均RTTS时,只要报文重传了,就不在把它列为计算样本。可是这又导致新的问题,如果某个时间点报文量突然增大,会引起重传报文,但是由于规定重传的报文RTT不作为样本,所以RTTS就意味着无法更新,进一步导致重传时间无法更新,所以为了解决问题的问题,名叫Karn算法中规定,超时重传时间是旧重传时间的两倍,直到不在发生重传时,才根据以上公式重新计算时间。事实证明,这种方法是行得通的。

现在我们已经介绍了最简单的停止等待协议,但是他有一个很大的问题,那就他的效率问题,每次发出一个数据是经过发送数据TD;数据报往返时间RTT;处理时间TA,用TD除以这三项加起来得到的结果就是信道的利用率。可想而知多么感人,所以这里TCP又引入了滑动窗口协议,这里先不详细谈此协议,后面下一小节我们会仔细刨析他:
在这里插入图片描述
为了解决效率,我们引入了滑动窗口,这是TCP协议精华的所在,也是连续ARQ协议最基本的概念。连续ARQ协议规定,发送端每收到一个应答,窗口就会向后一个分组的位置,而此时我们也采用了一种积累确认的方式,对按需的到达的最后一个分组发送确认,但这里我们先不详细谈滑动窗口协议,先来解决选项字段的一个遗留问题。

还记得我们的选项中的SACK字段么,SACK选项需要使用一个字节指明,还需要另一个字节指明选项占多少字节,这个字段是客户端和服务器建立连接时协商的一个字段。他解决的问题如下:

如果上图中的2,3,4,5正在送达的路上,但是只收到了3,5,说明4丢失了,这时候由于只能向发送端确定3以前的数据收到了,而发送端并不知道4以后的下落,所以导致4以后的数据都要重新发送,这可能会使网络负载变高,这种问题叫做回退。而我们SACK可以解决这个问题,我们只需要标明确实数据的左边界和右边界返回给发送端,让他仅仅传送丢失的数据即可。
但是,but因为一个边界就占用4个字节,两个边界为8字节,而选项部分一共为40字节,这就可以看出SACK并不是一种足够优的做法,并且SACK文档没有指明发送方怎么响应SACK,所以大多数情况下并不会使用SACK这一选项。

2.滑动窗口协议

上面我们已经提到过滑动窗口是一种使用连续ARQ协议的机制,这种机制会维护一个发送窗口,窗口中所有的数据可以连续的发送数据而不需要等待对方的确认,上面第一小结中向右的方向是时间轴,每收到新的确认滑动窗口即可向右滑动。
在这里插入图片描述
窗口大小的确定:

首先我们要纠正一下很多同学错误的观点:滑动窗口的大小不变。这里的理解是非常有问题的,就拿发送窗口的大小来说,一般发送窗口的大小要根据接收方接收缓冲区剩余空间大小来定的,如果接收端的上层并不立即取走数据,那么随着发送方数据的不停发送,接收方缓冲慢慢被沾满,发送窗口的大小也就将会趋向于0。

  • 假定发送方收到了接收方的应答,应答中确认序号为21,而窗口大小(接收方剩余接收缓冲的大小)为20,那么发送方就立即将自己的发送窗口大小设置21~40字节之间,但是这里我们仅仅讨论了接收缓冲区的大小,发送窗口的大小还要受到拥塞窗口大小的限制,我们后面谈到时再说。
  • 这里还要注意一个点,我们这里所谈的滑动窗口和TCP报头中窗口字段的意义时不相同的,前者重点在于当前能处理数据的多少,而后者是标识自己接收缓冲区剩余大小的一个字段,一定要将这俩个概念搞清楚。
  • 那么我们这里的滑动窗口到底在哪里呢,既然滑动窗口是进行首发数据管理的,那么他必然处于发送和接收缓冲区中。其实不难理解,如果让我们设计一个最简单的滑动窗口,那么窗口大小的确认其实就是缓冲区中的俩个下标罢了(仅仅确定大小)。
  • 正如上图中的前沿和后沿共同确定了一个发送窗口,这里的后沿变化只有俩种可能,一种是不动:因为他当前没有接到新的确认,另一种是前移:不难理解他收到新的确认即可前移。但是注意后沿不可能向后移动,我们不可能撤销掉一个已经确认接收的数据。
  • 前沿的变化则有三种可能,第一种是前沿前移:当收到新的确认且对方通知的窗口大小没有变小就会前移;第二种是前沿不动:当未收到新的确认且对方通知的窗口没有发生变化时不懂,另一种情况是收到了新的确认但是接收端通知窗口变小了恰好使前沿不动;第三种情况较为特殊就是前沿向后收缩:因为如果对方通知窗口变小导致我们不得不将前沿后移,在TCP标准中强烈不赞成这样做,因为发送端在收到这个通知前可能已经发送了发送窗口中的很多数据,如果一旦收缩可能就会导致错误。

数据发送中窗口的变化:
在这里插入图片描述
这里非常有必要阐明发送窗口在发送缓存的形态,如上图的发送窗口所示:

  • p1之前:p1之前的数据是已经发送并收到确定的数据,所以图中虽然是这样的画的,但是事实上这部分数据已经可以被删除
  • p1~p2之间:这部分处于发送窗口中,因为还未收到确定,所以还不能将他们删除,防止出现丢包需要重传。要注意的是这部分大小已经不是发送窗口的可用部分了
  • p2~p3之间:允许发送但是尚未发送的数据,这部分也被称为发送窗口的可用窗口部分

现在来观察上图中的发送和接收窗口,接收窗口中序号为32,33的数据已经收到,但是却没有收到31。由于发送方没有收到31号数据的确认,所以又要重新从31开始发送,这就是我们之前在连续ARQ协议提到的回退现象。这样就使我们在出现丢包的情况下发送效率大大降低,所以就可以使用SACK选项只发送丢失的数据包。当接收端收到31,32,33数据之后向发送端发送确认。
在这里插入图片描述
此时发送端的p1指针向前移动到34,但是仔细观察图p2的位置并没有发生变化,接收端的窗口通知大小也没有发生变化,所以p3向前移动到54。注意我们的可用窗口已经变大了,这里我们说的并不是整个发送窗口变大,而只是可用窗口那一部分。

再来观察接收端当前收到了37,38,40号数据,之前的数据并没有按序到达,但是从图中明显我们可以观察到41及之前的数据发送端已经全部发送,如果在一定时间内发送端没有收到确认那么就会触发超时重传机制,从此处我们就会发现TCP所有的机制共同保证了可靠传输这一特点。如果发送端已经将可用窗口的数据全部发送完,那么此时可用窗口就会为0:
在这里插入图片描述
到这里实际上我们就要更正上面说提到一个不严谨的点,如果我们从本质上来看,现在设计一个发送窗口俩个下标就不能满足了。因为我们要加一根指针区分可用窗口和已发送未收到确定的部分。

再谈窗口和缓存:

我们先来看看窗口和缓存的大体逻辑结构是什么样子的:
在这里插入图片描述
这里要说明俩点,第一点:虽然我们的示意图画的是一个长条状,但是因为缓存有大小限制的缘故,所以我们必须把缓存当作循环队列使用。第二点:图中为什么有一个发送程序?这是什么意思,其实我们之前写socket套接字时使用过write,read这些相关的函数,这些函数向socket对象写数据时是直接发给对方吗?不是的,其实这些接口说白了只负责将buf中的数据拷贝到此缓存中就返回了,其余的锅他便甩给TCP,这也就是图中程序存在的原因。

  • 发送缓存和窗口:发送缓存存放的是未发送的数据和已经发送但未收到确认的数据,收到确认的数据就会被缓存自动删除,所以发送窗口的后沿总是和缓存后沿重合的。最后写入字节位置减去最后确认字节位置就是当前缓存中的数据量。
  • 接收缓存和窗口:接收缓存存储按序到达的但尚未被上层取走的数据和未按序到达的数据,如果上层一直不读取数据,那么最后接收缓冲区就会被填满,接收窗口最后变为0。相反,如果上层将数据都取走,那么接收窗口就会变大,但是接收窗口一定不能超过接收缓存的大小。

最后对于滑动窗口我们还需要做出以下几点的总结:

  • TCP的发送窗口和接收窗口的大小在相同的时间下并不是一定相同大小的,因为网络传输数据需要时间,所以同一时间并不能做到完全同步。再者我们后面谈拥塞控制的时候拥塞窗口也是限制发送窗口大小的一个原因。
  • 对于未按序到达的数据TCP并没有进行明确的规定,如果接收方将未按序到达的数据一律丢弃虽然从管理上简单了很多,但是对于网络资源的浪费则变得非常严重。一种做法是TCP将未按序到达的数据暂时保留在缓冲区中,等收到缺失的字节后再一并交给上层。
  • TCP规定接收方必须要有累计确认的功能,这样可以减少传输开销。接收方可以在合适的时候发送,也可以在自己要发送数据的时候捎带应答。但是注意推迟应答的时间不能太久,因为太久有可能就会造成不必要的超时重传,这个时间一般不能超过0.5秒。
  • 上面累计应答是一种延迟应答提高效率的做法,滑动窗口中还有另外提高效率的做法。之前我们讲过的超时重传是在一个时间后没有收到才重传,这样效率实在太低,所以我们在拥塞控制中还要将一种快重传的机制,之所以不在这里将是因为快重传是与拥塞控制密不可分的。

TCP的流量控制

我们上一节中已经越过TCP协议中实现可靠传输的一座大山,而现在我们要来谈一谈TCP的流量控制。有的同学一看到流量控制会认为TCP设置这种机制是解决效率问题的,这么理解有问题,但不完全对。那你不控制流量一次将数据发完不是更快么?所以其实流量控制是防止发送端发送太快而导致接收端来不及接收所增加的一种机制,试想如果没有流量控制,那么接收端来不及接收就会将来不及接收的数据包丢弃。从可靠性角度来说,避免丢包就是一种可靠性的体现。但从效率角度来说也没有错,因为丢包后发送方就要重发,重发就占用了网络资源,而如果流量控制就能避免重发使其他数据更快到达,这也侧面解决了效率问题。

流量控制就是为了让发送方的发送速率不要太快,要让接收方来得及接收。 那么来看看TCP如何实现流量控制:
在这里插入图片描述
从图中我们能很清楚的观察到B主机也就是接收端向发送端A一共发送了3次确认,只有ACK为1时确认才有效。这三次确认每一次都对发送端进行了流量控制,我们一直在谈TCP头部窗口的选项,也就是图中rwnd。这个选项填的是自己当前接收缓冲区的大小,目的就是为了告诉发送方我现在接收缓冲还剩多大,你不能发送比窗口还大的数据。窗口的单位是字节。

可以看出,流量控制非常简单,仅仅使用TCP报头中的窗口选项就能实现流量控制。但是在控制的过程中还会遇到一些其他情况,假设某个情况下接收方的rwnd的大小为0,也就意味着发送方不能再发送数据了。此时进入发送暂停状态,这种状态直到接收方发送一个新的窗口值为止,但是不幸的是,这个响应可能在传输的过程中丢了,那么窗口现在虽然已经不是0了,但是对端直间仍然处于僵持状态。
为了解决这个问题,TCP为每个连接设有一个持续计时器。只要接收方发送了窗口为0的报文,那么发送方就启动这个计时器,当这个计时器过期,发送方就会主动向接收方发送一个探测报文,这个报文仅携带一个字节的数据。接收方收到这个报文就会向发送方发送当前窗口的大小。如果接收端的窗口不为0,那么僵局也就被打破。

TCP协议中使用窗口进行控制,不仅是一种简单的,并且可靠的流量控制方式。

TCP的拥塞控制

拥塞现象是指到达通信子网中某一部分的分组数量过多,使得该部分网络来不及处理,以致引起这部分乃至整个网络性能下降的现象,严重时甚至会导致网络通信业务陷入停顿,即出现死锁现象。

TCP拥塞控制,我们要面对的另一座大山,从上面对于TCP拥塞控制一段简单的话可以看出,此时对于TCP拥塞控制解决的问题不在是点对点的问题,而是要解决整个网络状况的问题。那么网络的拥塞是怎么发生的呢:对网络中某一资源的的需求超过了该资源可提供的能力,也就是说对资源的需求大于可用的资源时就会发生拥塞,整个网络的性能也就会随着下降。

有的人说,解决拥塞问题为什么要TCP去控制,直接把网络中的资源变多不就行了么。比如将路由器中的节点缓存容量变大,这样就能缓存更多的资源。但是你想没想过虽然节点缓存变大了,但是路由器处理数据包的速度并未发生变化,所以大量的数据就要进行排队,这时超时重传就会发生,导致的结果是节点排队的数据越来越来,直到路由器再也处理不过来就发生了死锁。又有人说将路由器处理效率提高不久可以了,这确实是一个办法,但是即便你提高了处理速度,此时网络传输的瓶颈又会被转移到其他部分,这有点像木桶原理,决定事物好坏的上限永远和事物的短板紧密相连。更何况,计算机也像人一样欲望是无穷的,对于处理速度惊人的计算机来说,怎么才是最好呢?

所以我们这里重在讨论是控制的思想,不管你所拥有的资源有多少,合理的控制才是最关键的。TCP拥塞控制就是防止过多的数据注入到网络中。那么我们此时使用一张图看看注入网络的数据量(也叫做网络负载)和网络的吞吐量的关系:
在这里插入图片描述
从图中我们可以看出,在理想状态下未发生拥塞时图形程45度角,也就是说发出的数据接收端总可以收到。但在实际的拥塞控制中当到达某一阶段虽然网络吞吐量还未饱和却已经出现了丢包,这是发生了轻度的拥塞现象。如果不进行拥塞控制,由于重传数据包会使网络加重拥塞,最后到达某一个点时就会发生死锁。

实际上我们要设计一个完美的拥塞控制的方案非常的难,因为拥塞问题是一个动态的问题,他时刻要随着网络的状况进行变化。一般拥塞控制会有两种方式,一种是开环控制,另一种是闭环控制。
开环控制的意思就是说在设计网络之前就已经充分考虑到了网络中所有会出现的情况,并设计一种理想的拥塞控制方案。但是想都不用想,在这个动态的问题面前开环的方式是决对行不通的。
闭环控制的思想则更合理一些,他会检测网络发生拥塞在何处,并将拥塞情况发送到可采取行动的地方,并调整网络系统来解决网络出现的问题。然而这也并不是万全之策,因为总是频繁的调整网络系统会使网络系统变得不稳定。所以我们尽量要选择一种合适的方式来进行拥塞控制,那么一起看看TCP是怎么实现拥塞控制的。

TCP的拥塞控制的方法:

关于TCP的拥塞控制方式我们需要谈到4种算法,他们分别是慢开始算法拥塞避免算法快重传算法快恢复算法。我们这里先来谈一谈慢开始算法个拥塞控制算法,他们俩结合起来就构成了我们最简单的拥塞控制机制。
这里还要强调的是,我们下面讨论的拥塞避免算法是基于窗口的拥塞控制,这又出现了一个新的概念叫做拥塞窗口,拥塞窗口的大小取决于网络的拥塞程度,网络未出现拥塞,此窗口则可以变得更大。相反,发生拥塞时此窗口就需要减小。这里我们可以先简单的理解为拥塞窗口的大小等于发送窗口的大小。
这里提到的第一种慢开始算法就是基于拥塞窗口实现的,慢开始算法的思想是这样的:既然我现在并不知道网络状况的是什么样子的,那我就开始先发少量的数据包,如果对方能够收到我们则发送更多的数据包。也就是将我们的发送窗口逐渐增大(这里其实是将我们的拥塞窗口增大),当发生拥塞后我们在将拥塞窗口减小。现在我们来看看拥塞窗口增大的规律是怎么样的。
在这里插入图片描述
从上图中我们可以看出,cwnd也就是拥塞窗口的大小从1~2, 2~4, 4~8进行指数级别的变化,每一轮次拥塞窗口都变为上次的2倍,但是我们发现如果拥塞窗口的大小按照指数级别增长的话那么很快拥塞窗口大的你就无法想象了举个很简单的栗子 2^32应该对于程序猿来说并不陌生,你想想假如按这种速度增长,32次之后这个窗口会变为多大。
在这里插入图片描述
暂时先不谈合不合理,有的同学认为这名字起的就有问题,这么快的增长速度为什么要叫做慢开始算法。其实这里慢开始算法指的并不是阻塞窗口增长的速率慢,而是TCP开始时先设置cwnd为1,探测以一下网络状态,这比第一次就发送一批数据要慢的多。
这里有一点非常重要,虽然上面图好像是每一个轮次之后才让窗口增大2倍但是实际TCP在设计时只要发送方接到一个新报文的确定就会让cwnd增大一。啊~,更本质的说,其实这里的窗口大小也不是增加一,他在底层肯定是一段长度,窗口增大其实就是给这段长度增大一个MSS+报头长的长度。这还不算完,初始的cwnd并非一定从1开始,这里只是为了更好的说明问题,TCP协议中规定初始cwnd大小不能超过4倍的MSS+TCP报头长度,也就是不能超过4倍的SMSS(最大报文长度),根据报文长度设置窗口大小为 2~4,有兴趣的同学可以参考一下计算机网络这本书。

我们上面已经明确的表明如果窗口大小一直按照指数级别增长那么反而会加重网络的拥塞,所以我们这里又要记住一个新名词:慢开始门禁,也叫慢开始阈值ssthresh,什么意思呢,就是当你的cwnd大于等于这个阈值之后就要执行我们接下来的下一种算法:拥塞避免算法

拥塞避免算法其实本质上就是一种"加法增大"思想的算法,也就是说,当cwnd等于慢开始门禁之后就不再进行指数级别的增长,而是随后每一次只将cend增大1,但是也不要被他的名字所迷惑,拥塞避免不意味着真的可以避免拥塞,只是使网络比较不容易出现拥塞,另一个方面这种加法的思想也可能大体的计算出网络饱和时的cwnd大小。
我们这里将慢开始门禁的阈值设置为16,如下图中所示,当到cwnd到16时,我们便开始执行拥塞避免算法,当到24时网络出现了拥塞,注意注意。当出现拥塞后我们的阈值会减半为拥塞时窗口的一般,我们的cwnd又会重新从1开始指数增长重复我们上面的动作。这种阈值减半的思想也叫乘法减小思想。
在这里插入图片描述
然而聊了这么久,我们一直不停的在说网络拥塞后怎么怎么样,那么计算机是怎么判断网络拥塞的呢?其实在计算机中出现了超时他就认为网络已经发生了拥塞,这里值的强调的是我们这不考虑数据丢失的问题,因为当前技术已经比较成熟,丢失的概率不足百分之一,所以丢失的特例我们就不给予讨论了。

那既然发生了超时重传我们就会重新将cwnd设置为1,然后又开始执行之前的动作,那假如某段网络可能真的是线路的问题出现了数据包的频繁丢失,而事实网络并没有阻塞,这种错误的判断让网络的利用率大大降低。为了解决这个问题,我们的TCP协议在拥塞控制中又引入俩种新的机制,分别是快重传算法快恢复算法

先来聊一聊快重传算法,这种算法按照笔者的理解来看,他其实即使是不使用在拥塞控制这一块,也从很大的程度上解决了我们数据包重传效率的一种算法,之前我们的数据包重传都是使用超时重传算法,所以也叫自动ARQ。但是快重传不一样当一个数据丢失后他接下来的三个确认会重复确认丢失的那个数据,发送方接收到这三个连续的应答后就会立即重传此报文。那么有的同学这里会有疑问,有了快重传还要超时做什么?其实超时重传是重传机制最后的底线,如果真的发生了网络拥塞,那么这三个连续的应答可能也就丢失了,当没有收到这些应答时,发送端就会进行超时重传
在这里插入图片描述
那你讲拥塞控制,关我快重传什么事,你这快重传不是用来提高重传的效率的么,和拥塞控制又没关系:
在这里插入图片描述
别急,回头看我们的问题,我们的问题是假设没有发生网络拥塞,是数据包丢失了,发送端误认为超时了,所以将窗口重新设置为1,但是加入快重传之后,我们的发送方接收到了这些应答,那既然应答可以接收到,也就证明网络并不拥塞,所以我们就不必直接将cwnd设置为1重新开始,而是执行一种快恢复的算法。
快恢复算法指的是如果发送方收到了来自接收端的3个连续重复应答后并不将cwnd设置为1,而是直接设置为当前慢开始门禁大小的一半然后执行拥塞避免算法,因为既然能收到来自接收端的3个连续重复应答,说明要么是数据丢失,要么是轻度拥塞,没有必要直接将窗口设置为1。下图因为笔者没找到资源,所以用手机拍的照片,看起来怪怪的。
在这里插入图片描述
所以现在你必须明白,实际上TCP的拥塞控制有两条路可以走,发生超时重传就执行慢开始算法,如果发生快重传就执行快恢复算法,不同的算法搭配起来能够更高效的对网络进行拥塞控制:
在这里插入图片描述
现在我们在处理之前的一个遗留问题,之前我们说发送的窗口的大小不仅要根据接收窗口大小来设定,还要根据拥塞窗口的大小来设置,我们此时也就能明白发送窗口其实是选取接收窗口和拥塞窗口中的最小值来设定的。rwnd和cwnd控制了发送方的发送速率。

自此我们的拥塞控制也就讲解完了,这里其实思想很简单,大家只要读懂意思相信对拥塞控制会有更生一步的理解。

TCP其他特点

我们翻越了几座大山之后,现在就只剩几个TCP简单的特性,我们这里简单的一笔带过就好,并不是什么难的问题:

  • TCP提供全双工通信:其实你仔细思考为什么TCP需要一组序号和确认号,按照我们上面的示例来看好像只需要发送端填充序号,接收端填充确认号不就行了。但是你得清楚,TCP建立连接的双方是平等的,他们之间既可以是发送端也可以是接收端,并且TCP支持同时发和收。
  • TCP捎带应答:如同字面意思一样,如果你给对方响应ACK报文的时候也可以带上数据,或者发送数据的时候带上应该,这里不在详细接收,很简单的机制。
  • TCP建立连接只有俩个断点:上过计算机公开课的老铁肯定都知道,老师端的电脑可以一次将一个数据扩散发送到全部同学的主机上,这是因为这种机制是UDP传输,他不建立连接,所以可以向广播一样发给所有人。TCP不一样,他一次只能建立连接只能对端通信,这其实也是TCP于UDP相比没有的优点。
  • TCP面向字节流:流这个概念怎么理解呢?取过快递的同学都明白快递是一个一个的,所以也称成有明显的边界。而流的意思就是说没有明显的边界,比如你接了一盆水,别人让你找出水和水之间的边界你可能会觉得这人有点大问题。拿到TCP中意思是说,TCP不关心数据的边界,他也不知道什么是边界,他将字符串只放到缓存区中,其余的事全部都交给上层处理。

这些就是TCP其余的特点,但是我们关于TCP还有一个经典到不能再经典的问题,TCP3次握手和4次挥手,也就是TCP的运输连接管理,本篇博客我们就不介绍了,而是把他单独拿出来,一是这个问题实在太重要剩要我们要单独拿出来讨论;另一方面笔者的这篇文章已经快1.5w字了,再写多会让读者觉得厌烦,所以我们下篇博客总结TCP的运输连接管理。

总结

这篇博客关于知识就不总结了,上面全都是重点,不要错过每一个信息。再次提醒读者们的是,本博客除了第一张图全都是来自谢希仁老师的《计算机网络》第五和第七版,笔者建议大家去读一读第七版,真的读完之后会有让你意想不到的收获和进步。

  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值