TCP 三次握手四次挥手

TCP 的概述

TCP 把连接作为最基本的对象,每一条 TCP 连接都有两个端点,这种端点我们叫作套接字(socket),它的定义为端口号拼接到 IP 地址即构成了套接字,例如,若 IP 地址为192.3.4.16 而端口号为 80,那么得到的套接字为192.3.4.16:80。

常用的熟知端口号

应用程序 FTP TFTP TELNET SMTP DNS HTTP/HTTPS SSH MYSQL
熟知端口 21,20 69 23 25 53 80/443 22 3306
传输层协议 TCP UDP TCP TCP UDP TCP TCP TCP

TCP 的特性

  • TCP 提供一种面向连接的、可靠的字节流服务
  • 在一个 TCP 连接中,仅有两方进行彼此通信。广播和多播不能用于 TCP
  • TCP 使用校验和,确认和重传机制来保证可靠传输
  • TCP 给数据分节进行排序,并使用累积确认保证数据的顺序不变和非重复
  • TCP 使用滑动窗口机制来实现流量控制,通过动态改变窗口的大小进行拥塞控制

注意:TCP 并不能保证数据一定会被对方接收到,因为这是不可能的。TCP 能够做到的是,如果有可能,就把数据递送到接收方,否则就(通过放弃重传并且中断连接这一手段)通知用户。因此准确说 TCP 也不是 100% 可靠的协议,它所能提供的是数据的可靠递送或故障的可靠通知。

TCP 报文首部

  1. 源端口目的端口,各占2个字节,分别写入源端口和目的端口;

  2. 序号,占4个字节,TCP 连接中传送的字节流中的每个字节都按顺序编号。

    例如,一段报文的序号字段值是 301 ,而携带的数据共有100字段,显然下一个报文段(如果还有的话)的数据序号应该从 401 开始;

  3. 确认号,占4个字节,是期望收到对方下一个报文的第一个数据字节的序号。

    例如,B 收到了 A 发送过来的报文,其序列号字段是 501,而数据长度是 200字节,这表明 B 正确的收到了 A 发送的到序号 700 为止的数据。因此,B 期望收到 A 的下一个数据序号是 701,于是 B 在发送给A 的确认报文段中把确认号置为 701;

  4. 数据偏移,占4位,它指出TCP报文的数据距离TCP报文段的起始处有多远;

  5. 保留,占6位,保留今后使用,但目前应都位0;

  6. 紧急URG,当 URG=1,表明紧急指针字段有效。告诉系统此报文段中有紧急数据;

  7. 确认ACK,仅当ACK=1时,确认号字段才有效。TCP 规定,在连接建立后所有报文的传输都必须把 ACK 置1;

  8. 推送PSH,当两个应用进程进行交互式通信时,有时在一端的应用进程希望在键入一个命令后立即就能收到对方的响应,这时候就将 PSH=1

  9. 复位RST,当 RST=1,表明 TCP 连接中出现严重差错,必须释放连接,然后再重新建立连接;

  10. 同步SYN,在连接建立时用来同步序号。

    SYN=1ACK=0,表明是连接请求报文,若同意连接,则响应报文中应该使 SYN=1ACK=1

  11. 终止FIN,用来释放连接。

    FIN=1,表明此报文的发送方的数据已经发送完毕,并且要求释放;

  12. 窗口,占2字节,指的是通知接收方,发送本报文你需要有多大的空间来接受;

  13. 检验和,占2字节,校验首部和数据这两部分;

  14. 紧急指针,占2字节,指出本报文段中的紧急数据的字节数;

  15. 选项,长度可变,定义一些其他的可选的参数。

TCP 三次握手和四次挥手

拟人化场景解释 TCP 三次握手和四次挥手:

三次握手:

Browser:先告诉服务器 “我要开始发起请求了,你那边可以吗?”
Server:服务器回复浏览器 “没问题,你发吧!”
Browser:告诉服务器 “好的,那我开始发了。”

四次挥手:

Browser:先告诉服务器 “我数据都发完了,你可以关闭连接了。”
Server:回复浏览器 “我先看看我这边还有没有数据没传完。”
Server:确认过以后,再次回复浏览器 “我这边数据传输完成了,你可以关闭连接了。”
Browser:告诉服务器 “好的,那我真的关闭了。你不用回复我了。”

Browser 又等了 2MSL,确认确实没有再收到请求了,才会真的关闭TCP连接。

三次握手

最开始的时候客户端和服务器都是处于 CLOSED 状态。主动打开连接的为客户端,被动打开连接的是服务器

所谓三次握手 (Three-way Handshake),是指建立一个 TCP 连接时,需要客户端和服务器总共发送3个包。

3handshakes
三次握手的目的是连接服务器指定端口,建立 TCP 连接,并同步连接双方的序列号和确认号,交换 TCP 窗口大小信息。

  • 第一次握手( SYN=1seq=x ):

    客户端发送一个 TCP 的 SYN 标志位置1的包,指明客户端打算连接的服务器的端口,以及初始序号 X,保存在包头的序列号(Sequence Number)字段里。

    发送完毕后,客户端进入 SYN_SEND 状态。

  • 第二次握手( SYN=1ACK=1seq=yACKnum=x+1 ):

    服务器发回确认包(ACK) 应答。即 SYN 标志位和 ACK 标志位均为 1。服务器端选择自己 ISN 序列号,放到 Seq 域里,同时将确认序号 (Acknowledgement Number) 设置为客户的 ISN 加1,即 X+1。 发送完毕后,服务器端进入 SYN_RCVD 状态。

  • 第三次握手( ACK=1ACKnum=y+1 )

    客户端再次发送确认包(ACK),SYN 标志位为 0,ACK 标志位为1,并且把服务器发来 ACK 的序号字段+1,放在确定字段中发送给对方,并且在数据段放写ISN的+1

    发送完毕后,客户端进入 ESTABLISHED 状态,当服务器端接收到这个包时,也进入 ESTABLISHED 状态,TCP 握手结束。

