TCP

OSI七层模型

img

OSI模型各层的基本作用

  • 应用层: 为应用程序提供网络服务。
  • 表示层:数据格式化,加密,解密
  • 会话层:建立、维护、管理会话连接
  • 传输层:建立、维护、管理端到端连接
  • 网络层:IP寻址和路由选择
  • 数据链路层: 控制网络层与物理层之间的通信
  • 物理层:比特流传输

TCP在网络OSI的七层模型中的第四层 --- Transport 层,IP在第三层 --- Network层,ARP在第二层 --- Data Link层(TCP/IP模型中属于网络层)

第二层的数据我们叫 Frame,在第三层的数据叫 Packet,第四层的数据叫 Segment

我们程序的数据 👉🏻 打到 TCP 的 Segment 👉🏻 打到 IP 的 Packet 👉🏻 打到以太网 Ethernet 的 Frame 中,传到对端后,各个层解析自己的协议,然后把数据交给更高层的协议处理。

TCP 头格式

img

注意

  • TCP包是没有IP地址的,那是 IP 层上的事情。但是有源端口和目标端口。
  • 一个 TCP 连接需要四个元组来表示是同一个连接(src_ipsrc_portdst_ipdst_port)准确说是五元组,还有一个是协议。但因为这里只是说 TCP 协议,所以,这里我们只说四元组。
  • 上图四个非常重要的东西:
    • Sequence Number是包的序列号:用来解决网络包乱序(reordering)问题。
    • Acknowledgement Number就是ACK:用于确认收到,用来解决不丢包的问题
    • Window又叫Advertised-Window,也就是著名的滑动窗口(Sliding Window)
    • TCP Flag,也就是包的类型,主要是用于操控TCP的状态机

关于其它的东西,可以参看下面的图示

img

TCP 状态机

其实,网络上的传输是没有连接的,包括TCP也是一样的。而 TCP 所谓的“连接”,其实只不过是在通讯双方维护一个“连接状态”,让它看上去好像有连接一样。所以,TCP状态变换是非常重要的。

下面是:TCP协议的状态机TCP建连接TCP断连接传数据的对照图。

img

img

常见面试题?

  1. 什么是 TCP 的三次握手和四次挥手?
  2. TCP的三次握手(为什么是三次?)
  3. TCP的四次挥手(为什么是四次?)

面试题

1. 什么是 TCP 的三次握手和四次挥手?(以下是TCP连接的背景知识)

TCP是面向连接的单播协议,在发送数据前,通信双方必须在彼此建立一条连接。所谓的“连接”,其实是客户端和服务器的内存里保存的一份关于对方的信息,如IP地址、端口号等。

TCP可以看成是一种字节流,它会处理 IP 层或以下的层的丢包、重复以及错误问题。在连接的建立过程中,双方需要交换一些连接参数。这些参数可以放在 TCP 头部。

TCP 提供了一个可靠、面向连接、字节流、传输层的服务,采用三次握手建立一个连接;采用四次挥手来关闭一个连接。

一个TCP连接由一个4元组构成,分别是两个IP地址和两个端口号。一个TCP连接通常分为三个阶段:启动、数据传输、退出(关闭)

当TCP接收到另一端的数据时,它会发送一个确认,但这个确认不会立即发送,一般会延迟一会。

ACK是累积的,一个确认字节号N的ACK 表示所有直到 N 的字节(不包括N)已经成功被接收了。这样的好处是如果一个ACK丢失,很可能后续的 ACK 就足以确认前面的报文段了。

一个完整的 TCP 连接是双向和对称的,数据可以在两个方向上平等地流动。给上层应用程序提供一种双工服务。一旦建立了一个连接,这个连接的一个方向上的每个TCP报文段都包含了相反方向上的每个TCP报文段都包含了相反方向上的报文段的一个ACK

序列号的作用是使得一个 TCP 接收端可丢弃重复的报文段,记录以杂乱次序到达的报文段。因为 TCP 使用 IP 来传输报文,而 IP 不提供重复消除或者保证次序正确的功能。

另一个方面,TCP是一个字节流协议,绝不会以杂乱的次序给上层程序发送数据。因此 TCP 接收端会被迫先保持大序列号的数据不交给应用程序,直到缺失的小序列号的报文段被填满。

2. TCP的三次握手(为什么是三次?)

img

三次握手的过程

