文章目录
- 参考资料
- TCP滑动窗口
- 概述
- 引入窗口概念的原因
- 窗口大小由哪一方决定?
- 发送方的滑动窗口
- 接收方的滑动窗口
- 接收窗口和发送窗口的大小是相等的吗?
- TCP的可靠性,超时重传怎么实现
- 滑动窗口如何实现面向流的可靠性?
参考资料
你还在为 TCP 重传、滑动窗口、流量控制、拥塞控制发愁吗?一文搞定!
太厉害了,终于有人能把TCP/IP协议讲的明明白白了!
TCP协议的滑动窗口具体是怎样控制流量的?
TCP滑动窗口
概述
滑动窗口协议的基本原理就是:
-
在任意时刻,发送方都维持了一个连续的允许发送的帧的序号,称为发送窗口。
-
同时,接收方也维持了一个连续的允许接收的帧的序号,称为接收窗口。
-
发送窗口和接收窗口的序号的上下界不一定要一样,甚至大小也可以不同。不同的滑动窗口协议窗口大小一般不同。
-
发送方窗口内的序列号代表了那些已经被发送,但是还没有被确认的帧,或者是那些可以被发送的帧。
引入窗口概念的原因
- 在以前,TCP在发送数据的过程中, 以1个段为单位,每发送一个段进行一次确认应答的处理。这样的传输方式有一个缺点,就是包的往返时间越长通信性能就越低。这就是停止等待协议。
就像是这样,一个单位就要来回应答一次,效率实在太差:
老师说"从"学生写"从". 学生说"嗯"老师说"前"学生写"前". 学生说"嗯"老师说"今天我还想早点下班呢..."
- 为解决这个问题,TCP 引入了窗口这个概念。确认应答不再是以每个分段,而是以更大的单位进行确认,转发时间将会被大幅地缩短。也就是说,发送端主机,在发送了一个段以后不必要一直等待确认应答,而是继续发送。
- 窗口大小就是指无需等待确认应答而可以继续发送数据的单位值。例如,上图中窗口大小为4个段。这个机制实现了使用大量的缓冲区,通过对多个段同时进行确认应答的功能。
窗口的实现实际上是操作系统开辟的一个缓存空间,发送方主机在等到确认应答返回之前,必须在缓冲区中保留已发送的数据。如果按期收到确认应答,此时数据就可以从缓存区清除。
假设窗口大小为 3
个 TCP 段,那么发送方就可以「连续发送」 3
个 TCP 段,并且中途若有 ACK 丢失,可以通过「下一个确认应答进行确认」。如下图:
图中的 ACK 600 确认应答报文丢失,也没关系,因为可以通话下一个确认应答进行确认,只要发送方收到了 ACK 700 确认应答,就意味着 700 之前的所有数据「接收方」都收到了。
这个模式就叫累计确认或者累计应答。
窗口大小由哪一方决定?
TCP 头里有一个字段叫 Window
,也就是窗口大小。
这个字段是接收端告诉发送端自己还有多少缓冲区可以接收数据。于是发送端就可以根据这个接收端的处理能力来发送数据,而不会导致接收端处理不过来。
通常窗口的大小是由接收方的决定的。
发送方发送的数据大小不能超过接收方的窗口大小,否则接收方就无法正常接收到数据。
发送方的滑动窗口
对于TCP会话的发送方,任何时候在其发送缓存内的数据都可以分为4类
-
“已经发送并得到对端ACK的”
-
“已经发送但还未收到对端ACK的”
-
“未发送但对端允许发送的”
-
“未发送且对端不允许发送”
下图就是发送方缓存的数据,根据处理的情况分成四个部分,其中蓝色方框是发送窗口,紫色方框是可用窗口:
**“已经发送但还未收到对端ACK的”和“未发送但对端允许发送的”**这两部分数据称之为发送窗口。
TCP 滑动窗口方案使用三个指针来跟踪在四个传输类别中的每一个类别中的字节。其中两个指针是绝对指针(指特定的序列号),一个是相对指针(需要做偏移)。
SND.WND
:表示发送窗口的大小(大小是由接收方指定的);SND.UNA
:是一个绝对指针,它指向的是已发送但未收到确认的第一个字节的序列号,也就是 #2 的第一个字节。SND.NXT
:也是一个绝对指针,它指向未发送但可发送范围的第一个字节的序列号,也就是 #3 的第一个字节。- 指向 #4 的第一个字节是个相对指针,它需要
SND.UNA
指针加上SND.WND
大小的偏移量,就可以指向 #4 的第一个字节了。
那么可用窗口大小的计算就可以是:
可用窗口大小 = SND.WND -(SND.NXT - SND.UNA)
- #1是已发送并收到 ACK确认的数据:1~31 字节
- #2 是已发送但未收到 ACK确认的数据:32~45 字节
- #3 是未发送但总大小在接收方处理范围内(接收方还有空间):46~51字节
- #4 是未发送但总大小超过接收方处理范围(接收方没有空间):52字节以后
当发送方把数据「全部」都一下发送出去后,可用窗口的大小就为 0 了,表明可用窗口耗尽,在没收到 ACK 确认之前是无法继续发送数据了。
当收到之前发送的数据 32~36
字节的 ACK 确认应答后,如果发送窗口的大小没有变化,则滑动窗口往右边移动 5 个字节,因为有 5 个字节的数据被应答确认,接下来 52~56
字节又变成了可用窗口,那么后续也就可以发送 52~56
这 5 个字节的数据了。
接收方的滑动窗口
接收窗口相对简单一些,根据处理的情况划分成三个部分:
- #1 + #2 是已成功接收并确认的数据(等待应用进程读取);
- #3 是未收到数据但可以接收的数据;
- #4 未收到数据并不可以接收的数据
三个接收部分,使用两个指针进行划分:
RCV.WND
:表示接收窗口的大小,它会通告给发送方。RCV.NXT
:是一个指针,它指向期望从发送方发送来的下一个数据字节的序列号,也就是 #3 的第一个字节。- 指向 #4 的第一个字节是个相对指针,它需要
RCV.NXT
指针加上RCV.WND
大小的偏移量,就可以指向 #4 的第一个字节了。
接收窗口和发送窗口的大小是相等的吗?
并不是完全相等。
接收窗口的大小约等于发送窗口的大小。
因为滑动窗口并不是一成不变的。
比如,当接收方的应用进程读取数据的速度非常快的话,这样的话接收窗口可以很快的就空缺出来。
那么新的接收窗口大小,是通过 TCP 报文中的 Windows 字段来告诉发送方。
那么这个传输过程是存在时延的,所以接收窗口和发送窗口是约等于的关系。
TCP的可靠性,超时重传怎么实现
M1,M2,M3,M4,M5,丢失M2;怎么重传M2?
因为每个TCP报文被发送时,都会设置一个重传定时器,若定时期到了还没收到ack包,则应重传。
为什么不用重传M4,M5?
因为采用了累积确认。有例如下:
- Server 发送80个字节 Part1,seq = 1
- Server 发送120个字节Part2,Seq = 81
- Server发送160个字节Part3,Seq = 201,此包由于其他原因丢失
- Client收到前2个报文段,并发送ACK = 201
- Server发送140个字节Part4, Seq = 361
- Server收到Client对于前两个报文段的ACK,将2个报文从窗口中移除,窗口有200个字节的余量
- 报文3的重传定时器到期,没有收到ACK,进行重传
- 这个时候Client已经收到报文4,存放在缓冲区中,也不会发送ACK【累计通知,发送ACK就表示3也收到了】,等待报文3,报文3收到之后,一块对3,4进行确认
- Server收到确认之后,将报文3,4移除窗口,所有数据发送完成
滑动窗口如何实现面向流的可靠性?
1)最基本的传输可靠性来源于“确认重传”机制。
2)TCP的滑动窗口的可靠性也是建立在“确认重传”基础上的。
3)发送窗口只有收到对端对于本段发送窗口内字节的ACK确认,才会移动发送窗口的左边界。
4)接收窗口只有在前面所有的段都确认的情况下才会移动左边界。当在前面还有字节未接收但收到后面字节的情况下,窗口不会移动,并不对后续字节确认。以此确保对端会对这些数据重传。