TCP三次握手和四次挥手

客户端ip:192.168.9.117,服务端ip:47.103.68.136

http请求:47.103.68.136:300

 

(1)三次握手

这里可以观察到的现象,http请求是服务端断开的连接,而不是常见socket通信中客户端请求断开连接。

 (2)四次挥手

 看资料,说要客户端断开连接,可以设置connection:close,

 

 但测试发现,仍然是服务端先断开连接的,只是服务端不是发送单独的Fin,ACK,而是同数据一起发送的。

 为什么是服务器主动断开?

短连接的核心思想是

  1. 服务器控制连接的生命周期

    • 服务器知道自己是否还有数据要发送,如果不需要保持连接,就可以直接 close(socket)

    • 客户端不能随意关闭连接,否则可能会导致数据未完全接收。

  2. 服务器节省资源

    • 服务器通常要处理多个请求,主动关闭连接可以释放 socket 资源,防止客户端长时间占用。

  3. HTTP 1.0 及早期 HTTP 1.1 实现

    • HTTP/1.0 默认是短连接,每个请求-响应对都会新建并关闭一个 TCP 连接。

    • HTTP/1.1 默认是 长连接(Keep-Alive),但如果服务器不希望保持连接,它会 在响应头中加 Connection: close,然后主动关闭

 

使用http1.1确实可以看到建立长连接,由客户端控制关闭,但事实上只看到了三次挥手,经查证,它是四次挥手的优化版本,它符合 TCP 协议的标准行为之一。

 按照标准,服务端的FIN是分开发的

Client → Server: [FIN, ACK] (请求关闭)
Server → Client: [ACK] (确认)
Server → Client: [FIN, ACK] (服务器也关闭)
Client → Server: [ACK] (确认)

是合法的! 服务器可以选择:

  • ACK 客户端的 FIN,稍后再 FIN

  • 或者直接 ACK + FIN 一起发送

这种 合并 ACKFIN 的方式 叫做 "优化的四次挥手",TCP 允许这种行为。服务器这么做的目的是减少一次网络往返,提高效率

如果服务器没有额外的数据要发送,它就可以立即 FIN。

1.为什么TCP会有四次挥手?(截图可以看出来,资源是分开返回的,返回html,再加载图片等资源)

tcp是全双工模式,即客户端和服务端能同时发送数据,所以每个方向的数据需要单独确认,主要是因为 TCP 允许双向数据传输是独立的,关闭一个方向的传输不应该影响另一个方向的正常通信,比如客户端请求关闭连接了,表示客户端不再发送数据,但不表示服务端不能再发送数据,服务端可以发送数据,客户端也可以接收数据。而关闭连接是双方都不再发送数据。所以客户端告诉服务端不再发送数据了,服务端确认,但服务端还是可以发送数据的,直到服务端发送fin,告诉客户端自己不再发送数据,客户端确认,才能确认连接关闭。

为什么不能客户端关闭,服务端就关闭?

服务器的数据还没完全发完,但连接已经关闭,客户端收不到数据,造成数据丢失。

四次挥手确保:

  • 客户端 FIN 只影响自己,不影响服务器继续发数据。

  • 服务器 FIN 只在数据完全发送完毕后才触发,确保所有数据完整传输。四次挥手确保服务器能在收到 FIN 后,继续发送剩余数据,直到完全发送完毕,才正式关闭连接。

客户端发起FIN,服务端ACK

              

2.为什么服务器 ACKFIN 可能会合并?

  • 服务器不需要再等数据:如果服务器 没有额外数据需要发送,它可以直接 FIN 关闭连接。

  • 减少 TCP 交互次数:合并 ACKFIN 可以 减少一次往返,提高效率

  • 操作系统 TCP 栈优化:某些系统为了减少 CLOSE_WAIT 可能会自动合并 ACK + FIN

3.为什么会有 TIME_WAIT 状态?

关键点:避免“旧连接数据”影响新连接  

如何做到的?进入TIME_WAIT状态,前的连接无法立即被重新使用。最大报文生存时间,当前TCP数据包可能在网络中留存的时间,该时间范围内,数据包被丢弃。

在 TCP 四次挥手后,主动关闭连接的一方(通常是客户端)会进入 TIME_WAIT 状态,通常会 持续 2 * MSL(最大报文生存时间),一般是 30-120 秒

