可靠传输的实现及流量控制
以字节为单位的滑动窗口
假定数据传输只在一个方向进行,即 A 发送数据,B 给出确认。现假定 A 收到了 B 发来的确认报文段,其中窗口是 20 字节,而确认号是 31(这表明 B 期望收到的下一个序号是 31,而序号 30 为止的数据已经收到了)。
发送窗口后沿的后面部分表示已发送且已收到了确认。这些数据显然不需要再保留了。而发送窗口前沿的前面部分表示不允许发送的,因为接收方都没有为这部分数据保留临时存放的缓存空间。
发送窗口的位置由 {窗口前沿和后沿位置} 共同确定。
- 后沿的变换 // 不可能向后移动,因为不能撤销掉已收到的确认
- 不动(没有收到新的确认)
- 前移(收到了新的确认)
- 前沿的变化
- 前移
- 不动(情况1:没收到新的确认,对方通知的窗口大小也不变;情况2:收到了新的确认但对方通知的窗口缩小了,使得发送窗口前沿正好不动)
- Tips
- A 的发送窗口并不总是和 B 的接收窗口一样大(因为有一定的时间滞后)
- 标准没有规定对不按序到达的数据应如何处理,通常是先临时存放在接收窗口中,等到字节流中所缺少的字节收到后,再按序交付上层的应用进程 // 选择确认 SACK (Selective ACK)
- TCP 要求接收方必须有累积确认的功能,这样可以减小传输开销
- 接收方发送确认
- 接收方可以在合适的时候发送确认,也可以在自己有数据要发送时把确认信息顺便捎带上
- 接收方不应过分推迟发送确认,否则会导致发送方不必要的重传,这反而浪费了网络的资源
- 捎带确认实际上并不经常发生,因为大多数应用程序很少同时在两个方向上发送数据
发送缓存&接收缓存
- 发送缓存:发送方的应用进程把字节流写入 TCP 的发送缓存
- 存放发送应用程序传送给发送方 TCP 准备发送的数据
- 存放 TCP 已发送出但尚未收到确认的数据
- 接收缓存:接收方的应用进程从 TCP 的接收缓存中读取字节流
- 存放按序到达的、但尚未被接收应用程序读取的数据
- 存放不按序到达的数据
流量控制
引入
- 一般说来,我们总是希望数据传输得更快一些。但如果发送方把数据发送得过快,接收方就可能来不及接收,这就会造成数据的丢失。
- 流量控制(flow control) 就是让发送方的发送速率不要太快,既要让接收方来得及接收,也不要使网络发生拥塞。
- 利用滑动窗口机制可以很方便地在 TCP 连接上实现流量控制。
举例
持续计时器
"死锁"场景
- B 向 A 发送了 0 窗口的报文段后不久,B 的接收缓存又有了一些存储空间。于是 B 向 A 发送了 rwnd = 400 的报文段
- 但这个报文段在传送过程中丢失了。A 一直等待收到 B 发送的非零窗口的通知,而 B 也一直等待 A 发送的数据
- 如果没有其他措施,这种互相等待的死锁局面将一直延续下去
解决"死锁"
- TCP 为每一个连接设有一个持续计时器
- 只要 TCP 连接的一方收到对方的零窗口通知,就启动该持续计时器
- 若持续计时器设置的时间到期,就发送一个零窗口探测报文段(仅携带 1 字节的数据),而对方就在确认这个探测报文段时给出了现在的窗口值
- 若窗口不是 0,则死锁的僵局就可以打破了
传输效率
可以用不同的机制来控制 TCP 报文段的发送时机:
- TCP 维持一个变量,它等于最大报文段长度 MSS。只要缓存中存放的数据达到 MSS 字节时,就组装成一个 TCP 报文段发送出去
- 由发送方的应用进程指明要求发送报文段,即 TCP 支持的推送 (push) 操作
- 发送方的一个计时器期限到了,这时就把当前已有的缓存数据装入报文段(但长度不能超过 MSS)发送出去