说说TCP的三次握手和四次挥手

一、传输控制协议TCP简介

1.1 简介

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

TCP是一种面向连接(连接导向)的、可靠的基于字节流的传输层通信协议。TCP将用户数据打包成报文段,它发送后启动一个定时器,另一端收到的数据进行确认、对失序的数据重新排序、丢弃重复数据。

TCP把连接作为最基本的对象,每一条TCP连接都有两个端点,这种端点我们叫作套接字(socket),将端口号拼接到IP地址即构成了套接字,例如 192.1.1.6:50030

1.2 特点

  • 特性一:可靠的连接与字节流传输 TCP是一种传输层通信协议,它以面向连接的方式运作,确保数据传输的可靠性。它基于字节流,这意味着数据被看作是连续的字节序列,从而保证数据在传输过程中的完整性和顺序性,让通信两端能稳定、有序地进行数据交互。

  • 特性二:数据流的分割与传输 TCP承担着将应用层传来的数据流精准分割成一个个独立的报文段的任务。完成分割后,这些报文段会被发送到目标节点的TCP层。在这个过程中,TCP确保每个报文段都能准确无误地抵达目标,为后续的数据重组和交付提供保障。

  • 特性三:有序传输与重传机制 每个TCP数据包都被赋予了唯一的序列号,接收方在成功接收到数据包后,会立即发送ACK确认信息,以此告知发送方数据已安全抵达。一旦发送方在规定的时间内未收到ACK确认,就会启动重传机制,重新发送该数据包,直到收到确认信息,确保数据不会丢失,保障数据传输的可靠性。

  • 特性四:数据校验保障准确性 为了确保数据在传输过程中不出现错误,TCP运用校验和技术。在数据发送前,TCP会根据数据包内容计算出一个校验和值,并将其附加在数据包中。接收方收到数据包后,会按照相同的算法重新计算校验和,并与接收到的校验和值进行比对。若两者不一致,说明数据在传输过程中出现了错误,接收方会要求发送方重传数据,以此保证数据的正确性。

二、TCP报文头

TCP报文头

1、源端口(Source Port)/ 目的端口(Destination Port):他们各占2个字节,标示该段报文来自哪里(源端口)以及要传给哪个上层协议或应用程序(目的端口)。进行tcp通信时,一般client是通过系统自动选择的临时端口号,而服务器一般是使用知名服务端口号或者自己指定的端口号 (比如DNS协议对应端口53,HTTP协议对应80)

2、序号(Sequence Number):占据四个字节,TCP是面向字节流的,TCP连接中传送的字节流中的每个字节都按顺序编号,例如如一段报文的序号字段值是 107,而携带的数据共有 100个字段,如果有下一个报文过来,那么序号就从 207(100+107) 开始,整个要传送的字节流的起始序号必须要在连接建立时设置。首部中的序号字段值指的是本报文段所发送的数据的第一个字节的序号

3、确认序号(Acknowledgment Number):4个字节,是期望收到对方下一个报文段的第一个数据字节的序号,若确认号=N,则表明:到序号N-1为止的所有数据都已正确收到,例如:B收到A发送过来的报文,其序列号字段是 301,而数据长度是 200字节,这表明了B正确的收到了A到序号 500(301+200-1) 为止的数据,因此B希望收到A的下一个数据序号是 501,于是B在发送给A的确认报文段中,会把ACK确认号设置为 501

4、数据偏移(Offset):4个字节。指出TCP报文段的数据起始处距离报文段的起始处有多远,这个字段实际上是指出TCP报文段的首部长度。由于首部中还有长度不确定的选项字段,因此数据偏移字段是必要的。单位是32位字,也就是4字节,4位二进制最大表示15,所以数据偏移也就是TCP首部最大60字节

5、保留(Reserved):6个字节。保留域

