TCP 主要解决的问题:可靠,有序,无丢失,不重复

可靠传输达到的效果:接收方读到的字节流和发送方发出的是相同的。

Q:TCP 通信是一对一的,为什么服务器可以同时和多个客户端进行通信? A:内核通过四元组来标识一个 socket: {server_ip, server_port, client_ip, client_port} 。服务器可以同时和多个客户端建立 TCP 连接,进行通信。服务器是用一个端口建立了多个连接的,这并不违背 TCP 一对一通信的特性。

长度问题

UDP 对于应用层传下来的数据,不拆分,不合并,直接包装后发送

TCP 是面向字节流的
如果应用层传来的数据太短,就先合并再发送;如果太长,就切分再发送。
发送的大小还收到接收方的窗口大小网络的拥塞程度影响。

MSS 最大报文段长度
数据字段的最大程度

连接管理

连接建立

image.png
前两个报文都是 SYN 报文,不能携带数据,要消耗一个序号
最后一个报文可以携带数据,如果不携带数据就不会消耗序号

注:在已经建立起连接的的情况下,后续的连接建立会被阻止。

为什么需要第三次握手
**
🌟 第一个解释:为了防止「已失效的连接建立请求报文」到达服务器端,建立起无效连接,浪费服务器的资源
考虑这种情况:连接建立过程只需要握手 2 次。客户机先发送一个 SYN,超时了;客户机又发送了 SYN,建立了连接,传送了数据,之后断开连接。先前发送的 SYN 在此时到达了服务器,由于只需要两次握手就能建立连接,因此服务器又进入了连接建立状态,这就浪费了服务器的资源。

🌟 第二个解释:TCP 的可靠性传输是建立在 seq - ACK 机制上面的,接收方需要确保发送方发来的序号是否是有效的。
为了确保发送方真的想要建立连接,需要:

  • 接收方向发送方确认序号是否有效
  • 发送方向接收方回复这条序号是有效的

以上过程至少需要三次报文。

如果接收方收到了很久以前传来的连接建立请求,并向发送方确认这条请求;发送方此时并不想建立连接,或者建立连接的序号和接收方发来的 ACK 对不上,那么发送方就可以不理会这条请求,连接不会建立起来。

另外:发送方是主动建立连接的一方,所以它不需要向接收方确认对方的序号是否有效。

连接释放

image.png

握手和挥手的报文都不携带用户数据,但是每个报文都要消耗掉一个序号(seq)

可靠传输

重传

有两种情况会引起 TCP 发送方重传报文,「超时」和「收到 3 次冗余 ACK」时

超时重传
这是被动的方式。
发送方会为每个发送出的报文设置一个定时器,如果超过指定时间后仍未收到对应报文的 ACK,就认为报文丢失,进行重传。
超时时间以 RTT(往返时间:从发出报文到收到 ACK 的时间) 为依据,计算 RTT 的均值,并略大于均值

冗余 ACK 重传
接收方主动通知,让发送方进行重传。
当发送方收到对同一个报文的 3 次冗余 ACK 时,就重传这个报文。接收方每收到一个失序报文,就会发送一次冗余 ACK。

可靠传输的机制

停止等待协议
TCP 并没有使用这种协议,这里提到这种协议是为了引入。
发送方在收到对上一个分组的确认后,才发送下一个分组。

发送方在超时时间后未收到确认,就会重传分组。
这种机制叫做自动重传请求 ARQ(automatic repeat reQuest):接收方不需要请求,发送方会自动重传。

连续 ARQ 协议
发送方有一个大小固定的发送窗口,发送方可以连续发送落在窗口内的数据而不需要等待确认;当收到确认时,就将窗口前移。
image.png

滑动窗口
不考虑拥塞控制的情况。发送方有一个发送窗口,接收方有一个接收窗口。
可以用三个指针来表示一个发送窗口: P1, P2, P3

  • P1 ~ P3 表示窗口的大小
  • P1 ~ P2 表示已经发送但未收到确认的
  • P2 ~ p3 表示还可以发送的数据

image.png

考虑以下情况:

  • 接收方收到了重复的分组,是否需要向发送方发出确认?
    • 需要。收到重复分组意味着发送方进行了重传,也就是没有收到对该分组的确认。接收方有必要在此发送对该分组的确认(包含在累计确认中也行)。

窗口机制

流量控制和拥塞控制涉及到了 3 个窗口:

  • 接收窗口 rwnd:仅与接收方的缓存有关。单位是 B
  • 拥塞窗口 cwnd:发送方估算网络拥塞程度,计算出的窗口。单位是 B,大小通常是整数个 MSS
  • 发送窗口 swnd:取接收窗口和拥塞窗口的最小值。单位是 B

