玩命加载中 . . .

TCP 的运输连接管理


TCP 运输连接的三个阶段

TCP 是面向连接的协议。运输连接是用来传送 TCP 报文的,共有三个阶段,即:

  • 连接建立(“三报文握手”)
  • 数据传送
  • 连接释放(“四报文挥手”)

TCP 连接的建立采用客户–服务器方式。主动发起连接建立的应用进程叫做客户,被动等待连接建立的应用进程叫做服务器

下文将客户简写为 A ,将服务器简写为 B 。

TCP 的连接建立

概念

TCP 建立连接的过程叫做握手,握手需要在 A 和 B 之间交换三个 TCP 报文段。

首先,来认识一下握手挥手中用到的一些标识位和缩写的含义:

  • SYN(Synchronize Sequence Numbers):同步序列编号,表示建立链接
  • FIN:标识位,表示关闭链接
  • ACK(Acknowledgement):标识位,表示响应
  • ack(Acknowledgement Number):确认号码,是期望收到对方下一个报文的第一个数据字节的序号
  • seq(Sequence Number):顺序号码(TCP连接中传送的字节流中的每个字节都按顺序编号)

过程

接下来先看下 TCP 三次握手的流程图:
TCP 三次握手流程图

  • 最初两端的 TCP 进程都处于 CLOSED (关闭)状态。
  • 一开始,B 的 TCP 服务器进程先创建 传输控制块 TCB 【用于存储 TCP 连接中的一些重要信息,如 TCP 连接表、指向发送和接收缓存的指针、当前的发送和接收序号】,准备接受 A 的连接请求。然后服务器进程就处于 LISTEN(收听)状态,等待客户的链接请求。
  • A 的 TCP 客户进程也是首先创建 传输控制块 TCB。然后,在打算建立 TCP 连接时,向 B 发送 TCP 连接请求报文段,并进入 SYN-SENT (同步已发送)状态。
    TCP 连接请求报文段首部中的同步位 SYN = 1(表明这是一个 TCP 连接请求报文段);序号字段 seq = x (作为 TCP 客户进程所选择的初始序号)。【注意:TCP 规定 SYN = 1 的报文段不能携带数据,但要消耗掉一个序号。】
  • B 收到 TCP 连接请求报文段后,如果同意建立连接,则向 A 发送 TCP 连接请求确认报文段,并进入 SYN-RCVD (同步已接受)状态。
    该报文段首部中的同步位 SYN 和确认位 ACK 都设置为 1 (表明这是一个 TCP 连接请求确认报文段);序号字段 seq = y(作为 TCP 服务器进程所选择的初始序号),确认号字段 ack = x + 1 (这是对 TCP 客户进程所选择的初始序号的确认)。【注意:这个报文段也不能携带数据,因为它是 SYN 被设置为 1 的报文段,但同样要消耗掉一个序号】。
  • A 收到 B 的确认后,还要向 B 发送一个普通的 TCP 确认报文段,并进入 ESTABLISHED (连接已建立)状态。
    该报文段首部中的确认位 ACK = 1 (表明这是一个普通的 TCP 确认报文段);序号字段 seq = x + 1 (这是因为 TCP 客户进程发送的第一个 TCP 报文段的序号为 x ,并且不携带数据,因此第二个报文段的序号为 x + 1)。【注意: TCP 规定普通的 TCP 确认报文段可以携带数据,但如果不携带数据,则不消耗序号】。在这种情况下,所发送的下一个数据报文段的序号仍是 seq = x + 1;确认号字段 ack 被设置为 y + 1 (这是对 TCP 服务器进程所选择的初始序号的确认)。
  • TCP 服务器进程收到该确认报文段后也进入 ESTABLISHED (连接已建立)状态。现在,TCP 双方都进入了连接已建立状态,他们可以基于已建立好的 TCP 连接进行可靠的数据传输了。

Q&A

  1. Q:为什么 A 最后还要发送一次确认呢,只有两次握手不行吗?

A:防止已经失效的连接请求报文突然又传送到服务器,从而导致不必要的错误和资源的浪费。

所谓“已失效的连接请求报文段”是这样产生的:

  • 先考虑一种正常情况:A 发出连接请求,但因连接请求报文丢失而未收到确认,于是 A 重新发送一次连接请求。后来收到了确认,建立了连接。数据传输完毕后,就释放了连接。这时 A 共发送了两个连接请求报文段,其中第一个丢失,第二个到达了 B ,没有“已失效的连接请求报文段”。
  • 现假定出现一种异常情况,即 A 发出的第一个连接请求报文段并没有丢失,而是在某些网络结点长时间滞留了,以致延误到连接释放以后的某个时间才到达 B 。本来这是一个早已失效的报文段。但 B 收到此失效的连接请求报文段后,就误以为是 A 又发出一次新的连接请求。于是就向 A 发出确认报文段,同意建立连接。假定不采用报文握手,那么只要 B 发出确认新的连接就建立了。
    而由于现在 A 不并没有发出建立连接的请求,因此不会理睬 B 的确认,也不会向 B 发送数据。但 B 却以为新的运输连接已经建立了,并一直等待 A 发来数据。这样的话, B 的许多资源就这样白白浪费了。
  1. Q:那为什么不是四次握手?五次六次不可以吗?

A:三次握手的目的是确认双方发送和接收的能力,当然,如果要 100 次都可以,但为了解决问题,三次就足够了,再多用处就不大了。

  1. Q:三次握手过程中可以携带数据么?

A1:第三次握手的时候,可以携带。前两次握手不能携带数据。
A2:如果前两次握手能够携带数据,那么一旦有人想攻击服务器,那么他只需要在第一次握手中的 SYN 报文中放大量数据,那么服务器势必会消耗更多的时间和内存空间去处理这些数据,增大了服务器被攻击的风险。
A3:第三次握手的时候,客户端已经处于ESTABLISHED状态,并且已经能够确认服务器的接收、发送能力正常,这个时候相对安全了,可以携带数据。

