概述
TCP 的 Window 表头用于指定滑动窗口的大小,单位是字节。因为 Window 占 16 位,所以 TCP 的标准滑动窗口最大为 2^16-1=65535 个字节。另外,Options 表头包含滑动窗口的扩大因子。滑动窗口的主要作用有:
- 流量控制:
发送方不能以超过接收方所能接受的速率发送分段,主要方式是:接收方在返回 ACK 时,包含自己的接收窗口的大小,进而控制发送方的发送窗口的大小
- 保证 TCP 的可靠性:
可靠性建立在“确认重传”的基础上
发送窗口
在任何时刻,TCP 会话的发送方的缓冲区中的数据都可以分为四类:
- 已发送并且得到对端 ACK 的
- 已发送但是未收到对端 ACK 的
- 未发送但是对端允许发送的
- 未发送并且对端不允许发送的
已发送但是未收到对端 ACK 的和未发送但是对端允许发送的,这两部分数据被称为发送窗口。
上图中的提议窗口就是接收方 ACK 时通报的窗口。
只有在收到对端的 ACK 时,发送窗口的左边缘才会移动。并且当收到的 ACK 要求左边缘左移的时候,会被丢弃,因为它是重复的确认。
窗口两端的运动会增加或减少窗口的大小。我们使用三个术语描述窗口边缘的左右运动:
- 窗口闭合(window closes):窗口的左边缘向右移动。窗口闭合发生在已发送分段并收到对端 ACK 的情况下
- 窗口打开(window opens):窗口的右边缘向右移动。当接收端的应用进程读取已确认的数据时,接收端的接收窗口的右边缘会向右扩张,实际上接收窗口是一个环行缓冲区,接收窗口的右边缘扩张会使用原来被应用进程取走内容的缓冲区。接收窗口扩张后会使用 ACK 通知发送方,进而导致发送方的发送窗口打开,因为 ACK 的序号仍然是上次的,所以发送窗口不会闭合
- 窗口收缩(window shrinks):窗口的右边缘向左移动。RFC 不鼓励这种做法,但是 TCP 必须能够处理这种情况
如果窗口的左边缘和右边缘重合,就称它为“零窗口”。它将停止发送者传输任何数据。
接收窗口
在任一时刻,TCP 会话的接收方的缓冲区,可以分为三部分:
- 保存已接收的数据的部分(因为 ACK 是由 TCP 协议栈直接回复的,无应用延迟,所以不存在已接收,但未 ACK 的情况)
- 允许接收数据的部分
- 不允许接收数据的部分
其中,允许接收数据的部分就是接收窗口。
接收窗口的移动:
- 窗口闭合:
接收窗口的左边缘向右移动。在收到发送方的数据后,接收方会确认数据的准确性,然后把数据存储到缓冲区中。需要注意的是:接收窗口只会确认连续的分组,对于乱序的分组,则是先接收下来,避免重复传送。 在确认连续的分组后,因为数据还没有被应用进程取走,此时就需要进行窗口的闭合
- 窗口打开:
参考发送窗口的窗口打开
- 窗口收缩:
接收窗口的右边缘向左移动。RFC 不鼓励这种做法,但是 TCP 必须能够处理这种情况
发送窗口和接收窗口的关系
TCP 是全双工的,可以同时传送和接收数据,因此 TCP 会话的两端各自维护一个接收窗口和发送窗口。其中,接收窗口的大小受限于应用程序、硬件、系统等;而发送窗口的大小则取决于对端通告的接收窗口。
滑动窗口和 Socket 缓冲区的关系
- TCP 的滑动窗口的大小实际上就是 Socket 的接收缓冲区的大小
- 对于 Server端 Socket 一定要在 Listen 前设置缓冲区大小,因为 Accept 时新产生的 Socket 会继承监听 Socket 的缓冲区大小。对于 Client 端 Socket 一定要在 Connet 前设置缓冲区大小,因为 Connet 时需要进行三次握手过程,会通知对方自己的窗口大小。在 Connet 之后再设置缓冲区,已经没有意义
参考文档