拥塞控制会改变 cwnd 窗口的大小,并没有直接改变 swnd 窗口的大小,也没有让窗口滑动,不能说用到了滑动窗口。

流量控制用到了滑动窗口
滑动窗口的单位是 B。对于发送方来说,没有收到 ACK 的报文占据着窗口大小,当收到 ACK 时,窗口发生滑动。

窗口大小的影响

  • 窗口太小:ACK 报文增多。窗口比较大的时候,累计确认可以减少 ACK 的数量。
  • 窗口太大:可能造成网络拥塞

窗口和缓存的关系
以发送缓存为例。应用程序将将要发送的数据写入发送缓存中,TCP 从缓存中读取数据发送出去。
缓存是实际存放数据的空间,发送窗口位于发送缓存之内,通过几个指针标识出来
发送窗口的后沿和发送缓存的后沿重合;发送窗口的前沿不能超过发送缓存的前沿。
image.png

流量控制**

流量控制是指:接收方根据自己缓存的大小,限制发送方的发送速率。
接收方会在 ACK 报文中告诉发送方自己的接受缓存还剩多少 B。

流量控制和拥塞控制的区别
流量控制和拥塞控制的直接手段都是减小发送速率,但是他们的目的是不同的。

网速慢: 如果排除带宽的原因,那么很有可能是 rwnd 较小造成的。rwnd 的最大值为时延带宽积, 带宽 * 往返时延

拥塞控制

拥塞控制是指:防止路由器或者链路超载,是全局的。

通信的端点如何感知拥塞:超时未收到 ACK,就发生了拥塞。
换句话说,路由器可以主动丢弃一些数据包来告知发送端降低发送速率。

基本的控制方法:只要没有发生拥塞,就一直增大拥塞窗口;一旦发生拥塞,就减小拥塞窗口。

慢开始和拥塞避免

发送方的拥塞窗口可以按照「慢开始」和「拥塞避免」的算法变化

总结:cwnd 在慢开始阶段按照指数增加,拥塞避免阶段按照线性增加;发生拥塞时 ssthreash 减半,重新进入慢开始阶段。
image.png

慢开始算法
慢开始算法:

  • 连接刚建立的时候,发送方将拥塞窗口 cwnd 设置为 1(单位为 MSS)
  • 每收到对一个新报文的确认,就将 cwnd 大小增加 1
  • 当 cwnd 大小到达 ssthresh(慢开始阈值)时,就采用拥塞避免算法

按照慢开始的算法,如果不发生拥塞,那么每经过 1 个 RTT,cwnd 大小就翻一倍

注:并不是说每经过一个 RTT 就将 cwnd 大小翻一倍,而是指:每个 RTT 可发送的报文数量等于 cwnd 的大小,发送的报文都收到了 ACK,每个新 ACK 让 cwnd 增加 1,结果是 cwnd 大小翻倍。

拥塞避免算法
当 cwnd 大小到达 ssthresh 时,不再采用「慢开始」算法,而是采用「拥塞避免」算法。

拥塞避免算法:

  • 每经过 1 个 RTT,就将 cwnd 大小增加 1

按照拥塞避免的算法,如果不发生拥塞,那么 cwnd 随着时间(RTT)以线性的方式增加

发生拥塞
在慢开始阶段或者拥塞避免阶段,发生拥塞

  • ssthresh 大小减小到当前 cwnd 大小的一半(最小为 2)
  • cwnd 大小减小到 1,使用「慢开始」算法来增加 cwnd 大小

注: 在某一轮 RTT 开始时,cwnd 为 4,ssthresh 为 6 那么在这一轮 RTT 结束时,cwnd 将变成 6,而不是 7 或 8

快重传和快恢复

「快重传」和「快恢复」是对「慢开始」和「拥塞避免」的补充
当发送方收到 3 个冗余的 ACK 时,就执行「快重传」和「快恢复」

使用了「慢开始」,「拥塞控制」,「快重传」和「快恢复」的情形:
image.png

快重传
收到 3 次冗余 ACK 后,发送方立即发送冗余 ACK 对应的报文。这里的「快」是相较于「超时重传」而言的

快恢复
收到 3 次冗余 ACK,意味着网络可能发生了拥塞,但有没有完全拥塞,所以 cwnd 大小不必降低为 1

快恢复算法

  • 将 ssthresh 大小降低为当前 cwnd 的一半(最小为 2)
  • 将 cwnd 大小设置为当前 cwnd 的一半,执行拥塞控制算法(cwnd 大小线性增长)