TIME_WAIT 的作用

        a.防止延迟数据干扰新连接

    • 如果网络中存在延迟的 TCP 数据包,而新连接刚好复用了相同的 IP:Port,这些 “旧数据” 可能被错误地认为是新连接的数据,导致数据错乱。

    • TIME_WAIT 让旧数据有足够时间被丢弃,保证新连接的数据不会受干扰。

       b.确保对方正确接收到 ACK

    • 在 TCP 最后一个 ACK 发送后,如果客户端 立刻释放连接,但这个 ACK 丢失了,服务器会 重发 FIN,但客户端已经关闭,导致服务器 一直等待

    • TIME_WAIT 确保客户端可以重发 ACK,确保服务器顺利关闭连接。

根据上面的测试,http服务器通常是主动关闭连接的一方,http1.0协议大多都是短连接协议,这就可能导致:

  • 在高并发服务器上,短连接频繁创建和关闭,会导致大量 TIME_WAIT 连接:
  • 端口资源被占用,服务器可能 无法创建新连接port exhaustion)。

系统性能下降,太多 TIME_WAIT 连接会占用内存和 CPU。

 

解决方案

为了避免 TIME_WAIT 状态占用过多资源,有一些方法可以优化:

  • 调整 TIME_WAIT 的持续时间

    在 Linux 上,你可以通过调整内核参数来减小 TIME_WAIT 的持续时间:

 sysctl -w net.ipv4.tcp_fin_timeout=30

     这将减少 TIME_WAIT 的持续时间。

  • 启用端口重用(SO_REUSEADDR

某些操作系统允许在 TIME_WAIT 状态时重用端口(例如,Linux 系统中通过设置 SO_REUSEADDR 选项)

  • 使用长连接(Keep-Alive)

对于需要频繁通信的应用,使用 TCP 长连接而不是每次都创建新的短连接,可以显著减少 TIME_WAIT 状态。

 4.CLOSE_WAIT状态

 是 TCP 协议中的一个连接状态,表示一方已经收到关闭连接的请求(FIN),并且已经准备好关闭连接,但还没有关闭自己的数据流(即没有发送 FIN 包)。换句话说,CLOSE_WAIT 是连接的一方等待关闭连接的状态

当 TCP 连接的一方收到另一方的 FIN(关闭连接的请求)时,该方会进入 CLOSE_WAIT 状态。这个状态表示:

  • 另一方已经请求关闭连接(发送 FIN)。
  • 当前这方还没有关闭连接,即还没有发送自己的 FIN 来完成连接的断开。

    a.客户端关闭请求: 发送 FIN 请求关闭连接,进入 FIN_WAIT_1 状态。

    b.服务端收到 FIN 请求: 服务端会返回一个 ACK 来确认收到 FIN,并进入 CLOSE_WAIT 状态。

    c.服务端:CLOSE_WAIT 状态下,服务端应当在完成必要的数据处理后(比如应用层数据传输)发送自己的 FIN 来关闭连接。

如果一方长时间处于 CLOSE_WAIT 状态,可能意味着该方没有正确关闭连接。

  • 系统资源可能会泄漏,因为即使连接已经关闭,但连接的套接字资源仍然被占用,无法释放,导致无法回收端口。

  • TCP 连接可能会积压大量的 CLOSE_WAIT 连接,这会导致服务器的资源耗尽,影响正常服务。

状态的常见原因

  • 应用程序没有及时关闭连接:

      如果服务器端的应用程序没有处理完所有数据后立即关闭连接,CLOSE_WAIT 状态将持续存在。通常,程序需要在收到 FIN 后,尽早调用 close()shutdown() 来关闭连接。

  • 资源未释放:

     如果某个程序无法及时释放资源,或存在 bug 导致它没有正确关闭连接,也会导致大量 CLOSE_WAIT 状态。

解决方案

 

  • 检查应用层代码:

     确保应用程序在收到 FIN 后,及时调用 close()shutdown() 来关闭连接。通常,在应用层处理完数据后,必须关闭连接

    在 Linux 系统上,你可以使用 lsofnetstat 命令来查看哪些应用程序导致了 CLOSE_WAIT 状态:lsof -i :<port> | grep CLOSE_WAIT   netstat -anp | grep CLOSE_WAIT

    在windows系统上,使用netstat命令查看:netstat -an |findstr CLOSE_WAIT
 
  • 优化服务器应用程序:
  • 优化连接管理

    • 使用长连接(如 HTTP Keep-Alive)减少连接的建立和关闭次数。

    • 如果可能,使用 连接池 等技术,重用连接而不是频繁地打开和关闭连接。 

  • 增加操作系统的连接关闭超时设置:

一些操作系统允许你调整连接关闭的超时设置,例如在 Linux 上使用 sysctl 配置:

sysctl -w net.ipv4.tcp_fin_timeout=30

其他的TCP问题:

问题1:SYN Flood 攻击防护

  • TCP 三次握手中,服务器收到 SYN 但未收到 ACK,会进入 SYN_RECV 状态。

  • 攻击者可以伪造大量 SYN 请求,导致服务器 SYN_RECV 过多,拒绝新连接。

解决方案:

  1. 启用 SYN Cookies(Linux 默认开启)

     
    sysctl -w net.ipv4.tcp_syncookies=1
    SYN Cookies 机制允许服务器在不创建完整 TCP 连接的情况下响应 SYN 请求,防止资源耗尽。
  2. 限制半连接队列大小

     
    sysctl -w net.ipv4.tcp_max_syn_backlog=2048
  3. 使用防火墙(iptables)限制异常连接

     
    iptables -A INPUT -p tcp --syn -m limit --limit 1/s --limit-burst 3 -j ACCEPT

问题2:TCP 拥塞控制优化

  • 在高流量环境下,TCP 拥塞控制会影响带宽利用率,常见算法包括:

    • Reno(经典拥塞控制)

    • Cubic(Linux 默认,适合高带宽)

    • BBR(Google 开发,低延迟高吞吐)

优化方案:

  • 切换到 BBR 拥塞控制算法(适用于高并发、大带宽场景)

     
    sysctl -w net.ipv4.tcp_congestion_control=bbr sysctl -w net.core.default_qdisc=fq

问题3:TCP 延迟 ACK 影响实时性

  • 默认情况下,TCP 为了减少小数据包数量,会合并多个 ACK 进行延迟发送。

  • 在某些场景(如低延迟应用、金融交易),这种延迟会影响性能。

解决方案:

  • 关闭 TCP 延迟 ACK(慎用,可能增加小包流量)

     
    int flag = 1; setsockopt(sock, IPPROTO_TCP, TCP_NODELAY, (char *)&flag, sizeof(int));

问题4:TCP 半连接 & 半关闭

  • 在某些应用场景下,可能只需要单向关闭连接,例如:

    • HTTP 服务器 关闭输入流,但继续发送响应。

    • 文件传输 一方发送完数据后,只关闭写端,保留读端。

解决方案:

  • 使用 shutdown() 进行单向关闭:

     
    shutdown(sock, SHUT_WR); // 关闭写端,继续读取数据

问题5:TCP RST 断开连接

  • 在某些情况下,TCP 连接可能直接被 RST(重置)断开,导致连接异常:

    • 服务器端口未监听,直接 RST 返回。

    • 应用层超时,主动调用 setsockopt() 关闭连接。

    • 服务器进程崩溃,导致 TCP 连接直接 RST 终止。

排查方法:

  1. 使用 tcpdump 捕获 RST 包:

     
    tcpdump -i eth0 'tcp[tcpflags] & tcp-rst != 0'
  2. 检查应用程序是否使用 SO_LINGER

     
    struct linger linger_opt = {1, 0}; // 立即关闭,不等待数据传输 setsockopt(sock, SOL_SOCKET, SO_LINGER, &linger_opt, sizeof(linger_opt));

问题6:负载均衡 & TCP 会话保持

  • 在多台服务器之间做 负载均衡(如 Nginx, LVS) 时,TCP 连接可能在多个后端之间跳转,导致会话丢失。

解决方案:

  • 基于 IP 或 Cookie 绑定连接

  • 使用 TCP 直连模式(Nginx proxy_pass + keepalive

  • 使用 QUIC(HTTP/3),避免 TCP 连接重建

 

posted @ 2025-04-01 12:12  头号程序媛  阅读(59)  评论(0)    收藏  举报