TCP

TCP (Transmission Control Protocol) 是一种传输层通信协议。

在因特网协议族(Internet protocol suite)中,TCP层是位于IP层之上,应用层之下的中间层。

不同主机的应用层之间经常需要可靠的、像管道一样的连接,但是IP层不提供这样的流机制,而是提供不可靠的包交换。

TCP 传输过程

一个典型的传输过程,就是一个对数据的逐层封装:

  • 一、应用层向传输层发送用于网间传输的、用8位字节表示的数据流(Stream)。

    用户数据
  • 二、传输层的 TCP 把数据流分割成适当长度(受所处网络的数据链路层 MTU 限制)的报文段(segment)。

    TCP 头 用户数据
  • 三、TCP 把结果传给网络层。网络层的 IP 将报文段封装成数据报(Datagram)。

    IP 头 TCP 头 用户数据
  • 四、IP 层再将数据报交由数据链路层封装成帧(Frame)用于发送。

    帧头 IP 头 TCP 头 用户数据 帧尾
  • 五、通过物理层传输数据。

接受端对数据的操作是个反向操作,称为解封装,逐层移除头信息。

TCP 连接创建(三次握手 three-way handshake)

建立一个 TCP 连接,需要经过三次握手:

  1. SYN:客户端选择一个随机序列号 x ,发送一个 SYN 分组(可包含其他TCP标志、选项)。

  2. SYN ACK:服务端给 x 加 1 ,同时自身选择一个随机序列号 y ,返回响应(可包含其他TCP标志、选项)。

  3. ACK:客户端给 x、y 加 1,发送 ACK 分组。

    客户端可以在发送完该分组后立即发送数据,服务端则需要接收到该分组之后才能开始发送数据

经过这三次握手之后,连接就创建了。

三次握手会带来很大的延迟。因此建立连接是个性能开销很大的操作。

为什么建立链接使用三次握手

有创建链接的两个角色:客户端(下称 C)服务端(下称 S),想象以下交互场景:

第一步,C 发送给 S:“S,你能收到吗?” 做了这一步之后:

  • C 不知 S 能否能接收、发送
  • S 知道 C 能发送,不知 C 能否接收

第二步,S 发送给 C:“C,我收到了,你也能收到吗?” 做了这一步之后:

  • C 知道 S 能接收,也知道 S 能发送
  • S 知道 C 能发送,不知 C 能否接收

第三步,C 发送给 S:“S,我也收到了,我们都能收到和发送,那就开始说正事啦…” 做了这一步之后:

  • C 知道 S 能接收,也知道 S 能发送
  • S 知道 C 能发送,也知道 C 能接收

经过以上三步,C 与 B 都确认了对方可以接收和发送,因此,一个双向的通道得到确认。

三次握手实质是两个方向各一次问询和各一次确认共四次交互,只是中间两次(确认+问询)可以合并优化成三次。

三次握手甚至更多次握手都无法确保链接的绝对可靠,因为最后一个包总有可能丢失…可以参考“两军问题”。在上面的例子中,客户端 C 在第三步发送的包可能是丢失的,所以,单纯靠三次握手是不能保证可靠的,需要其他的机制来实现。

总的来说,三次握手是在尽可能可靠和尽可能高效中取得平衡的折中做法。

TCP 传输数据

在TCP的数据传送状态,很多重要的机制保证了TCP的可靠性和强壮性。

  • 使用序号 对收到的TCP报文段进行排序以及检测重复的数据

  • 使用校验和 TCP用一个校验和函数来检验数据是否有错误,在发送和接收时都要计算校验和。

  • 使用确认和计时器 收端实体对已成功收到的包发回一个相应的确认(ACK), 如果发送端实体在合理的往返时延(RTT)内未收到确认,那么对应的数据包就被假设为已丢失将会被进行重传。

TCP 连接断开(四次握手 four-way handshake)

断开一个 TCP 连接,A、B 两端需要经过四次挥手的过程,初始 A、B 两端都处于 ESTABLISHED 状态,A 准备断开:

  1. FIN: A 端发送 FIN 报文,携带一个序列号 m,停止发送数据,此时 A 转为 FIN_WAIT_1 状态(直至收到 ACK)。
  2. ACK: B 端收到 FIN 之后,会返回 ACK 报文,且把收到的 m + 1 作为 ACK 报文的序列号值,此时 B 转为 CLOSE_WAIT 状态,A 端收到 ACK 后,会进入 FIN_WAIT_2 状态。
  3. FIN: B 端发送 FIN 报文,且指定一个序列号。此时 B 转为 LAST_ACK 的状态。
  4. ACK: A 端收到 FIN 之后,返回一个 ACK 报文,且把收到的序列值 +1。此时转为 TIME_WAIT 状态之后等待 2MSL 后转成 CLOSED 状态。B 端收到 ACK 后也变成 CLOSED 状态。

A 在发送 ACK 之后,TCP 连接并不能马上释放掉,需要继续等待一会(2MSL),以确保 B 能收到自己的 ACK 报文,如果 B 没有在规定时间内收到 A 发送的 ACK 报文,会重新发送 FIN 报文给 A,A 再次收到 FIN 报文之后,可以知道之前的 ACK 报文丢失了,于是再次发送 ACK 报文给 B,一旦 B 收到 ACK 报文之后,就真正关闭连接了,连接将变成 CLOSED 状态。

为什么需要四次挥手

TCP 链接,是个双向的链接,有半关闭(half-close)特性,在连接的一端结束发送数据后,还能接收来另一端的数据。 所以,一次完整的关闭、释放,需要在两端都做一次发送 FIN、接受 ACK 响应共四次的挥手行为。

象相下面交互场景:

首先假设有客户端 C,服务端 S,正处在 ESTABLISHED 状态。如果 C 已经没有新数据需要发送,准备关闭这个连接,则:

第一步,C 告诉 S,我已经发完数据,准备关闭连接啦。
第二步,S 回复 C,我知道了,等我的通知。
第三步,S 告诉给 C,我也发完数据,可以关闭链接啦。
第四步,C 回复 S,好的,我继续等一小会确保你收到我这个回复就关掉。

经过这样的四次挥手后,连接的双方均可以确认连接可以安全关闭。