一, 为什么 TCP 是可靠传输?
1. 停止等待协议
通过确认与超时重传机制实现可靠传输
在发送完一个分组后, 必须暂时保留已发送的分组的副本.
分组和确认分组都必须进行编号.
超时计时器的重传时间应当比数据在分组传输的平均往返时间更长一些.
出现差错或丢失的时候, 发送方会将自己备份的副本再重传一次, 直到收到接收的确认信息. 当接收方收到重复的数据时, 会直接丢弃, 但是会给发送方请确认自己已经收到了.
2. 改进的停止等待协议 -- 连续 ARQ 协议和滑动窗口协议
上面的停止等待协议每发送一组数据就必须等到接收方回复确认后, 再发起第二组数据, 如果出现超时重传的话, 效率更低. 因此为了提高传输的效率, 改进了等待传输协议.
连续 ARQ 协议和滑动窗口协议的机制是以接收方回复确认为单位, 每次连续发送一个滑动窗口指定的数据组. 示例图如下:
2.1 什么是滑动窗口协议?(发送方怎么发送数据)
滑动窗口是由发送方维护的类似指针的变量, 在每收到一个接收方的确认消息后, 该指针向前移动并发送数据, 到窗口指定大小的数据组时停下, 等待接收方的确认. 示意看图:
2.2 接收方怎么回复确认?
累积确认机制
发送方不对收到的分组逐个发送确认, 而是对按序到达的最后一个分组发送确认, 这样就表示: 到这个分组为止的所有分组都已正确收到了.
优点: 容易实现, 即使确认丢失也不必重传.
缺点: 不能向发送方反映出接收方已经正确收到的所有分组的信息.
Go-back-N(回退 N)
为了解决上述同一窗口中数据组不能完整确认的问题, 连续 ARQ 协议采用了回退机制. 比如说: 发送方发送了前 5 个分组, 而中间的第 3 个分组丢失了. 这时接收方只能对前两个分组发出确认. 发送方无法知道后面三个分组的下落, 而只好把后面的三个分组都再重传一次. 这就叫做 Go-back-N(回退 N), 表示需要再退回来重传已发送过的 N 个分组.
结论: 当通信线路质量不好时, 连续 ARQ 协议会带来负面的影响. 可能还不如传统的停止等待协议.
3. TCP 可靠传输的实现
TCP 连接的每一端都必须设有两个窗口 -- 一个发送窗口和一个接收窗口.
TCP 的可靠传输机制用字节的序号进行控制. TCP 所有的确认都是基于序号而不是基于报文段.
TCP 两端的四个窗口经常处于动态变化之中.
TCP 连接的往返时间 RTT 也不是固定不变的. 需要使用特定的算法估算较为合理的重传时间.
3.1 以字节为单位的滑动窗口技术
滑动窗口是面向字节流的, 为了方便记住每个分组的序号, 下面的图解以一个分组假设 100 个字节, 为了方便画图表示, 将分组进行编号简化表示, 如图所示, 但是要记住, 每一个分组的序号是多少.
3.2 改进的确认 -- 选择确认(SACK)
TCP 通信时, 如果发送序列中间某个数据包丢失, TCP 会通过重传最后确认的分组后续的分组, 这样原先已经正确传输的分组也可能重复发送, 降低了 TCP 性能. SACK(Selective Acknowledgment, 选择确认)技术, 使 TCP 只重新发送丢失的包, 不用发送后续所有的分组, 而且提供相应机制使接收方能告诉发送方哪些数据丢失, 哪些数据已经提前收到等. 在建立 TCP 连接时, 就要在 TCP 首部的选项中加上 "允许 SACK" 的选项, 而双方必须都事先商定好. 原来首部中的 "确认号字段" 的用法仍然不变. 只是以后在 TCP 报文段的首部中都增加了 SACK 选项, 以便报告收到的不连续的字节块的边界.
选择性确认最多表示 4 个边界: 由于首部选项的长度最多只有 40 字节. 需要一个字节指明是 SACK 选项, 另一个字节指明占多少字节. 而指明一个边界就要用掉 4 字节. 在选项中最多只能指明 4 个字节块的边界信息. 因 4 个字节块共 8 个边界信息.
抓包分析:
3.3 超时重传时间的选择
重传机制是 TCP 中最重要和最复杂的问题之一. TCP 每发送一个报文段, 就对这个报文段设置一次计时器. 只要计时器设置的重传时间到但还没有收到确认, 就要重传这一报文段. 那么这个重传时间到底应该设置多少呢? 这里面有学问. 以下是我截取的 "手抄报", 暂时看不懂. 建议跳过.
加权平均往返时间
TCP 保留了 RTT 的一个加权平均往返时间 RTTS(这又称为平滑的往返时间). 第一次测量到 RTT 样本时, RTTS 值就取为所测量到的 RTT 样本值. 以后每测量到一个新的 RTT 样本, 就按下式重新计算一次 RTTS:
超时计时器设置的超时重传时间 RTO
往返时间的测量
Karn 算法
二, TCP 的流量控制
流量控制 (flow control) 就是让发送方的发送速率不要太快, 既要让接收方来得及接收, 也不要使网络发生拥塞. 利用滑动窗口机制可以很方便地在 TCP 连接上实现流量控制.
流量控制举例说明:
零窗口处理 -- 持续计数器
考虑上面的例子中, 当 A 发送的数据已经到达 B 的接收窗口上限, 此时 A 就必须等待 B 处理了部分数据后, 待接收窗口有空闲的时候, 再次发送数据, 那么 A 是怎么知道 B 的接收窗口何时有空闲呢? 这时就用到了持续计时器.
TCP 为每一个连接设有一个持续计时器. 只要 TCP 连接的一方收到对方的零窗口通知, 就启动持续计时器. 若持续计时器设置的时间到期, 就发送一个零窗口探测报文段(仅携带 1 字节的数据), 而对方就在确认这个探测报文段时给出了现在的窗口值. 若窗口仍然是零, 则收到这个报文段的一方就重新设置持续计时器. 若窗口不是零, 则死锁的僵局就可以打破了.
三, TCP 的传输效率
关于 TCP 的传输效率问题, 需要从三方面来考虑, 1. 何时发送; 2. 少字节发送数据问题; 3. 糊涂窗口综合症问题
3.1 TCP 报文的发送时机:
第一种机制是 TCP 维持一个变量, 它等于最大报文段长度 MSS. 只要缓存中存放的数据达到 MSS 字节时, 就组装成一个 TCP 报文段发送出去.
第二种机制是由发送方的应用进程指明要求发送报文段, 即 TCP 支持的推送 (push) 操作.
第三种机制是发送方的一个计时器期限到了, 这时就把当前已有的缓存数据装入报文段 (但长度不能超过 MSS) 发送出去.
3.2 少量字节发送数据问题:
问题描述: 如果应用程序一次产生一字节数据, 这样会导致网络由于太多的包而过载. 如: 从键盘输入的一个字符, 占用一个字节, 可能在传输上造成 41 字节的包, 其中包括 1 字节的有用信息和 40 字节的标题数据.
解决方案(NAGLE 算法): 发送端的应用进程将欲发送的数据逐个字节地送到 TCP 缓存, 则发送端就将第一个字符先发送出去. 将后面到达的字符都缓存起来. 当接收端收到对第一个字符确认后, 再将缓存中的所有字符装成一个报文段发送出去, 同时继续对随后到在的字符进行缓存. 只有在收到对前一个报文段确认后才继续发送下一个报文段.
3.3 糊涂窗口综合症问题:
问题描述: 设想一种情况: 接收端缓存已满, 而交互式的应用进程一次只从缓存中读取一个字符, 然后向发送端发送确认, 并将窗口设置 1 个字节. 接着发送端又发来 1 个字符. 接收端发回确认, 仍然将窗口设置为 1 个字节. 这样下去, 网络效率非常低.
解决方案: 设想一种情况: 接收端缓存已满, 而交互式的应用进程一次只从缓存中读取一个字符, 然后向发送端发送确认, 并将窗口设置 1 个字节. 接着发送端又发来 1 个字符. 接收端发回确认, 仍然将窗口设置为 1 个字节. 这样下去, 网络效率非常低.
来源: https://www.cnblogs.com/ytuan996/p/10598532.html