TCP的三次握手和四次挥手

TCP(Transmission Control Protoco),是一种基于字节流面向连接的传输层协议。数据的传输需要通信双方建立一个连接,TCP协议采用三次握手建立一个连接,采用 4 次挥手来关闭一个连接。每一个TCP连接都有两个端点,叫作套接字(socket),它的定义为IP地址+端口号拼接。

TCP/IP协议概况

  • IPv4: 网际协议版本4(Internet Protocol version 4), 使用32位地址
  • IPv6: 网际协议版本6(Internet Protocol version 6), 使用128位地址,是IPv4替代品,通常把它两者称为”IP“
  • TCP: 传输控制协议(Transmission Control Protocol),TCP是一个面向连接的协议,为用户进程提供可靠全双工字节流, TCP套接字是一种流套接字(stream sockte), 关心确认, 超时, 重传等细节
  • UDP: 用户数据报协议(User Datagram Protocol), UDP是一个无连接协议,UDP套接字是一种数据报套接字(datagram socket)
  • SCTP:流控制传输协议(Stream Control Transmission Protocol),SCTP是一个提供可靠全双工关联的面向连接的协议
  • ICMP:网际控制消息协议(Internet Control Message Protocol),处理在路由器和主机之间流通的错误和控制消息
  • ICMPv6:网际控制消息协议版本6
  • IGMP: 网际组管理协议,用于多播
  • ARP: 地址解析协议(Address Resolution Protocol),把IPv4地址映射成一个硬件地址(如以太网地址)
  • RARP: 反地址解析协议(Reverse..), 将硬件地址映射成IPv4地址
  • BPF: BSD分组过滤器

TCP通信三部曲

  • 建立:三次握手
  • 传输:超时重传、快速重传、流量控制、拥塞控制等
  • 断开:四次挥手

TCP服务模型

一个 TCP 连接由一个 4 元组构成,分别是两个 IP 地址和两个端口号。一个 TCP 连接通常分为三个阶段:启动、数据传输、关闭。

当 TCP 接收到另一端的数据时,它会发送一个确认,但这个确认不会立即发送,一般会延迟一会儿。ACK 是累积的,一个确认字节号 N 的 ACK 表示所有直到 N 的字节(不包括 N)已经成功被接收了。这样的好处是如果一个 ACK 丢失,很可能后续的 ACK 就足以确认前面的报文段了。

一个完整的 TCP 连接是双向和对称的,数据可以在两个方向上平等地流动。给上层应用程序提供一种双工服务。一旦建立了一个连接,这个连接的一个方向上的每个 TCP 报文段都包含了相反方向上的报文段的一个 ACK。

序列号的作用是使得一个 TCP 接收端可丢弃重复的报文段,记录以杂乱次序到达的报文段。因为 TCP 使用 IP 来传输报文段,而 IP 不提供重复消除或者保证次序正确的功能。另一方面,TCP 是一个字节流协议,绝不会以杂乱的次序给上层程序发送数据。因此 TCP 接收端会被迫先保持大序列号的数据不交给应用程序,直到缺失的小序列号的报文段被填满。

TCP报文头部

TCP-Header

  • 源端口和目的端口,各占2个字节;
  • 序号,占4个字节,TCP连接中传送的字节流中的每个字节都按顺序编号;
  • 确认号,占4个字节,是期望收到对方下一个报文的第一个数据字节的序号,即最后被成功接收的数据字节序列号加 1,这个字段只有在 ACK 位被启用的时候才有效;
  • 数据偏移,占4位,它指出TCP报文的数据距离TCP报文段的起始处有多远;
  • 保留,占6位,保留今后使用,但目前应都位0;
  • 紧急URG,当URG=1,表明紧急指针字段有效。告诉系统此报文段中有紧急数据;
  • 确认ACK,仅当ACK=1时,确认号字段才有效。TCP规定,在连接建立后所有报文的传输都必须把ACK置1;
  • 推送PSH,当两个应用进程进行交互式通信时,有时在一端的应用进程希望在键入一个命令后立即就能收到对方的响应,这时候就将PSH=1;
  • 复位RST,当RST=1,表明TCP连接中出现严重差错,必须释放连接,然后再重新建立连接;
  • 同步SYN,在连接建立时用来同步序号。当SYN=1,ACK=0,表明是连接请求报文,若同意连接,则响应报文中应该使SYN=1,ACK=1;
  • 终止FIN,用来释放连接。当FIN=1,表明此报文的发送方的数据已经发送完毕,并且要求释放;
  • 窗口,占2字节,指的是通知接收方,发送本报文你需要有多大的空间来接受;
  • 检验和,占2字节,校验首部和数据这两部分;
  • 紧急指针,占2字节,指出本报文段中的紧急数据的字节数;