假设 A 为客户端,B为服务端。

首先 B 处于 LISTEN(监听)状态,等待客户端的连接请求。

  • A 向 B 发送连接请求报文,SYN = 1, ACK = 0, 选择一个初始的序号 x.
  • B收到连接请求报文,如果同意建立连接,则向 A 发送连接确认报文,SYN=1,ACK=1,确认号为 x+1,同时也选择一个初始的序号y
  • A 收到 B 的连接确认报文后,还要向 B 发出确认,确认号为 y+1,序号为 x+1

B收到A的确认后,建立连接。

为什么要三次呢?

  1. 第三次握手是为了防止失效的连接请求到达服务器,让服务器错误打开连接。
  2. 通俗易懂的理解:
    • 第一次握手:客服端发送网络包,服务端收到了。这样服务端就能得出结论:客户端的发送能力、服务端的接收能力是正常的。
    • 第二次握手:服务端发包,客户端收到了。
      • 这样客户端就能得出结论:服务端的接收和发送能力是正常的,客户端的发送和接收能力正常的
    • 第三次握手:客户端发包,服务端收到了
      • 服务端能够得出结论:客户端的接收、发送能力,服务端的发送、接收能力是正常的。

从上面的过程可以看到,最少是需要三次握手过程的。两次达不到让双方都得出自己、对方的接收、发送能力都正常的结论。

3. TCP的四次挥手(为什么是四次?)

img

四次挥手的过程

  • 客户端发送一个 FIN 段,并包含一个希望接受者看到的自己当前的序列号k,同时还包含一个 ACK 表示确认对方最近一次发过来的数据。
  • 服务端将 k 值加 1 作为ACK序号值,表明收到了上一个包。这时上层的应用程序会被告知另一端发起了关闭操作,通常这将引起应用程序发起自己的关闭操作。
  • 服务端发起自己的 FIN 段,ACK = K + 1, Seq = L
  • 客户端确认。进入 TIME-WAIT 状态,等待 2MSL(最大报文存活时间)后释放连接。ACK = L + 1

为什么要四次挥手呢?

  • TCP 连接是双向传输的对等的模式,就是说双方都可以同时向对方发送或接收数据。当有一方要关闭连接时,会发送指令告知对方,我要关闭连接了。

  • 这时对方会回一个 ACK,此时一个方向的连接关闭。但是另一个方向仍然可以继续传输数据。也就是说,服务端收到客户端的 FIN 标志,知道客户端想要断开这次连接了,但是,我服务端,我还想发数据呢?我等到发完所有的数据后,会发送一个 FIN 段来关闭此方向上的连接。接收方发送 ACK 确认关闭连接。

  • 客户端发送了 FIN 连接释放报文之后,服务器收到了这个报文,就进入了 CLOSE-WAIT 状态。这个状态是为了让服务器端发送还未传送完毕的数据,传送完毕之后,服务器会发送 FIN 连接释放报文。

  • 因为服务端在 LISTEN 状态下,收到建立连接请求的 SYN 报文后,把 ACK 和 SYN 放在一个报文里发送给客户端。而关闭连接时,当收到对方的 FIN 报文时,仅仅表示对方不再发送数据了,但是还能接收数据,己方是否现在关闭发送数据通道,需要上层应用来决定,因此,己方 ACK 和 FIN 一般都会分开发。

  • TIME_WAIT

    • 客户端接收到服务器端的FIN报文后进入此状态,此时并不是直接进入 CLOSED 状态,还需要等待一个时间计时器设置的时间 2MSL。这么做有两个理由:
      • 确保最后一个确认报文能够到达。如果 B 没有收到 A 发送来的确认报文,那么就会重新发送释放请求的报文,A 等待一段时间是为了让本连接持续时间内锁产生的所有报文都从网络中消失,使得下一个新的连接不会出现就的连接请求报文。

TCP 重传机制

为什么 TCP 要有重传机制?

TCP 要保证所有的数据包都可以到达,所以,必须要有重传机制。

重传机制包括四种

  1. 超时重传
  2. 快速重传
  3. SACK方法
  4. D-SACK方法

重传的场景

Note: 接收端给发送端的 Ack 确认只会确认最后一个连续的包。