TCP 的连接释放

先看下四次挥手的流程图,心里有数好一点:
TCP 四次挥手流程图

  • 当TCP 的运输连接管理的第二个阶段-数据传输结束后,通信的双方都可释放连接。
  • 现在 A 和 B 都处于 ESTABLISHED (连接已建立)状态。假设 A 的应用进程通知其主动关闭 TCP 连接,就发送 TCP 连接释放报文段,停止发送数据,并进入 FIN-WAIT-1 (终止等待1)状态。
    TCP 连接释放报文段首部中的终止位 FIN 和确认为 ACK 的值都被设置为 1 (表明这是一个 TCP 连接释放报文段,同时也对之前收到的报文段进行确认);序号 seq = u (它等于 TCP 客户进程之前已传送过的数据的最后一个字节的序号加 1 )【注意:TCP 规定终止位 FIN 等于 1 的报文段即使不携带数据,也要消耗掉一个序号。】;确认号 ack = v (它等于 TCP 客户进程之前已收到的数据的最后一个字节的序号加 1 )。
  • B 收到 TCP 连接释放报文段后,会发送一个普通的 TCP 确认报文段并进入 CLOSE-WAIT(关闭等待) 状态。
    TCP 确认报文段首部中的确认位 ACK = 1 (表明这是一个普通的 TCP 确认报文段);序号 seq = v (它等于 TCP 服务器进程之前已传送过的数据的最后一个字节的序号加 1 ,这也与之前收到的 TCP 连接释放报文段中的确认号匹配);确认号 ack = u + 1 (这是对 TCP 连接释放报文段的确认)。
    收到 A 发来的连接释放报文段后,B 应用进程这时应通知高层应用进程:TCP 客户进程要断开与自己的 TCP 连接。于是,从 A –> B 这个方向的连接就释放了。这时的 TCP 连接属于 half-close (半关闭)状态,也就是 A 已经没有数据要发送了。但如果 B 还有数据要发送,A 仍要接收。也就是说,从 B –> A 这个方向的连接并未关闭,这个状态可能会持续一段时间。
  • A 收到来自 B 的确认后,就进入 FIN-WAIT-2 (终止等待2)状态,等待 B 发出的连接释放报文段。
  • 若 B 已经没有要向 A 发送的数据,其应用进程就通知 TCP 释放连接。这时 B 发出的连接释放报文段必须使 FIN = 1 。现假定 B 的序号为 w (在半关闭状态 B 可能又发送了一些数据)。B 还必须重复上次已发送过的确认号 ack = u + 1 。这时 B 就进入 LAST-ACK (最后确认)状态,等待 A 的确认。
  • A 在收到 B 的连接释放报文段后,必须对此发出确认。在确认报文段中把 ACK = 1 ,确认号 ack = w + 1 ,而自己的序号是 seq = u + 1 (根据 TCP 标准,前面发送过的 FIN 报文段要消耗掉一个序号),然后进入到 TIME-WAIT (时间等待)状态。【注意:现在 TCP 连接还没有释放掉,必须经过时间等待计时器设置的时间 2 MSL(最长报文段寿命,RFC 793 建议设为 2 分钟,现如今 2 分钟可能已经太长了,可以自己修改合适的时间) 后,A 才能进入到 CLOSED 状态,然后才能开始建立下一个新的连接】。当 A 撤销相应的传输控制块 TCB 后,就算结束了这次的 TCP 连接。
  • B 只要收到了 A 发出的确认,立即进入 CLOSED 状态。同样,撤销 TCB 后,就结束了这次的 TCP 连接。
  • 可以看到,B 结束 TCP 连接的时间要比 B 早一些。

QAQ

  1. Q:为什么 A 在 TIME-WAIT 状态必须等待 2MSL 的时间呢?

A:答案是:①保证客户端发送的最后一个 ACK 报文段能够达到服务器;②防止已经失效的关闭连接报文段出现在本连接中。为什么这么说呢,可以来看看:

  • ①由于这个 ACK 报文段有可能丢失,因而使处在 LAST-ACK 状态的 B 收不到对己发送的 FIN + ACK 报文段的确认。
    B 会超时重传这个 FIN + ACK 报文段,而 A 就能在 2MSL 时间内收到这个重传的 FIN + ACK 报文段。
    接着 A 重传一次确认,重新启动 2MSL 计时器。最后,A 和 B 都正常进入到 CLOSED 状态。
    如果 A 在 TIME-WAIT 状态不等待一段时间,那么就无法收到 B 重传的 FIN + ACK 报文段,因而也不会再发送一次确认报文段。这样,B 就无法按照正常步骤进入 CLOSED 状态。
  • ② A 在发送完最后一个 ACK 报文段后,再经过时间 2MSL ,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失。这样就可以使下一个新的连接中不会出现这种旧的连接请求报文段。
  1. Q:为什么是四次挥手而不是三次?

A:因为服务端在接收到FIN, 往往不会立即返回FIN, 必须等到服务端所有的报文都发送完毕了,才能发FIN。因此先发一个ACK表示已经收到客户端的FIN,延迟一段时间才发FIN。这就造成了四次挥手。

  1. Q:如果是三次挥手会有什么问题?

A:等于说服务端将ACK和FIN的发送合并为一次挥手,这个时候长时间的延迟可能会导致客户端误以为FIN没有到达客户端,从而让客户端不断的重发FIN。


文章作者: hcyety
版权声明: 本博客所有文章除特別声明外,均采用 CC BY 4.0 许可协议。转载请注明来源 hcyety !
评论
  目录