Skip to content

Latest commit

 

History

History
208 lines (153 loc) · 13.6 KB

tcp.md

File metadata and controls

208 lines (153 loc) · 13.6 KB

TCP 文档

特点

  • 全双工
  • 点对点(一对一)
  • 可靠数据传输
  • 传输字节流(把应用层数据看成一连串的无结构的字节序列,TCP 不知道所传送字节流的含义)
  • 拥有拥塞控制机制

TCP 报文段结构

TCP 报文由首部字段和数据字段两部分组成。

首部字段

1. 源端口号和目的端口号

用于多路复用/分解

2. 32 比特的序号字段和确认号字段

用于实现可靠数据传输服务。TCP 字节流每一个字节都按顺序编号。确认号是接收方期望从发送方收到的下一字节的序号。

3. 16 比特的接收窗口字段

告诉对方自己的接收窗口大小。

发送方维护一个称为接收窗口(rwnd)的变量用来提供流量控制,提示接收方还有多少可用的缓存空间。

接收窗口 = 接收方缓存 - (接收方已放入接收缓存中的数据流的最后一个字节编号 - 接收方应用程序已读取的最后一个字节编号)。

TCP 为每个连接设有一个持续计时器,只要收到零窗口(接收窗口为零,表示没有缓存空间了)通知,就启动计时器。若计时器到期了,就发送一个零窗口 探测报文(携带 1 字节数据),对方确认时会给出现在的窗口值,如果窗口仍是零,重新启动计时器。

4. 4 位的数据偏移

指出 TCP 报文段的数据起始处距离 TCP 报文段的起始处有多远,实际上指出了 TCP 报文段的首部长度。

5. 可选与变长的选项字段

用于双方协商最大报文段长度(MSS)时使用,或在高速网络环境下用作窗口调节因子时使用。

6. 6 比特的标志字段

PSH URG 一般不使用

  • ACK 用于指示确认字段中的值是有效的 ACK=1 有效 ACK=0 无效。
  • RST 重置报文段,释放连接,再重新建立连接。还用来拒绝打开一个连接或非法报文段。
  • SYN 用于连接建立,SYN 为 1 时,表明这是一个请求建立连接报文。
  • FIN 用于连接拆除,FIN 为 1 时,表明发送方数据已发送完毕,并要求释放连接。
  • PSH 被设为 1 时,表示接收方应立即将数据交给上层,而不要等缓存填满了再上交。
  • URG 表示报文段存在被发送方上层置为“紧急”的数据

7. 选项

长度可变,最多 40 字节,没有选项时,TCP 首部长度为 20 字节。

  1. 选项字段有一个窗口扩大选项,占 3 字节。其中有一个字节表示移位值 S,新的窗口值等于 TCP 首部中的窗口位数从 16 增大到 16 + S。移位值最大值为 14, 相当于窗口最大值增加到 2**30 - 1。窗口扩大选项在双方连接建立时进行协商,如果不需要扩大窗口,发送 S = 0,使窗口大小回到 16 位。

  2. 时间戳选项占 10 字节,其中时间戳值字段(4 字节),时间戳回送回答字段(4 字节)。时间戳选项有两个功能,一是用来计算往返时间 RTT;二是用于处理 TCP 序号超过 2**32 的情况,这称为防止序号绕回 PAWS。当序号超过上限值时,会从 0 重新开始计算。

连接建立过程(三次握手)

TCP 标准规定,ACK 报文段可以携带数据,但不携带数据就不用消耗序号。

  1. 客户端发送一个不包含应用层数据的 TCP 报文段,首部的 SYN 置为 1,随机选择一个初始序号放在 TCP 报文段的序号字段中。(SYN 为 1 的时候,不能携带数据,但要消耗掉一个序号)
  2. TCP 报文段到达服务器主机后,提取报文段,为该 TCP 连接分配缓存和变量,并向客户端发送允许连接的 TCP 报文段(不包含应用层数据)。 这个报文段的首部包含 3 个信息:SYN 置为 1;确认号字段置为客户端的序号 + 1;随机选择自己的初始序号。
  3. 收到服务器的 TCP 响应报文段后,客户端也要为该 TCP 连接分配缓存和变量,并向服务器发送一个报文段。这个报文段将服务器端的序号 + 1 放置在确认号字段中, 用来对服务器允许连接的报文段进行响应,因为连接已经建立,所以 SYN 置为 0。 最后一个阶段,报文段可以携带客户到服务器的数据。并且以后的每一个报文段,SYN 都置为 0。

连接拆除过程