四次挥手

TCP 的连接的拆除需要发送四个包,因此称为四次挥手(Four-way handshake),也叫做改进的三次握手。

客户端或服务器均可主动发起挥手动作。

4handshakes

  • 第一次挥手( FIN=1seq=x )

    假设客户端想要关闭连接,客户端发送一个 FIN 标志位置为 1 的包,表示自己已经没有数据可以发送了,但是仍然可以接受数据。

    发送完毕后,客户端进入 FIN_WAIT_1 状态。

  • 第二次挥手( ACK=1ACKnum=x+1 )

    服务器端确认客户端的 FIN 包,发送一个确认包,表明自己接受到了客户端关闭连接的请求,但还没有准备好关闭连接。

    发送完毕后,服务器端进入 CLOSE_WAIT 状态,客户端接收到这个确认包之后,进入 FIN_WAIT_2 状态,等待服务器端关闭连接。

  • 第三次挥手( FIN=1seq=y )

    服务器端准备好关闭连接时,向客户端发送结束连接请求,FIN 置为1。

    发送完毕后,服务器端进入 LAST_ACK 状态,等待来自客户端的最后一个 ACK

  • 第四次挥手( ACK=1ACKnum=y+1 )

    客户端接收到来自服务器端的关闭请求,发送一个确认包,并进入 TIME_WAIT 状态,等待可能出现的要求重传的 ACK 包。

    服务器端接收到这个确认包之后,关闭连接,进入 CLOSED 状态。

客户端等待了两个最大段生命周期(2MSL,2 Maximum Segment Lifetime)之后,没有收到服务器端的 ACK ,认为服务器端已经正常关闭连接,于是自己也关闭连接,进入 CLOSED 状态。

三次握手的过程为什么是三次而不是两次、四次?

为什么不是两次?

为什么TCP客户端最后还要发送一次确认呢?

一句话,主要防止已经失效的连接请求报文突然又传送到了服务器,从而产生错误。

如果使用的是两次握手建立连接,假设有这样一种场景,客户端发送了第一个请求连接并且没有丢失,只是因为在网络结点中滞留的时间太长了,由于TCP的客户端迟迟没有收到确认报文,以为服务器没有收到,此时重新向服务器发送这条报文,此后客户端和服务器经过两次握手完成连接,传输数据,然后关闭连接。

此时此前滞留的那一次请求连接,网络通畅了到达了服务器,这个报文本该是失效的,但是,两次握手的机制将会让客户端和服务器再次建立连接,这将导致不必要的错误和资源的浪费。

如果采用的是三次握手,就算是那一次失效的报文传送过来了,服务端接受到了那条失效报文并且回复了确认报文,但是客户端不会再次发出确认。由于服务器收不到确认,就知道客户端并没有请求连接。

为什么不是四次?

可以是四次,但是三次就可以安全建立连接,四次会浪费资源不必要。

为什么是四次挥手而不是三次?

为什么建立连接是三次握手,关闭连接确是四次挥手呢?

建立连接的时候, 服务器在 LISTEN 状态下,收到建立连接请求的 SYN 报文后,把 ACKSYN 放在一个报文里发送给客户端。

而关闭连接时,服务器收到对方的 FIN 报文时,仅仅表示对方不再发送数据了但是还能接收数据,而自己也未必全部数据都发送给对方了,所以己方可以立即关闭,也可以发送一些数据给对方后,再发送 FIN 报文给对方来表示同意现在关闭连接,因此,己方 ACKFIN 一般都会分开发送,从而导致多了一次。

为什么客户端最后还要等待2MSL?

MSL 是 Maximum Segment Lifetime 的英文缩写,可译为“最长报文段寿命”,它是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。TCP 允许不同的实现可以设置不同的 MSL 值。

第一,保证客户端发送的最后一个 ACK 报文能够到达服务器,因为这个 ACK 报文可能丢失,站在服务器的角度看来,我已经发送了FIN+ACK报文请求断开了,客户端还没有给我回应,应该是我发送的请求断开报文它没有收到,于是服务器又会重新发送一次,而客户端就能在这个 2MSL 时间段内收到这个重传的报文,接着给出回应报文,并且会重启2MSL计时器。

第二,防止类似与“三次握手”中提到了的“已经失效的连接请求报文段”出现在本连接中。客户端发送完最后一个确认报文后,在这个 2MSL 时间中,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失。这样新的连接中不会出现旧连接的请求报文。

TCP KeepAlive

TCP 的连接,实际上是一种纯软件层面的概念,在物理层面并没有“连接”这种概念。

TCP 通信双方建立交互的连接,但是并不是一直存在数据交互,有些连接会在数据交互完毕后,主动释放连接,而有些不会。

在长时间无数据交互的时间段内,交互双方都有可能出现掉电、死机、异常重启等各种意外,当这些意外发生之后,这些 TCP 连接并未来得及正常释放,在软件层面上,连接的另一方并不知道对端的情况,它会一直维护这个连接,长时间的积累会导致非常多的半打开连接,造成端系统资源的消耗和浪费,为了解决这个问题,在传输层可以利用 TCP 的 KeepAlive 机制实现来实现。主流的操作系统基本都在内核里支持了这个特性。

TCP KeepAlive 的基本原理是,隔一段时间给连接对端发送一个探测包,如果收到对方回应的 ACK,则认为连接还是存活的,在超过一定重试次数之后还是没有收到对方的回应,则丢弃该 TCP 连接。


本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!