TCP/IP 三次握手&四次挥手详解,以及异常状态分析

1.TCP/IP 三次握手
TCP/IP 三次握手过程

主要依靠IP协议报文中的 SYN ACK 两个标识位,SYN 表示是请求连接的报文,ACK 表示确认报文的请求

过程:

  1. 客户端处于 CLOSE 状态,服务器处于 LISTEN 状态,客户端向服务器发送请求连接报文,SYN=1 seq=x,发送成功后,客户端状态修改为 SYN_SEND 状态
  2. 服务器接收到客户端的请求报文,就发送 ACK 确认报文,表示服务器接收到了这个请求,ACK=1 syn=y ack=x+1 服务器将状态为 SYN_RCVD 状态
  3. 客户端收到服务器发送的确认报文后,到这里,客户端知道服务器接收和发送功能都没有问题,但是服务器只知道客户端有发送功能,所以有第三次握手,
  4. 客户端发送确认报文,ACK=1 ack=y+1 seq=x+1 ,这里seq=x+1 可以用来标识第一次连接的握手,到这里客户端处于 ESTABLISHED 状态
  5. 服务器收到客户端的确认报文后,知道了客户端收发能力没问题,将状态修改为 ESTABLISHED 状态,连接建立成功,可以开始发送消息了
为什么是三次握手?
  1. 确认客户端与服务器之间都有收发功能。
  2. 如客户端发出连接请求,但因连接请求报文丢失而未收到确认,于是客户端再重传一次连接请求。后来收到了确认,建立了连接。数据传输完毕后,就释放了连接,
    客户端共发出了两个连接请求报文段,其中第一个丢失,第二个到达了服务端,但是第一个丢失的报文段只是在某些网络结点长时间滞留了,延误到连接释放以后的某个时间才到达服务端,
    此时服务端误认为客户端又发出一次新的连接请求,于是就向客户端发出确认报文段,同意建立连接,不采用三次握手,只要服务端发出确认,就建立新的连接了,
    此时客户端忽略服务端发来的确认,也不发送数据,则服务端一致等待客户端发送数据,浪费资源。
半连接队列和全连接队列
  1. 对于状态为 SYM_RCVD 的状态,服务器会将这个连接加入到半连接队列中,等待客户端发送数据,当三次握手完成,服务器会将这个连接加入到全连接队列中
  2. 全连接队列,顾名思义就是指完成三次握手的连接,如果队列满了就会出现丢包的情况
ISN(initial sequence number)是否是固定的吗?

ISN 是一个随机数(本质是32位的一个计数器),用来标识连接的起始序号,ISN 会随连接的建立而改变,ISN 的值是随机的,也不完全随机,和系统策略有关,所以不会出现 ISN 冲突的情况。

  1. 这样选择序号的目的在于防止在网络中被延迟的分组在以后又被传送,而导致某个连接的一方对它做错误的解释。
  2. ISN不固定也可以防止攻击者猜到后续的确认号,提升连接的安全性
三次握手过程能携带数据?

第一次和第二次都不能携带数据

  1. 第一次不能携带数据的原因主要是防止 SYN 攻击,攻击者伪造大量的SYN请求到服务器,如果还携带了大量数据,服务器会分配很多资源来处理数据进一步增加了服务器的负担
  2. 第二次不能携带数据的主要原因是此时还只是半连接状态,服务器还不能确认客户端的接收能力是否正常。如果第二次握手能够携带数据,那么服务器在还没有完全确认客户端的接收能力的情况下就发送数据,可能会导致数据丢失或混乱。
SYN 攻击

在TCP三次握手中,当客户端向服务器发送SYN报文时,服务器会回复一个SYN+ACK报文,并等待客户端的ACK报文。如果客户端没有回应ACK报文,服务器会在一定时间内重发SYN+ACK报文。
SYN攻击就是利用这个机制,恶意攻击者发送大量的伪造SYN报文给服务器,而不回应ACK报文。这样,服务器会不断重发SYN+ACK报文,消耗大量资源,最终导致无法响应正常请求。

TCP/IP 四次挥手