FIN 报文段即使不携带数据,也要消耗序号。

  1. 客户端发送一个 FIN 置为 1 的报文段。
  2. 服务器回送一个确认报文段。
  3. 服务器发送 FIN 置为 1 的报文段。
  4. 客户端回送一个确认报文段。

发送与接收过程

发送

TCP 发送方仅需维护已发送但未确认的字节的最小序号(SendBase)和下一个要发送的字节的序号(NextSeqNum)。

  1. 客户进程通过套接字将数据传给 TCP,TCP 将数据放到该连接的发送缓存,接下来 TCP 会不时的从发送缓存里取出一块数据发送。
  2. 发送时 TCP 会为数据加上 TCP 首部,从而形成报文段。
  3. 报文段下发到网络层,网络层将其封装成 IP 数据报,最后经历链路层、物理层发送到网络中。

接收

TCP 接收方会将正确接收但失序的报文缓存起来。

  1. 接收时从物理层、链路层、网络层一路解包,最后到达传输层。
  2. TCP 接收后会放到该连接的接收缓存,
  3. 应用程序通过套接字从此缓存读取数据流。

TCP 报文段发送时机

有三种发送机制

  1. TCP 维持一个变量,它等于 MSS,只要缓存中的数据达到 MSS 字节时,就组装成一个 TCP 报文段发送出去。
  2. 由发送方的应用进程指明要求发送报文段,即 TCP 支持的推送操作。
  3. 发送方的一个计时器时限到了,这时就把当前已有的缓存数据装入报文段(不能超过 MSS)发送出去。

在 TCP 的实现中广泛采用 Nagle 算法

  1. 发送方把第一个字节发送出去,把后面到达的字节缓存起来
  2. 在收到对第一个字节的确认后,再把缓存中的数据组装成一个报文段发送出去,随后继续缓存数据
  3. 只有收到对前一个报文段的确认后,才会继续发送下一个报文段
  4. 另外,如果缓存数据达到发送窗口的一半或者达到报文段的最大长度时,就立即发送一个报文段

Nagle 算法会引发几种 HTTP 性能问题。首先,小的 HTTP 报文可能无法填满一个分组,可能会因为等待那些永远不会到来的确认分组而产生时延。 其次,Nagle 算法与延迟确认之间的交互存在问题——Nagle 算法会阻止数据的发送,直到收到一个确认分组,但确认分组自身会被延迟确认算法延迟 100 ~ 200 毫秒。

HTTP 应用程序可以在自己的栈中设置参数 TCP_NODELAY,禁用 Nagle 算法,提高性能。

名词解释

1. MSS

最大报文段长度,指报文段应用层数据的最大长度,不包括 TCP 头部,MSS 值和接收窗口没有关系。

2. MTU

最大链路层帧长度,以太网和 PPP 链路层协议具有 1500 字节的 MTU,因此 MSS 的典型值为 1460 字节(TPC/IP 首部长度通常为 40 字节)。

3. 路径 MTU

指从源到目的地的所有链路层上,所能发送的最大链路层帧

4. 累积确认和选择确认

累积确认

TCP 只确认从流开始到第一个丢失字节为止的字节,所以被称为累积确认。

假如发送方发送了 0-10,11-20,21-30 三个报文段,接收方由于某种原因只收到 0-10 和 21-30 字段,所以它下一个报文段的确认号为 11,表示期望收到字节 11(及以后)。

接收方不必对每个接收到的分组逐个发送确认,而是在收到几个分组后,对按序到达的最后一个分组发送确认,表示到这个分组为止的所有分组都已收到了。

选择确认

要使用选择确认,必须在建立连接时,在首部选项中加上允许 SACK 的选项,双方都协商好。然而 SACK 文档没有指明发送方要怎么响应 SACK,所以大多数的实现还是重传所有 未被确认的数据块。

5. 重传

TCP 重传具有最小序号的还未被确认的报文段。

6. 重传超时间隔

它被设置为平均往返时间加上一点余量。重传发生后,下一次的超时间隔被设置为之前的两倍。

7. 快速重传

当接收方接收到一个比期望序号大的失序报文时,说明有报文段丢失,接收方将对最后一次按序字节数据进行重复确认(产生一个冗余 ACK)。 因为发送方是流水线式(一个接一个)发送报文,如果一个报文段丢失,很可能会引起多个冗余的 ACK。当发送方接收到对相同数据的 3 个冗余 ACK 时, 表明这个确认过 3 次的报文段已经丢失,发送方就执行快速重传,即在定时器过期之前重传丢失的字段。

8. 拥塞控制

拥塞控制就是防止过多的数据注入网络,这样可以使网络中的路由或链路不过载。拥塞控制是一个全局性的过程,涉及所有的主机、路由器, 以及与降低网络传输性能有关的所有因素。

