tcp的三次握手和四次挥手

这篇文章我们将详细聊一下 tcp 的三次握手和四次挥手

TCP

传输控制协议(TCP,Transmission Control Protocol)是一种面向连接的可靠的基于字节流的 传输层通信协议。

TCP报文段首部最小长度是20个字节,下图是TCP报文段的首部格式

tcp头部

三次握手

为了方便理解我们先看一个男女生聊天的问题
男孩想找女孩聊天
三次握手情景剧
上面类似 tcp 三次握手,接下来我们详细说明一下

1
2
3
4
SYN: 同步位(只有在连接请求和连接请求确认才为1)
seq: 序号位(随机产生)
ACK: 确认位
ack: 确认号(期待下一次接收的序号,只有ACK=1时,ack才有用)
  • 第一次握手:
    客户端发送连接请求报文段,无应用层数据,此时状态由 LISTEN 变为SYN_SENT

    SYN=1,seq=x(随机)

  • 第二次握手:
    服务器为该 TCP 连接分配缓存和变量,并向客户端返回确认报文段,允许连接,无应用层数据,此时状态由 LISTEN 变为SYN_RECV

    SYN=1,ACK=1,seq=y(随机),ack=x+1

  • 第三次握手:
    客户端收到后,为该 TCP 连接分配缓存和变量,并向服务器返回确认的确认,可携带数据,双方状态ESTABLISHED

    ACK=1,seq=x+1,ack=y+1

三次握手

:由于第二次握手服务器会分配缓存和变量可能产生 SYN 洪宏攻击

四次挥手

咱们还是用男女聊天的问题来看一下 4 次挥手终止回话的情况
四次挥手情景剧
接下来我们详细说明一下

1
2
3
4
FIN: 结束位
seq: 序号位
ACK: 确认位
ack: 确认号(期待下一次接收的序号,只有ACK=1时,ack才有用)

客户端和服务器双方都可以结束回话,本文以客户端先结束为例

  • 第一次挥手
    客户端发送连接释放报文段,停止发送数据,主动关闭 TCP 连接。

    FIN=1,seq=u

  • 第二次挥手
    服务器返回一个确认报文段,客户到服务器这个连接方向就释放了,此时为半关闭状态

    ACK=1,seq=v,ack=u+1

  • 第三次挥手
    服务器发送完数据,就发送连接释放报文段,主动关闭 tcp 连接。

    FIN=1,ACK=1,seq=w,ack=u+1

  • 第四次挥手
    客户端回送一个确认报文段,再等到时间等待计时器设置的 2MSL(最长报文段寿命)后,连接彻底关闭。

    ACK=1,seq=u+1,ack=w+1

四次挥手

相关字段解释

  • FIN_WAIT_1: 这个状态要好好解释一下,其实FIN_WAIT_1FIN_WAIT_2状态的真正含义都是表示等待对方的FIN报文。
    而这两种状态的区别 是:FIN_WAIT_1状态实际上是当SOCKETESTABLISHED状态时,它想主动关闭连接,向对方发送了 FIN 报文,此时该SOCKET即 进入到FIN_WAIT_1状态。
    而当对方回应ACK报文后,则进入到 FIN_WAIT_2状态,当然在实际的正常情况下,无论对方何种情况下,都应该马上回应 ACK 报文,所以FIN_WAIT_1状态一般是比较难见到的,而FIN_WAIT_2状态还有时常常可以用netstat看到。
  • FIN_WAIT_2:上面已经详细解释了这种状态,实际上FIN_WAIT_2状态下的SOCKET,表示半连接,也即有一方要求 close 连接,但另外还告诉对方,我暂时还有点数据需要传送给你,稍后再关闭连接。
    如果 FIN_WAIT_1 状态下,收到了对方同时带 FIN 标志和 ACK 标志的报文时,可以直接进入到 TIME_WAIT 状态,而无须经过 FIN_WAIT_2 状态。
  • CLOSING: 这种状态比较特殊,实际情况中应该是很少见,属于一种比较罕见的例外状态。正常情况下,当你发送 FIN 报文后,按理来说是应该先收到(或同时收到)对方的 ACK 报文,再收到对方的 FIN 报文。但是 CLOSING 状态表示你发送 FIN 报文后,并没有收到对方的 ACK 报文,反而却也收到了对方的 FIN 报文。
    什么情况下会出现此种情况呢?其实细想一下,也不难得出结论:那就是如果双方几乎在同时 close 一个 SOCKET 的话,那么就出现了双方同时发送 FIN 报 文的情况,也即会出现 CLOSING 状态,表示双方都正在关闭 SOCKET 连接
  • CLOSE_WAIT: 这种状态的含义其实是表示在等待关闭
    怎么理解呢?当对方 close 一个 SOCKET 后发送 FIN 报文给自己,你系统毫无疑问地会回应一个 ACK 报文给对方,此时则进入到 CLOSE_WAIT 状态。接下来呢,实际上你真正需要考虑的事情是察看你是否还有数据发送给对方,如果没有的话,那么你也就可以 close 这个 SOCKET,发送 FIN 报文给对方,也即关闭连接。所以你在 CLOSE_WAIT 状态下,需要完成的事情是等待你去关闭连接。
  • LAST_ACK: 这个状态还是比较容易好理解的,它是被动关闭一方在发送 FIN 报文后,最后等待对方的 ACK 报文。当收到 ACK 报文后,也即可以进入到 CLOSED 可用状态了。