6、TCP Flags:控制位,由八个标志位组成,每个标志位表示控制的功能,我们主要来介绍TCP Flags中常用的六个,

  • URG(紧急指针标志):当 URG=1 时,表明紧急指针字段有效。它告诉系统此报文段中有紧急数据,应尽快传送(相当于高优先级的数据),而不要按原来的排队顺序来传送。例如,已经发送了很长的一个程序在主机上运行。但后来发现了一些问题,需要取消该程序的运行。因此用户从键盘发出中断命令。如果不使用紧急数据,那么这两个字符将存储在接收TCP的缓存末尾。只有在所有的数据被处理完毕后这两个字符才被交付接收方的应用进程。这样做就浪费了许多时间

  • ACK(确认序号标志):当 ACK=1 时确认号字段有效。当 ACK=0 时,确认号无效。TCP规定,在连接建立后所有的传送的报文段都必须把ACK置1

  • PSH(push标志):当两个应用进程进行交互式的通信时,有时在一端的应用进程希望在键入一个命令后立即就能收到对方的响应。在这种情况下,TCP就可以使用推送操作。这时,发送方TCP把PSH置1,并立即创建一个报文段发送出去。接收方TCP收到PSH=1的报文段,就尽快地交付接收应用进程,而不再等到整个缓存都填满了后向上交付

  • RST(重置连接标志):TCP连接中出现严重差错(如由于主机崩溃或其他原因),必须释放连接,然后再重新建立运输连接,可以用来拒绝一个非法的报文段或拒绝打开一个连接

  • SYN(同步序号,用于建立连接过程):在连接建立时用来同步序号。当 SYN=1而ACK=0 时,表明这是一个连接请求报文段。对方若同意建立连接,则应在相应的报文段中使用 SYN=1和ACK=1。因此,SYN置为1就表示这是一个连接请求或连接接受保温。

  • FIN(finish标志,用于释放连接):当 FIN=1 时,表明此报文段的发送方的数据已发送完毕,并要求释放运输连接

7、窗口(Window)是TCP流量控制的一个手段。这里说的窗口,指的是接收通告窗口(Receiver Window,RWND)。它告诉对方本端的TCP接收缓冲区还能容纳多少字节的数据,这样就可以控制发送数据的速度

8、检验和(Checksum):检验范围包括首部和数据两部分,由发送端填充,接收端对TCP报文段执行CRC算法以检验TCP报文段在传输过程中是否损坏。这也是TCP可靠传输的一个重要保障

9、紧急指针(Urgent Pointer):紧急指针仅在URG=1时才有意义,它指出本报文段中的紧急数据的字节数(紧急数据结束后就是普通数据)。因此,紧急指针指出了紧急数据的末尾在报文段中的位置。当所有紧急数据都处理完时,TCP就告诉应用程序恢复到正常操作。值得注意的是,即使窗口为零时也可发送紧急数据。

10、TCP可选项(TCP Options):长度可变,最长可达40字节。当没有使用“选项”时,TCP的首部长度是20字节。

三、TCP的三次握手

所谓三次握手(Three-Way Handshake)即建立TCP连接,就是指建立一个TCP连接时,需要客户端和服务端总共发送3个包以确认连接的建立。在socket编程中,这一过程由客户端执行connect来触发,整个流程如下图所示:

TCP的三次握手

在TCP/IP协议中,TCP协议提供可靠的连接服务,采用三次握手建立一个连接。

第一次握手: 建立连接时,客户端发送 SYN包(syn=j)到服务器,并进入SYN_SEND 状态,等待服务器确认,SYN:同步序列编号(Synchronize Sequence Numbers)。

第二次握手: 服务器收到 SYN 包,必须确认客户的 SYN(ack=j+1),同时自己也发送一个 SYN包(syn=k),即SYN+ACK包,此时服务器进入 SYN_RECV 状态;

第三次握手: 客户端收到服务器的SYN + ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED(TCP连接成功)状态,完成三次握手。