而流量控制是指点到点通信量的控制,是个端到端的问题。流量控制抑制发送端的发送速率,以使接收端来得及接收。

如何判断发生了拥塞?TCP 发生丢包就是出现了拥塞。

丢包事件:超时或者收到接收方的 3 个冗余 ACK。

9. 拥塞窗口(cwnd)

发送方维护的一个额外变量,它对 TCP 发送流量速率进行了限制,在发送方中未被确认的数据量不会超过 cwnd 和 rwnd 中的最小值。 TCP 使用确认(成功收到接收方的 ACK)来触发增大它的拥塞窗口长度。

10. 拥塞控制算法

慢启动和拥塞避免是 TCP 的强制部分,两者的区别在于对收到的 ACK 做出反应时增加 cwnd 长度的方式。 慢启动比拥塞避免能更快地增加 cwnd 的长度。快速恢复是推荐部分,对于 TCP 发送方不是必需的。

(1) 慢启动

慢启动 cwnd 初始值设为较小的值,每收到一个接收方的 ACK,就将 cwnd 的值翻倍(指数增长)。

结束慢启动有两种方式,一是当检查到拥塞时,这时将 ssthresh(慢启动阈值) 设为 cwnd/2,然后将 cwnd 置为 1 个 MSS,并重新开启慢启动。 二是到达或超过 ssthresh 值时,再翻倍就超过 cwnd 的值了,所以这时结束慢启动进入拥塞避免模式。

(2) 拥塞避免

拥塞避免模式,每个 RTT 将 cwnd 的值增加一个 MSS。

结束拥塞避免有两种方式,第一种是超时,它和结束慢启动的方式一相同。第二种是收到 3 个冗余 ACK 时,将 ssthresh 置为 cwnd/2, 然后 cwnd = ssthresh + 3 * MSS, 接下来进入快速恢复模式。

(3) 快速恢复

在快速恢复阶段,每收到重复的ACK,则cwnd加1;收到非重复ACK时,置cwnd = ssthresh, 转入拥塞避免阶段;如果发生超时重传,则置ssthresh为当前cwnd的一半,cwnd = 1,重新进入慢启动阶段。

快速恢复阶段退出条件:收到非重复ACK。

step1:
        if ( dupacks >= 3 ) {
                ssthresh = max( 2 , cwnd / 2 ) ;
                cwnd = ssthresh + 3 * SMSS ;
        }
 
step2:重传丢失的分组
 
step3:此后每收到一个重复的ACK确认时,cwnd++
 
step4:当收到对新发送数据的ACK确认时,cwnd = ssthresh,这个ACK能够对那些在
丢失的分组之后,第一个重复ACK之前发送的所有包进行确认。

11. 发送窗口

发送窗口里面的序号表示允许发送的序号,只有收到接收方的确认报文段,才能往前移动(继续发送序号大的报文)。

12. 套接字

套接字 socket = (IP 地址:端口号)

TCP 连接 = { (ip1:port1), (ip2:port2) }

13. 延迟确认

TCP 发送后需要收到 TCP 接收方发回的 ACK 确认报文才能确认这个报文段被对方收到。如果在规定时间内没有收到确认信息就会重传刚才发送的报文段。 由于确认报文很小,所以 TCP 允许在发往相同方向的输出数据分组中对其进行“捎带”,这样可以更有效地利用网络。

为了增加确认报文找到同向传输数据分组的可能性,很多 TCP 栈都实现了一种“延迟确认”算法。延迟确认算法会在一个特定的窗口时间(通常为 100 ~ 200 毫秒)内将确认信息存放在缓冲区中,以寻找能够捎带它的输出数据分组。如果在那个时间段内没有输出数据分组,就将确认信息放在单独的分组中发送。

但是 HTTP 具有双峰特征的请求-应答行为降低了捎带信息的可能性。当希望有相反方向回传分组的时候,偏偏没有那么多。 通常,延迟确认算法会引入相当大的时延。根据操作系统的不同,我们可以调整或禁止延迟确认算法。

14. TIME_WAIT 累积

当 TCP 关闭连接时,会在内存中维护一个小的控制块,用来记录最近所关闭连接的 IP 地址和端口号。这类信息只会维持一小段时间,通常是最大分段使用期的两倍(称为 2MSL,通常为 2 分钟)左右,以确保在这段时间内不会创建具有相同地址和端口号的新连接。

有些操作系统将 2MSL 设置为一个较小的值,但超过此值时要特别小心。如果来自之前连接的复制分组插入了具有相同连接值的新 TCP 流,会破坏 TCP 的数据。

谈谈 TCP 的 TIME_WAIT