TCP状态转换

状 态 描 述
CLOSED 关闭状态,没有连接活动或正在进行
LISTEN 监听状态,服务器正在等待连接进入
SYN_RCVD 收到一个连接请求,尚未确认
SYN_SENT 已经发出连接请求,等待确认
ESTABLISHED 连接建立,正常数据传输状态
FIN_WAIT_1 (主动关闭)已经发送关闭请求,等待确认
FIN_WAIT_2 (主动关闭)收到对方关闭确认,等待对方关闭请求
TIMED_WAIT 完成双向关闭,等待所有分组死掉
CLOSING 双方同时尝试关闭,等待对方确认
CLOSE_WAIT (被动关闭)收到对方关闭请求,已经确认
LAST_ACK (被动关闭)等待最后一个关闭确认,并等待所有分组死掉

tcp-ip-handshark

那么状态转换为什么要经历三次握手和四次挥手呢?

TCP三次握手

  1. 服务器进程先创建传输控制块TCB,时刻准备接受客户进程的连接请求,此时服务器就进入了LISTEN(监听)状态;
  2. 客户端先创建传输控制块TCB,然后向服务器发出连接请求报文,SYN=1,表示请求建立连接,同时选择一个初始序列号 seq=x ,此时,TCP客户端进程进入了 SYN_SENT(同步已发送状态)状态。TCP规定,SYN报文段(SYN=1的报文段)不能携带数据,但需要消耗掉一个序号。
  3. 服务器收到请求报文后,如果同意连接,则发出确认报文。确认报文中应该 ACK=1,SYN=1,确认号是ack=x+1,同时也要为自己初始化一个序列号 seq=y,此时,TCP服务器进程进入了SYN_RCVD(同步收到)状态。这个报文也不能携带数据,但是同样要消耗一个序号。
  4. T客户端收到确认后,还要向服务器给出确认。确认报文的ACK=1,ack=y+1,自己的序列号seq=x+1,此时,TCP连接建立,客户端进入ESTABLISHED(已建立连接)状态。TCP规定,ACK报文段可以携带数据,但是如果不携带数据则不消耗序号。
  5. 当服务器收到客户端的确认后也进入ESTABLISHED状态,三次握手结束,连接建立。

tcp-three-handshake

为什么需要第三次客户端ACK?

三次握手的目的是“为了防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误

例如网络出现抖动导致第一次SYN=1并没有及时到达服务端,从而客户端无法收到服务端的ACK,会触发客户端的重试。如果只有两次握手,第二次重发建立连接后,第一次延迟的请求刚好到达服务端,服务端会再次回包ACK,这样本来无效的请求又会建立新的连接。如果是三次握手,客户端收到服务端ACK并不会再次发出确认,这样就不会有新的连接建立。

TCP四次挥手

以客户端主动关闭为例,服务器端也可以主动关闭,方向与下面相反。

