窗口概念的引入
由于TCP的可靠传输,在接收到一包数据的时候就会有一个应答,这样就像面对面聊天,你一句我一句,这种方式的缺点就是效率比较低。如果接收方收到消息后没有及时回复应答给发送方,那么发送方就会阻塞,直到接收到应答后才会发送下一次的消息。这显然效率太低。
这样包往返时间越长吞吐量越低,通信效率就越低。
为解决这个问题,TCP引入了窗口这个概念。即使在往返时间较长的情况下也不形象通信的效率。
既然有了窗口这个概念就可以设置窗口的大小,窗口的大小指的就是无需等待应答,还可以继续发送的数据包的最大值。
窗口的实现实际上是操作系统开辟了一个缓存空间,发送方在等到确认应答返回前会将已发送的数据保留在自己的缓存空间中,等待收到应答啊消息后再从缓冲区中清除。
假如窗口大小是3个tcp段,那么发送方就可以连续发送3个tcp段,并且中途如果有1个ack丢失可以通过下一个ack进行确认。
这个模式叫做累计确认或累计应答。
什么是滑动窗口
TCP头里面有一个 Window 字段,也就是窗口大小。
这个字段是接收方告诉发送方自己还有多少缓冲区可以接收数据,于是发送端就可以根据接收端的处理能力来发送数据,而不会导致接收端处理不过来。
所以通常窗口的大小是由接收端确定的。发送方发送的数据不能超过接收端的窗口大小,否则接收方就无法正常接收到数据了。
发送方的滑动窗口
我们从下图来看看发送⽅缓存的数据:
蓝色框是发送窗口,紫色框是可用窗口。
- #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 个字节的数据了。
上面提到的4个部分程序是如何表达的?
TCP滑动窗口用3个指针追踪四个类别中的每个类别的字节。其中两个是绝对指针,1个是相对指针。
- SND.WND:表示发送方的窗口大小,由接收方指定。
- SND.UNA:是一个绝对指针,它指向已发送但未收到确认的第一个字节的序列号,也就是#2的第一个字节。
- SND.NXT:也是一个绝对指针,它指向未发送但是可发送范围的第一个字节的序号,也就是#3的第一个字节。
- 指向#4的第一个字节是一个相对指针,它需要SND.UNA加上SND.WND大小的偏移量,就可以指向#4的第一个字节。
可用窗口的大小计算:
可用窗口 = SND.WND - (SND.NXT - SND.UNA)
接收方的滑动窗口
接收方的滑动窗口较发送方来说简单一些。划分为三个部分:
- #1+#2是接收成功并已经确认的数据(等待应用进程读取)
- #3是未收到但是可以接收的数据
- #4是未收到且不可接收的数据。
其中三个部分使用2个指针划分。 - RCV.WND:表示窗口大小,它会通知给发送方
- RCV.NXT:是一个指针,指向未收到数据但可以接收的序列号,它指向期望从发送方发送来的的下一个数据的字节的序列,也就是#3的第一个字节。
- 指向#4的第一个字节的指针是一个相对指针,它需要RCV.NXT指针加上RCV.WND大小的偏移量。
接收窗口和发送窗口的大小是相等的吗?
并不完全相等,是约等于的关系。
因为滑动窗⼝并不是⼀成不变的。⽐如,当接收⽅的应⽤进程读取数据的速度⾮常快的话,这样的话接收窗⼝可以很快的就空缺出来。那么新的接收窗⼝⼤⼩,是通过 TCP 报⽂中的 Windows 字段来告诉发送⽅。那么这个传输过程是存在时延的,所以接收窗⼝和发送窗⼝是约等于的关系。