3.1 为什么需要三次握手才能建立连接

  • 序列号初始值的初始化与可靠数据传输:为实现可靠的数据传输,TCP协议要求通信双方都必须维护一个序列号。这个序列号用于标识发送出去的数据包,进而明确哪些数据包已被对方成功接收。TCP协议通过初始化Sequence Number(序列号)的初始值,确保数据传输的有序性与准确性。而三次握手这一过程,正是通信双方相互告知对方序列号起始值,并确认对方已收到该起始值的关键步骤。

  • 两次握手的局限性:若仅采用两次握手,在连接过程中,至多只有连接发起方所选择的起始序列号能够得到确认,而另一方所选择的序列号则无法获得确认。这将导致在数据传输过程中,无法保证双方都能准确知晓彼此数据包的发送与接收情况,从而可能引发数据丢失、乱序等问题,无法实现可靠的数据传输。

3.2 首次握手的隐患——SYN超时

一、问题起因分析:

  1. 当服务器成功接收到客户端发送的SYN请求后,会随即回复一个包含SYN和ACK的响应包。然而,在某些情况下,服务器发出的这个回复包并未收到来自客户端的ACK确认。
  2. 一旦出现上述情况,服务器并不会立即放弃连接尝试,而是会不断进行重试,直至达到超时时间。在Linux系统中,默认会等待63秒才会断开连接。具体的重试机制为:除了第一次尝试,后续会重复进行5次重试。重试的时间间隔从1秒开始,每次重试的等待时间都翻倍递增,即按照1秒、2秒、4秒、8秒、16秒、32秒这样的时间序列进行重试,这些时间累加起来恰好为63秒 。

二、针对SYN Flood的防护措施:

  1. SYN队列满后,通过tcp_syncookies参数会发SYN cookie【源端口+目标端口+时间戳组成】
  2. 若为正常连接则Client会回发SYN Cookie,直接建立连接;

3.3 保活机制:

当我们建立连接后,Client出现故障怎么办?

  1. 向对方发送保活探测报文,如果未收到相应则继续发送;
  2. 尝试次数达到保活探测数仍未收到相应则中断连接;

四、TCP的四次挥手

所谓四次挥手(Four-Way Wavehand)即终止TCP连接,就是指断开一个TCP连接时,需要客户端和服务端总共发送4个包以确认连接的断开。在socket编程中,这一过程由客户端或服务端任一方执行close来触发,整个流程如下图所示:

TCP的四次挥手

TCP连接具有全双工特性,这意味着数据能够在两个方向上同时进行传输 。基于此特性,在关闭连接时,每个数据传输方向都需要单独进行关闭操作。当通信的一方完成了数据发送任务,便会向对方发送一个FIN(结束标志)报文,以此来终止该方向的数据传输连接。当接收方收到FIN报文时,仅仅表明在这个方向上不会再有新的数据流入,即接收方不会再收到来自发送方的数据,但在该TCP连接的另一个方向上,仍然可以继续发送数据,直到发送方也发送了FIN报文,彻底结束该方向的连接。在关闭连接的过程中,率先发起关闭操作的一方被称为主动关闭方,而另一方则作为被动关闭方响应关闭操作。

  • 第一次挥手: Client发送一个FIN,用来关闭Client到Server的数据传送,Client进入FIN_WAIT_1状态
  • 第二次挥手: Server收到FIN后,发送一个ACK给Client,确认序号为收到序号+1(与SYN相同,一个FIN占用一个序号),Server进入CLOSE_WAIT状态
  • 第三次挥手: Server发送一个FIN,用来关闭Server到Client的数据传送,Server进入LAST_ACK状态
  • 第四次挥手: Client收到FIN后,Client进入 TIME_WAIT状态,接着发送一个ACK给Server,确认序号为收到序号+1,Server进入CLOSED状态,完成四次挥手

一、为什么会有TIME_WAIT状态