建立一个连接需要三次握手,而终止一个连接要经过四次挥手(也有将四次挥手叫做四次握手的)。这由TCP的半关闭(half-close)造成的。所谓的半关闭,其实就是TCP提供了连接的一端在结束它的发送后还能接收来自另一端数据的能力。

TCP/IP 四次挥手过程
  1. 动作开始,客户端和服务器都属于 ESTABLISHED 状态,客户端发送完毕,发送 FIN 报文,FIN=1,seq=u,停止发送数据,关闭 TCP 连接,客户端进入到 FIN_WAIT1 状态
  2. 服务器接收到 FIN 报文,于是向客户端发送 ACK 报文,表示知道了客户端的关闭,ACK=1,ack=u+1,seq=v,服务器进入到 CLOSE_WAIT 状态
  3. 客户端收到服务器的 ACK 报文后,进入 FIN_WAIT2 状态,此时服务器的数据可能还没有传输完成,等待服务器发送完毕后,向客户端发送 FIN 报文
  4. 服务器数据发送完成,向客户端发送 FIN 报文,FIN=1,seq=w,ACK=1 ack=u+1,服务器进入 LAST_ACK 状态
  5. 客户端收到服务端的FIN报文后,发送一个ACK报文给服务端,ACK=1,ack=w+1,表示确认收到了FIN报文。服务端收到报文后,进入CLOSED状态,客户端为TIME_WAIT状态,需要过2MSL后,才会进入到CLOSE状态。

在步骤四中,服务端发送的FIN报文常常会被合并成一个带有ACK标志的报文,这样服务端可以在一个报文段中同时表示“我收到了你的FIN”和“现在我也要关闭连接”。
这种做法在TCP协议中是被允许的,并且由于减少了网络往返时间(RTT),可以提高效率。
步骤三中的ACK是对客户端的FIN的确认,而步骤四中的ACK(如果有的话)通常是对之前未确认的数据的确认。这两个ACK是不同的,分别对应于不同的报文段和不同的确认目的。

为什么是 2MSL 时间才进入到 close 状态
  1. 防止迟到的报文段干扰:当TCP连接关闭后,网络中可能仍然存在未到达目的地的报文段。这些报文段可能由于网络延迟或其他原因而延迟到达。为了确保这些迟到的报文段不会干扰新的连接或导致混淆,
    TCP连接在关闭后需要等待一段时间,以确保所有可能的迟到的报文段都已经从网络中消失。2MSL时间足够长,可以确保这些报文段在网络中自然消亡。
  2. 确保对方收到ACK报文:在TCP的四次挥手过程中,当一方发送FIN报文请求关闭连接时,对方会回复一个ACK报文以确认收到关闭请求。然而,由于网络的不确定性,发送方可能无法确定对方是否真正收到了这个ACK报文。
    通过等待2MSL时间,发送方可以确保如果对方没有收到ACK报文,它会重发FIN报文。如果在2MSL时间内没有收到重发的FIN报文,发送方可以确信对方已经收到了ACK报文。
2.TCP/IP 异常状态
1.第一次和第二次握手失败

第一次SYN丢包,客户端处于 SYN_SEND状态,服务器处于LISTEN状态,客户端会进行超时重传,每一次重传时间的间隔都是上一次重传间隔的两倍,控制tcp重传次数的内核参数是 /proc/sys/net/ipv4/tcp_syn_retries
第二次SYN和ACK丢包,客户端处于SYN_SEND状态,服务器处于SYN_RCVD状态,服务器也会进行超时重传,时间间隔也是上一次重传时间的两倍,参数是 /proc/sys/net/ipv4/tcp_synack_retries

客户端一段时间得不到回应便会自动重发syn,且每次发送间隔是上一次间隔的2倍,服务端收到客户端syn报文后其超时定时器并不会重置,在隔到指定时间后仍会重发synack报文,且报文的重发间隔也是上一次间隔的两倍。

最后客户端和服务端在接受不到应有的回答后各自重传均达到5次后断开连接。

2.第三次握手失败