相关问题

  1. 为什么建立连接协议是三次握手,而关闭连接却是四次握手呢?
    这是因为服务端的LISTEN状态下的SOCKET当收到SYN报文的建连请求后,它可以把ACKSYN放在一个报文里来发送。
    但关闭连接时,当收到对方的FIN报文通知时,它仅仅表示对方没有数据发送给你了;但未必你所有的数据都全部发送给对方了,所以你可以未必会马上会关闭SOCKET,也即你可能还需要发送一些数据给对方之后,再发送 FIN 报文给对方来表示你同意现在可以关闭连接了,所以它这里的ACK报文和FIN报文多数情况下都是分开发送的。

  2. 为什么 TIME_WAIT 状态还需要等 2MSL 后才能返回到 CLOSED 状态?
    因为虽然双方都同意关闭连接了,而且握手的 4 个报文也都发送完毕,按理可以直接回到 CLOSED 状态(就好比从 SYN_SENT 状态到 ESTABLISH 状态那样),但是我们必须假想网络是不可靠的,你无法保证你(客户端)最后发送的 ACK 报文一定会被对方收到,就是说对方处于 LAST_ACK 状态下的 SOCKET 可能会因为超时未收到 ACK 报文,而重发 FIN 报文,所以这个 TIME_WAIT 状态的作用就是用来重发可能丢失的 ACK 报文。

  3. 关闭 TCP 连接一定需要 4 次挥手吗?
    不一定,4 次挥手关闭TCP连接是最安全的做法。但在有些时候,我们不喜欢 TIME_WAIT 状态(如当MSL数值设置过大导致服务器端有太多TIME_WAIT状态的 TCP 连接,减少这些条目数可以更快地关闭连接,为新连接释放更多资源),这时我们可以通过设置 SOCKET 变量的 SO_LINGER 标志来避免 SOCKETclose()之后进入 TIME_WAIT 状态,这时将通过发送 RST 强制终止 TCP 连接(取代正常的 TCP 四次握手的终止方式)。但这并不是一个很好的主意,TIME_WAIT对于我们来说往往是有利的。

  4. accept() connect() listen()对应三次握手什么阶段

    • 客户端的 Connect() 函数:是一个阻塞函数,通过 TCp 三次握手与服务器建立连接,客户端主动连接服务器,建立连接方式通过 TCP 三次握手通知 Linux 内核自动完成 TCP 三次握手连接,如果连接成功为 0 ,失败返回值-1
      一般的情况下,客户端的 connect 函数 默认是阻塞行为 直到三次握手阶段成功为止。
    • 服务器端的 listen() 函数:不是一个阻塞函数,功能:将套接字套接字对应队列的长度告诉 Linux 内核,他是被动连接的,一直监听来自不同客户端的请求
    • 服务器端的 listen() 函数:只要作用将 socketfd 变成被动的连接监听 socket,其中参数 backlog 作用是设置内核中队列的长度。
    • 客户端的 accept() 函数: 阻塞,从处于 established 状态的队列中取出完成的连接,当队列中没有完成连接时候会形成阻塞,直到取出队列中已完成连接的用户连接为止。
  5. 服务器没有及时调用 accept 函数取走完成连接的队列怎么办?
    服务器的连接队列满掉后,服务器不会对再对建立新连接的 syn 进行应答,所以客户端的 connect 就会返回 ETIMEDOUT。但实际上 Linux 的并不是这样的 当 TCP 连接队列满了之后 Linux 并不会书中所说的拒绝连接,只是会延时连接。

  6. TCP的6个标志位?

    • SYN:同步位
      • 只应用在第一、第二次握手时
    • ACK:确认位
      • 数值为对方的seq+1
    • FIN:结束位
      • 只应用在四次挥手时
    • URG:紧急位
      • 表示紧急指针位置有效,告诉系统此报文段中有紧急数据,应尽快传送,而不要按原来的排队顺序来传送,发送方的TCP就把紧急数据放到本报文段数据的最前面。
      • URG标志位要与首部中的紧急指针字段配合使用,紧急指针指向数据段中的某个字节,(数据从第一个字节到指针所指的字节就是紧急数据)。值得注意的是即使窗口为0时也可以发送紧急数据,紧急数据不进入接收缓冲区直接交给上层进程。
    • PSH:
      • 客户发一个请求给服务器时希望立即能够收到对方的响应,这种情况下,客户应用程序通知TCP使用推送(push)操作,TCP就把PSH置为1,并立即创建一个报文段发送过去,类似的服务器的TCP收到一个设了PSH标志的报文段时就尽快将所有收到的数据立即提交给服务进程,而不在等到整个缓存都填满了再向上交付
    • RST:
      • 这个标志表示连接复位请求。用来复位那些产生错误的连接,也被用来拒绝错误和非法的数据包。
  7. URG和PSH的区别?

    URG:紧急标志位,表示的是此报文段中有紧急数据,将紧急数据排在普通数据的前面;当接受端收到此报文后后必须先处理紧急数据,而后再处理普通数据。

    PSH: 催促标志位,当发送端将PSH置为1时,TCP会立即创建一个报文并发送。接受端收到PSH为1的报文后就立即将接受缓冲区内数据向上交付给应用程序,而不是等待缓冲区满后再交付。

参考地址

  1. 字节跳动 Java 岗一二三面全经过分享
  2. TCP报文段中URG和PSH的区别_菜鸟成长记-CSDN博客_tcp urg
您的支持将鼓励我继续创作!