当客户端连接接收到服务器发送的结束报文段后,并不会径直进入CLOSED(关闭)状态,而是转换到TIME_WAIT(时间等待)状态。在此状态下,客户端连接需要等待长达2MSL(即两倍的报文段最大生存时间)的时长,才能完成整个关闭流程。这一设计背后,主要存在以下两点关键原因:

  • 确保ACK包成功送达:客户端在收到服务器的结束报文段后,会向服务器发送ACK(确认)包。但网络传输存在不确定性,可能会出现ACK包丢失的情况。设置2MSL的等待时间,能够确保在这段时间内,即便ACK包因各种原因未能及时送达,服务器也有足够的时间重发结束报文段,客户端可以再次响应,从而保证双方都能准确知晓连接关闭的状态。
  • 避免新旧连接混淆:在网络环境中,报文段可能会因为网络延迟等因素而在网络中滞留较长时间。若客户端在收到结束报文段后立即关闭连接,紧接着又重新建立新连接,旧连接中滞留的报文段可能会被新连接接收,从而导致数据混乱。通过在TIME_WAIT状态等待2MSL时间,能够让网络中可能存在的旧连接报文段在这段时间内自然消亡,有效避免新旧连接之间出现混淆,保证新连接的数据传输准确性和稳定性。

二、为什么需要四次握手才能断开连接

TCP连接是一种全双工的网络协议,这意味着在同一时刻,通信的双方既能发送数据,也能接收数据。这种特性极大地提升了数据传输的效率和灵活性。同时,TCP连接允许收发两个方向的连接被独立关闭,这一设计十分必要,比如当client数据发送完毕,向server发送FIN以关闭连接时,server可能还有待发送至client的数据尚未发送完毕。

基于以上特性和需求,关闭TCP连接需要进行四次握手。在每次关闭一个方向上的连接时,都需要经过FIN和ACK两次握手。具体来说,发送方通过发送FIN报文来告知接收方自己不再有数据要发送,请求关闭该方向的连接;接收方收到FIN报文后,会回复一个ACK报文,确认已收到关闭请求。同样,当接收方也没有数据要发送时,也会向发送方发送FIN报文,而发送方则需回复ACK报文进行确认。这样一来,发送方和接收方都要参与FIN报文和ACK报文的交互过程,以此确保两个方向的连接都能安全、有序地关闭 。

三、服务器出现大量CLOSE_WAIT状态的原因

是由于对方关闭socket连接,我方忙于读或写,没有及时关闭连接

在网络通信中,当客户端由于某些原因,率先向服务端发送了FIN信号,这就会致使服务端进入被动关闭状态。此时,如果服务端没有主动关闭socket并向Client发送FIN,那么服务端的Socket就会处于CLOSE_WAIT状态,而非LAST_ACK状态。一般情况下,系统默认的超时时间为7200秒,也就是2小时,这意味着一个CLOSE_WAIT状态会至少维持2个小时。倘若服务端程序因为某种原因,导致系统中出现大量的CLOSE_WAIT状态,这些状态会持续消耗系统资源。在这种情况下,很可能还未等到资源自然释放,系统就会因为资源耗尽而崩溃,严重影响服务的正常运行和稳定性。

解决:

1、检查代码,特别是释放资源的代码

2、检查配置,特别是处理请求的线程配置

Linux的检查代码: netstat -n | awk '/^tcp/{++S[$NF]}END{for(a in S) print a,S[a]}'

在这里插入图片描述

五、总结

到这里TCP的三次握手四次挥手就讲完了,好久都没有写技术文章了,写了一下,感觉还挺好的,上面是博主的认识,有写的不好的地方,大家可以在评论区讨论或者提问,博主看到了会第一时间回复大家,最近也准备开始面试了,先好好准备一下,希望今年可以找到心满意足的工作,也希望今年面试的小伙伴们都有一个好的office,大家一起加油,我是牧小农,我喂自己带盐,大家加油。

原文阅读