tcp-four-wave

  1. 客户端进程发出连接释放报文,并且停止发送数据。释放数据报文首部,FIN=1,其序列号为seq=u(等于前面已经传送过来的数据的最后一个字节的序号加1),此时,客户端进入FIN_WAIT_1(终止等待1)状态。 TCP规定,FIN报文段即使不携带数据,也要消耗一个序号。
  2. 服务器收到连接释放报文,发出确认报文,ACK=1,ack=u+1,并且带上自己的序列号seq=v,此时,服务端就进入了CLOSE_WAIT(关闭等待)状态。TCP服务器通知高层的应用进程,客户端向服务器的方向就释放了,这时候处于半关闭状态,即客户端已经没有数据要发送了,但是服务器若发送数据,客户端依然要接受。这个状态还要持续一段时间,也就是整个CLOSE_WAIT状态持续的时间。
  3. 客户端收到服务器的确认请求后,此时,客户端就进入FIN_WAIT_2(终止等待2)状态,等待服务器发送连接释放报文(在这之前还需要接受服务器发送的最后的数据)。
  4. 服务器将最后的数据发送完毕后,就向客户端发送连接释放报文,FIN=1,ack=u+1,由于在半关闭状态,服务器很可能又发送了一些数据,假定此时的序列号为seq=w,此时,服务器就进入了LAST_ACK(最后确认)状态,等待客户端的确认。
  5. 客户端收到服务器的连接释放报文后,必须发出确认,ACK=1,ack=w+1,而自己的序列号是seq=u+1,此时,客户端就进入了TIME_WAIT(时间等待)状态。注意此时TCP连接还没有释放,必须经过2*MSL(最长报文段寿命)的时间后,当客户端撤销相应的TCB后,才进入CLOSED状态。
  6. 服务器只要收到了客户端发出的确认,立即进入CLOSED状态,撤销TCB结束TCP连接。

为什么主动关闭连接方最后需要等待2MSL?

MSL(Maximum Segment Lifetime),报文最大生存时间。

  1. 保证客户端发送的最后一个ACK报文能够到达服务器,如果因为网络原因导致服务器没收到,服务器会重新发送一次FIN+ack请求关闭连接,客户端就能收到这个重传的报文再次ACK,并且重启2MSL计时器。
  2. 防止类似与“三次握手”中提到了的“已经失效的连接请求报文段突然又传送到了服务端,因而产生错误”。客户端发送完最后一个确认报文后,在这个2MSL时间中,就可以使本连接持续的时间内所产生的所有报文段都从网络中消失,新的连接中不会出现旧连接的请求报文。
  3. 需要注意的是在TIME_WAIT状态 时两端的端口不能使用,客户端要等到2MSL时间结束才可继续使用。

为什么关闭连接需要四次挥手,比建立连接多一次呢?

建立连接的时候, 服务器在LISTEN状态下,收到建立连接请求的SYN报文后,把ACK和SYN放在一个报文里发送给客户端。
而关闭连接时,ACK和SYN是分开发送的,服务器收到对方的FIN报文时,仅仅表示客户端请求关闭但是还能接收数据,而且服务器的数据可能也没有发送完毕,所以服务器可以选择立即关闭FIN也可以再发送一部分数据然后再发送FIN报文给客户端关闭连接,由于服务器ACK和FIN分开发送,所以多了一次。

如果通信双方同时请求连接或同时请求释放连接?

这种情况虽然发生的可能性极小,但是是确实存在的,TCP也特意设计了相关机制,使得在这种情况下双方仅建立一条连接。

  • 双方同时请求连接的情况下,双方同时发出请求连接报文,并进入SYN_SENT状态;当收到对方的请求连接报文后,会再次发送请求连接报文,确认号为对方的SYN+1,并进入SYN_RCVD状态;当收到对方第二次发出的携带确认号的请求报文之后,会进入ESTABLISHED状态。
  • 双方同时请求释放连接也是同样的,双方同时发出连接释放报文,并进入FIN_WAIT_1状态;在收到对方的报文之后,发送确认报文,并进入CLOSING状态;在收到对方的确认报文后,进入TIME_WAIT状态,等待2MSL之后关闭连接。需要注意的是,这个时候虽然不用再次发送确认报文并确认对方收到,双方仍需等待2MSL之后再关闭连接,是为了防止“已失效的连接请求报文段”的影响。

TIME_WAIT状态

首先TIME_WAIT状态是执行主动关闭的那一端产生的,从上面2MSL中我们了解TIME_WAIT状态有两个存在的理由:

  1. 可靠地实现TCP全双工连接的终止,即最后一次ACK如果丢失,可以重新发送FIN并再次ACK;
  2. 允许老的重复分节在网络中消逝,新的连接中不会出现旧连接的请求报文;

在高并发短连接的TCP服务器上,当服务器处理完请求后立刻按照主动正常关闭连接。这个场景下,会出现大量socket处于TIME_WAIT状态。如果客户端的并发量持续很高,大量端口处于TIME_WAIT状态,无法正常使用,此时部分客户端就会显示连接不上,所以需要引起重视。


参考📚:
TCP的三次握手与四次挥手