服务端由于一直收不到客户端的ACK报文停留在SYN_RCVD状态,而客户端由于收到服务端的第二次握手SYN-ACK,已处于ESTABLISH状态。

服务端得不到ACK将会重传SYN_ACK报文,服务端达到最大重传次数,便中断连接,客户端仍保持ESTABLISH状态。使客户端发送数据,由于服务端已CLOSE无法响应,客户端便会一直试图重传,且重传的时间间隔越来越大,但并不是超时重传的每次乘2倍的算法。

客户端重传数据的内核参数由tcp_retries2控制,默认为15,据此上述客户端并不会一直重传下去,而且在重传次数达到15次后退出。

cat /proc/sys/net/ipv4/tcp_retries2
15

如果客户端一直不发送数据,TCP也有机制断开其连接,负责这个任务的是tcp的保活机制,这个机制的原理是这样的:

定义一个时间段,在这个时间段内,如果没有任何连接相关的活动,TCP 保活机制会开始作用,每隔一个时间间隔,发送一个「探测报文」,该探测报文包含的数据非常少,如果连续几个探测报文都没有得到响应,则认为当前的 TCP 连接已经死亡,系统内核将错误信息通知给上层应用程序。

在 Linux 内核可以有对应的参数可以设置保活时间、保活探测的次数、保活探测的时间间隔,以下都为默认值:

net.ipv4.tcp_keepalive_time=7200
net.ipv4.tcp_keepalive_intvl=75  
net.ipv4.tcp_keepalive_probes=9

总体来讲,会出现两种情况

  • 如果客户端没发送数据包,一直处于 ESTABLISHED 状态,然后经过 2 小时 11 分 15 秒才可以发现一个「死亡」连接,于是客户端连接就会断开连接。(前提是客户端要打开keepalive选项,否则就会一直持续下去)
  • 如果客户端发送了数据包,一直没有收到服务端对该数据包的确认报文,则会一直重传该数据包,直到重传次数超过 tcp_retries2 值(默认值 15 次)后,客户端就会断开 TCP 连接。
对于挂起,程序崩溃的情况

对于挂起,拔线等没有导致进程崩溃的情况,只要客户端能在服务端的重传时间内重新连上服务端,就能够保持之前的连接,否则服务端一旦达到最大重传次数就会自动断开连接,之后客户端再次若尝试连上服务端,服务端便会无情地回复RST中断连接;

对于重启(包括关机再重启),SIGINT中断,kill等导致客户端进程崩溃的情况,客户端会向服务端发送FIN报文断开连接,将先前的连接清除,下次客户端再度重新连接服务端时,将采用新的四元组连接。

至于服务端没传数据的情况则更加简单,若客户端进程拔线,则原有连接不影响,服务端将在一定时间后启动tcp保活机制检测连接是否“活着”,若此时客户端仍不活跃,服务端将会清除该连接。而客户端主机宕机,进程崩溃的情况就是直接断连了

相关推荐

  1. TCP握手挥手状态转换详解

    2024-02-21 14:30:02       39 阅读
  2. TCP握手挥手

    2024-02-21 14:30:02       20 阅读
  3. tcp握手挥手

    2024-02-21 14:30:02       22 阅读

最近更新

  1. TCP协议是安全的吗?

    2024-02-21 14:30:02       18 阅读
  2. 阿里云服务器执行yum,一直下载docker-ce-stable失败

    2024-02-21 14:30:02       19 阅读
  3. 【Python教程】压缩PDF文件大小

    2024-02-21 14:30:02       18 阅读
  4. 通过文章id递归查询所有评论(xml)

    2024-02-21 14:30:02       20 阅读

热门阅读

  1. GET和POST两种HTTP 方法比较

    2024-02-21 14:30:02       34 阅读
  2. pytorch使用文档

    2024-02-21 14:30:02       23 阅读
  3. 代码随想录算法训练营第五十一天| 139.单词拆分

    2024-02-21 14:30:02       33 阅读
  4. Selenium Grid4.0 - 多台计算机上并行运行

    2024-02-21 14:30:02       34 阅读
  5. 本地项目如何连接远程git库

    2024-02-21 14:30:02       23 阅读