比如,发送端发了1,2,3,4,5一共五份数据,接收端收到了1,2,于是回ack3,然后收到了4(注意此时3没收到),此时的TCP会怎么办?我们要知道,因为正如前面所说的,SeqNum和Ack是以字节数为单位,所以ack的时候,不能跳着确认,只能确认最大的连续收到的包,不然,发送端就以为之前的都收到了。

超时重传机制

一种是不回ack,死等3,当发送方发现收不到3的ack超时后,会重传3。一旦接收方收到3后,会ack回4 --- 意味着3和4都收到了。而发送方也完全不知道发生了什么事情,应为没有收到Ack,所以,发送方可能会悲观地认为也丢了,所以有可能也会导致4和5的重传。

对此有两种选择:

  • 一种是仅仅重传timeout的包,也就是第三份数据。
  • 另一种是重传timeout后所有的数据,也就是第3,4,5这三份数据

这两种方式有好也有不好。第一种会节省带宽,但是慢,第二种会快一点,但是会浪费带宽,也可能会有无用功。但总体来说都不好。因为都在等timeout,timeout可能会很长。

快速重传机制

TCP引入了一种叫 Fast Retransmit 的算法,不以时间驱动,而以数据驱动重传。也就是说,如果,包没有连续到达,就ack最后那个可能被丢了的包,如果发送方连续收到3次相同的ack,就重传。Fast Retransmit 的好处是不用等 timeout 了再重传。

比如:如果发送方发出了1,2,3,4,5份数据,第一份先到送了,于是就ack回2,结果2因为某些原因没收到,3到达了,于是还是ack回2,后面的4和5都到了,但是还是ack回2,因为2还是没有收到,于是发送端收到了三个ack=2的确认,知道了2还没有到,于是就马上重转2。然后,接收端收到了2,此时因为3,4,5都收到了,于是ack回6。示意图如下:

img

Fast Retransmit只解决了一个问题,就是timeout的问题,它依然面临一个艰难的选择,就是,是重传之前的一个还是重传所有的问题。对于上面的示例来说,是重传#2呢还是重传#2,#3,#4,#5呢?因为发送端并不清楚这连续的3个ack(2)是谁传回来的?也许发送端发了20份数据,是#6,#10,#20传来的呢。这样,发送端很有可能要重传从2到20的这堆数据(这就是某些TCP的实际的实现)。可见,这是一把双刃剑。

SACK 方法

另外一种更好的方式叫:Selective Acknowledgment(SACK)(参看RFC 2018),这种方式需要在 TCP 头里加一个 SACK 的东西,ACK还是Fast Retransmit的ACK,SACK则是汇报收到的数据碎版。

img

这样,在发送端就可以根据回传的SACK知道哪些数据到了,哪些没有到。于是就优化了Fast Retransmit的算法。

这里还需要注意一个问题——接收方Reneging,所谓Reneging的意思就是接收方有权把已经报给发送端SACK里的数据给丢了。这样干是不被鼓励的,因为这个事会把问题复杂化了,但是,接收方这么做可能会有些极端情况,比如要把内存给别的更重要的东西。所以,发送方也不能完全依赖SACK,还是要依赖ACK,并维护Time-Out,如果后续的ACK没有增长,那么还是要把SACK的东西重传,另外,接收端这边永远不能把SACK的包标记为Ack。

注意:SACK会消费发送方的资源,试想,如果一个攻击者给数据发送方发一堆SACK的选项,这会导致发送方开始要重传甚至遍历已经发出的数据,这会消耗很多发送端的资源。详细的东西请参看《TCP SACK的性能权衡

Duplicate SACK - 重复收到数据的问题

Duplicate SACK 又称 D-SACK,其主要使用了SACK来告诉发送方有哪些数据被重复接收了。RFC-2883里有详细描述和示例。

TCP 的 RTT 算法

经典算法

Karn / Partridge 算法

Jacobson / Karels 算法

TCP 滑动窗口

TCP必须要解决的可靠传输以及包乱序(reordering)的问题,所以,TCP必需要知道网络实际的数据处理带宽或是数据处理速度,这样才不会引起网络拥塞,导致丢包。

TCP引入了一些技术和设计来做网络流控,Sliding Window是其中一个技术。前面我们说过,TCP头里有一个字段叫Window,又叫Advertised-Window,这个字段是接收端告诉发送端自己还有多少缓冲区可以接收数据,于是发送端就可以根据这个接收端的处理能力来发送数据,而不会导致接收端处理不过来。

参考阅读