TCP 连接建立

<aside> ❓

为什么要建立连接?

TCP 的连接建立要解决以下三个问题:

  1. 使 TCP 双方能够确知对方的存在;
  2. 使 TCP 双方能够协商一些参数(如最大窗口值、是否使用窗口扩大选项和时间戳选项以及服务质量等);
  3. 使 TCP 双方能够对运输实体资源(如缓存大小、连接表中的项目等)进行分配。 </aside>

TCP 是面向连接的协议,在使用 TCP 之前需要先建立连接,而建立连接是通过三次握手来进行的。

image.png

  1. 连接前,服务端端口处于 LISTEN 状态

  2. 客户端会随机初始化序号(client_isn),将此序号置于 TCP 首部的「序号」字段中,同时把 SYN 标志位置为 1,表示 SYN 报文。接着把第一个 SYN 报文发送给服务端,表示向服务端发起连接,该报文不包含应用层数据,之后客户端处于 SYN-SENT 状态。

    format,png-20230309230500953.webp

  3. 服务端收到客户端的 SYN 报文后,首先服务端也随机初始化自己的序号(server_isn),将此序号填入 TCP 首部的「序号」字段中,其次把 TCP 首部的「确认应答号」字段填入 client_isn + 1, 接着把 SYN 和 ACK 标志位置为 1。最后把该报文发给客户端,该报文也不包含应用层数据,之后服务端处于 SYN-RCVD 状态。

    format,png-20230309230504118.webp

  4. 客户端收到服务端报文后,还要向服务端回应最后一个应答报文,首先该应答报文 TCP 首部 ACK 标志位置为 1 ,其次「确认应答号」字段填入 server_isn + 1 ,最后把报文发送给服务端,这次报文可以携带客户到服务端的数据,之后客户端处于 ESTABLISHED 状态。

    format,png-20230309230508297.webp

  5. 服务端收到客户端的应答报文后,也进入 ESTABLISHED 状态。

从上面的过程可以发现第三次握手是可以携带数据的,前两次握手是不可以携带数据的,这也是面试常问的题。

一旦完成三次握手,双方都处于 ESTABLISHED 状态,此时连接就已建立完成,客户端和服务端就可以相互发送数据了。

为什么需要三次握手?

  1. 避免旧的重复连接初始化造成混乱:

    format,png-20230309230525514.webp

    在两次握手的情况下,服务端在收到 SYN 报文后,就进入 ESTABLISHED 状态,意味着这时可以给对方发送数据,但是客户端此时还没有进入 ESTABLISHED 状态,假设这次是历史连接,客户端判断到此次连接为历史连接,那么就会回 RST 报文来断开连接,而服务端在第一次握手的时候就进入 ESTABLISHED 状态,所以它可以发送数据的,但是它并不知道这个是历史连接,它只有在收到 RST 报文后,才会断开连接。

    https://cdn.xiaolincoding.com//mysql/other/fe898053d2e93abac950b1637645943f.png

    可以看到,如果采用两次握手建立 TCP 连接的场景下,服务端在向客户端发送数据前,并没有阻止掉历史连接,导致服务端建立了一个历史连接,又白白发送了数据,妥妥地浪费了服务端的资源。

    <aside> 💡

    也可以使用该图简化说明

    image.png

    </aside>

  2. 同步双方初始序列号

    序列号在 TCP 中有这关键的作用,有了序列号,可以去除重复数据、按序接收数据,同时还能知道发出去的数据包中哪些是已经被对方收到的(通过 ACK 报文中的序列号知道)

    可见,序列号在 TCP 连接中占据着非常重要的作用,所以当客户端发送携带「初始序列号」的 SYN 报文的时候,需要服务端回一个 ACK 应答报文,表示客户端的 SYN 报文已被服务端成功接收,那当服务端发送「初始序列号」给客户端的时候,依然也要得到客户端的应答回应,这样一来一回,才能确保双方的初始序列号能被可靠的同步。

    format,png-20230309230639121.webp

    四次握手其实也能够可靠的同步双方的初始化序号,但由于第二步和第三步可以优化成一步,所以就成了「三次握手」。

    而两次握手只保证了一方的初始序列号能被对方成功接收,没办法保证双方的初始序列号都能被确认接收。

TCP 连接断开

<aside> 💡 客户端和服务端都可以断开连接,下面以客户端断开连接为例

</aside>

image.png