[技术求教] 如何主动杀死一个 tcp 连接?请大家帮我分析分析。 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
请不要在回答技术问题时复制粘贴 AI 生成的内容
aizya
V2EX    程序员

[技术求教] 如何主动杀死一个 tcp 连接?请大家帮我分析分析。

  •  1
     
  •   aizya 2024-10-11 18:33:37 +08:00 2765 次点击
    这是一个创建于 378 天前的主题,其中的信息可能已经有所发展或是发生改变。

    问题背景

    我开发了一个 Web 系统,有一个功能需要提供一个 HTTP REST 接口供设备端调用,我的 Web 用 Java 开发,设备的用的是 Labview ,他的代码具体怎么实现我不清楚。

    系统在运行一段时间后,客户反馈 HTTP 接口偶发调用失败,但是我通过查询日志,发现在上报接口无法调用的时刻,后台日志中接口都没有被触发。这个时候想到了看一下 TCP 连接数,监控发现 Web 系统有很多 CLOSE_WAIT 的 TCP 连接。

    netstat -an | grep "8080" | grep 'CLOSE_WAIT' 

    后面发现一个规律,随着 CLOSE_WAIT 连接越来越多,各个设备反馈接口无法调用的频率也随之增多。

    排查过程

    1. 先分析服务端的 CLOSE_WAIT 连接,发现一个特别怪异的事,服务端存在某个客户端(设备)的连接,但是在设备端,同样执行 netstat ,居然没有与服务端对应的 tcp 连接。从表象上来说,客户端不知道用什么方式杀死了连接,服务端还保留了 socket 。很奇怪啊,按照正常的逻辑,客户端应该存在很多 FIN-WAIT-2 的连接。

    四次挥手

    1. 于是分别在出问题的情况下,使用 tcpdump 和 wirehark 抓了服务器和某几个存在 CLOSE_WAIT 连接的设备的网络包,发现在设备端反馈 FIN 关闭连接时,会出现一个 RST 的帧,并且 8080 不会回 FIN,只会 ACK ,导致 CLOSE_WAIT 的出现。但是,这种问题具体出现的原因,我一直想不明白。

    抓包

    抓包

    1. 经过一番折腾,我考虑能不能通过服务器配置或者主动杀死 tcp 连接的方式减少 CLOSE_WAIT 连接,来尽可能减少 CLOSE_WAIT 堆积引起的高频率 CLOSE_WAIT 问题(解决不了问题,就解决出现问题的地方)。 我采用的是 killcx https://killcx.sourceforge.net/ ,来模拟 SYNC ,主动关闭连接。但是,不出意外,还是除了意外,连接关不掉!!!其他的连接都可以,但是就是 CLOSE_WAIT 的无法关闭。

    2. 最后,只能定时重启 JAVA 程序,主动释放连接才能恢复,在重启的三四天时间不会有问题,过了一个时间点,这个问题又出问题了。。。周而复始。。。

    请大家帮忙分析分析,为啥会出现服务器( Linux )保持了 TCP 连接,但是客户端( Windows )没有。到底是我 JAVA 应用的问题,还是网络问题抑或者是客户端关闭连接的问题?给提供点思路,拜求了!

    第 1 条附言    323 天前

    经过两个多月的排查,问题总算缓解了。根本原因大概率是接口中处理逻辑太复杂,导致运行一段时间后tcp请求队列堵塞,导致新的请求进不来直接丢弃。为了不直接去改后台应用逻辑(太复杂了),我解决的方式是通过调整tcp的系统参数:

    # 默认值:128 sudo sysctl -w net.ipv4.tcp_max_syn_backlog=4096 # 默认值:5 # 作用:此参数控制的是 TCP 客户端在发起连接时重试 SYN 包的次数。当 TCP 握手的 SYN 包没有收到响应时,客户端会重新发送 SYN 包。默认情况下,Linux 会重试 5 次。 # 调整效果:将该值降低到 2,表示 TCP 客户端在连接失败时会更早放弃重试,这对于高流量场景下减少资源浪费是有帮助的,尤其是在网络不稳定或服务端响应较慢时。降低此值可以加快连接的失败检测和释放未使用的资源。 sudo sysctl -w net.ipv4.tcp_syn_retries=2 # 默认值: net.ipv4.tcp_rmem:4096 87380 6291456 net.ipv4.tcp_wmem:4096 16384 4194304 sudo sysctl -w net.ipv4.tcp_rmem="8192 87380 67108864" sudo sysctl -w net.ipv4.tcp_wmem="8192 87380 67108864" # 默认值:60 # 作用:此参数控制的是处于 FIN_WAIT_2 状态的 TCP 连接在主动关闭一方等待对方确认关闭的最大时间。当 TCP 连接关闭后,主动关闭的一方会进入 FIN_WAIT_2 状态,在等待关闭确认之前会保留连接。 # 调整效果:将该值从 60 秒降低到 30 秒,能够更快地释放资源,特别是在高连接数的环境中,减少由于连接关闭超时导致的资源占用。然而,过短的超时时间可能导致资源清理不完全,因此要根据实际情况调整。 sudo sysctl -w net.ipv4.tcp_fin_timeout=30 # 默认值:4096 # 作用:此参数定义了系统可以保留的“孤儿” TCP 连接的最大数量。孤儿连接是指没有任何应用程序接收的数据包的 TCP 连接。在某些情况下,这些连接会由于异常而无法被正常关闭,导致系统资源被浪费。 # 调整效果:增大该值(比如设置为 327680)能够容纳更多的孤儿连接,适用于需要大量并发连接的场景。不过,如果该值设置过大,可能会导致系统资源被大量浪费,增加系统负担。 sysctl -w net.ipv4.tcp_max_orphans=327680 # 默认值:7200(即 2 小时) # 作用:此参数控制的是 TCP 连接在进入“空闲”状态后发送第一个 keepalive 探测包的时间,单位是秒。当 TCP 连接处于空闲状态时,系统会定期发送 keepalive 包,以确保连接仍然有效。 # 调整效果:将该值从默认的 2 小时降低到 600 秒(即 10 分钟),会加快检测空闲连接的速度,及时清理长时间没有数据传输的连接。对于需要及时发现死连接的应用场景(如 Web 服务)较为合适。 sysctl -w net.ipv4.tcp_keepalive_time=600 # 默认值:75 # 作用:此参数控制的是如果 TCP 连接处于空闲状态,keepalive 探测包的发送间隔时间,单位为秒。即在发送第一个探测包后,每隔多少秒发送一个探测包。 # 调整效果:将该值调整为 60 秒,表示每 60 秒发送一个 keepalive 包,检测连接是否仍然有效。这个值适用于需要快速检测死连接的场景。 sysctl -w net.ipv4.tcp_keepalive_intvl=60 # 默认值:9 # 作用:此参数控制的是在认为连接已断开之前,最大重试的探测次数。即,如果一个 TCP 连接没有响应 keepalive 包,系统会重试发送 keepalive 包的次数。 # 调整效果:将该值从默认的 9 次降低到 5 次,意味着在 5 次探测包失败后,系统会认为连接断开并关闭该连接。降低此值有助于尽早发现和关闭死连接,但设置得过低也可能导致正常连接被误判为断开。 sysctl -w net.ipv4.tcp_keepalive_probes=5 

    回过头来看,1L大哥就已经给出了答案。

    24 条回复    2024-10-13 02:00:44 +08:00
    dode
        1
    dode  
       2024-10-11 18:58:29 +08:00
    CLOSE_WAIT 问题 配置相关内核参数
    mango88
        2
    mango88  
       2024-10-11 19:00:21 +08:00
    60s 服务端没发送 FIN 帧, 大概率是你的代码有问题
    slowman
        3
    slowman  
       2024-10-11 19:11:25 +08:00
    是你 JAVA 应用的问题
    palfortime
        4
    palfortime  
       2024-10-11 19:21:20 +08:00 via Android
    1. 服务端用了哪个 java 的框架。
    2. 客户端与服务端之间是直连吗?有用了正向或反向代理吗?
    3. 服务端有发起其它 TCP 连接吗?确认 close_wait 的连接是客户端请求服务端的?
    Monad
        5
    Monad  
       2024-10-11 19:22:34 +08:00 via iPhone   1
    close_wait 不是你服务端没有 close 连接嘛 又不是 time_wait 大量 close_wait 是 bug 吧
    Mohanson
        6
    Mohanson  
       2024-10-11 19:22:49 +08:00   1
    拒绝废话: 服务端没有调用 accept 或者 accept 失败, 大概率后者

    详细解释: http://accu.cc/content/go/socket_not_accept/
    clester
        7
    clester  
       2024-10-11 19:25:26 +08:00 via iPhone
    对双端发送 reset
    geminikingfall
        8
    geminikingfall  
       2024-10-11 19:26:19 +08:00
    close_wait 堆积一般都是短链接集中关闭或者服务过于频繁来不及关闭,需要根据具体情况来决定怎么改。(背的八股用上了?)
    Mohanson
        9
    Mohanson  
       2024-10-11 19:27:15 +08:00
    还有一种可能就是服务端没有调用 Close. 总之是代码写的有问题.
    ysc3839
        10
    ysc3839  
       2024-10-11 19:30:38 +08:00 via Android   1
    感觉是因为你没把 write stream 关掉,对端一直等不到你的 FIN ,超时发送 RST 了。
    我个人觉得“四次挥手”这说法有问题,我个人会称作“两端关闭”。TCP 连接可以看成两条方向相反的管道,其中一端发送 FIN ,代表关闭自己这端的写管道(即对端的读管道),一端关闭后,另一端还是能继续写数据的,直到对端也关闭管道,两条管道都关闭了,TCP 连接断开。
    如果用“四次挥手”的话,就会给人感觉一端发送 FIN 后,另一端也会自动发送 FIN 关闭连接,但其实并不是,需要主动关闭。
    同时没记错的话 FIN 是可以和其他包合并的,就会出现不是正好四个包断开连接的情况。
    arloor
        11
    arloor  
       2024-10-11 19:37:59 +08:00   1
    被动关闭方(这里是 linux 服务端)没有调用 socket 的 close 方法,也就是没有发出 FIN 。
    所以你 java 应用到底用的啥框架,如果是 spring 应该不会有这种问题。
    如果是手搓的,那大概就是没有 close
    cppc
        12
    cppc  
       2024-10-11 21:58:20 +08:00
    CLOSE_WAIT 一般是你自己没有关闭连接导致的,如果你是自己手搓的 Web 服务端网络框架的话,注意排查一下
    itechify
        13
    itechify  
    PRO
       2024-10-11 22:02:04 +08:00
    这是一个好问题,mark
    seWindows
        14
    seWindows  
       2024-10-12 00:32:52 +08:00
    CLOSE_WAIT 是你忘了关了,对端断开是 TIME-WAIT/FIN-WAIT
    2xvaHoK2LGxQ29R5
        15
    2xvaHoK2LGxQ29R5  
       2024-10-12 00:45:50 +08:00 via Android
    杀死制造请求的人
    onesuren
        16
    onesuren  
       2024-10-12 09:36:37 +08:00
    现在看还是像是后端得问题 。https://blog.csdn.net/u013467442/article/details/90375361
    cheng6563
        17
    cheng6563  
       2024-10-12 10:11:28 +08:00
    你 Web 服务是用框架启的吧,框架应该不会忘记 Close 。

    翻下 std 有无输出 OOM 错误吧。我是建议直接加 -XX:+ExitOnOutOfMemoryError
    aizya
        18
    aizya  
    OP
       2024-10-12 13:00:17 +08:00
    @palfortime

    1. 服务端提供 REST 接口使用的是 RESTeasy ( JAX-RS )
    2. 直连的,没有用代理
    3. 服务端没有再调其他三方的接口,CLOSE_WAIT 对应的 ip 地址对应的就是客户端的。

    系统运行一段时间后才会慢慢出现 CLOSE_WAIT 问题,如果是本身没有关闭连接应该一请求就出来,很困惑不清楚如何排查。
    aizya
        19
    aizya  
    OP
       2024-10-12 13:40:44 +08:00
    @Mohanson #6 感谢,这篇文章是目前看到最匹配我遇到的问题的。但是您提到的 accept 失败,是由于对应 JAVA 进程的 open files 增多引起的?我翻看了日志,没有体现 too much open files 相关的日志,请问有其他排查的方法吗
    aizya
        20
    aizya  
    OP
       2024-10-12 13:43:50 +08:00
    @onesuren 受教了,感觉这个跟我的问题还不太一样,文章中是由于有代码中增加了休眠(运行时间过长引起的。)应该是每次请求都会增加一条 CLOSE_WAIT? 我的情况是系统运行前几天一切正常,之后就会出现问题。
    seedhk
        21
    seedhk  
       2024-10-12 13:57:57 +08:00
    CLOSE_WAIT 很明显就是服务端问题
    导出线程栈信息,找到所有 CLOSE_WAIT 的线程,看堆栈找对应代码
    iceheart
        22
    iceheart  
       2024-10-12 15:41:43 +08:00 via Android
    客户端没连接是因为已经 close 了,
    客户端 close 了服务端才有 CLOSE_WAIT
    服务端收到断开通知就要 close
    CLOSE_WAIT 的字面意思就是 等待(应用程序) 来调用 close
    palfortime
        23
    palfortime  
       2024-10-12 22:47:04 +08:00 via Android
    @aizya 不了解这个框架,你可以试一下跑个测试的服务,让客户端的进程跑一个请求就退出,看看会不会产生。
    可以在服务端 jstack 一下,看看有没有工作线程是卡住了。可能由于工作线程卡住,导致框架无法处理 close 请求。
    同时可以在服务端加载一下 opentelemetry 的 javaagent 收集一下 trace 和 metric ,看看是否有服务端的异常。
    xxxbin
        24
    xxxbin  
       2024-10-13 02:00:44 +08:00
    有意思。那么各位大佬,CLOSE_WAIT 的 tcp 该咋关?
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2549 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 24ms UTC 13:18 PVG 21:18 LAX 06:18 JFK 09:18
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86