Java TCP服务器为何接收已关闭Socket?异常排查求助
排查Socket Closed频繁抛出的问题:从代码到环境的分析
首先,咱们先把你的场景和代码理清楚:你有一个负责监听TCP连接的类A,每次接到新连接就把Socket传给线程B处理收发,之前运行了好几年都正常,最近突然频繁抛出Socket Closed异常,而且相同应用在其他服务器上跑没问题,想搞清楚是网络、线程还是服务器本身的锅。
先贴一下你的核心代码和异常信息,方便一起分析:
你的核心代码实现
class A { B b = new B(); public void run() { b.start(); while(true){ Socket socket = serverSocket.accept(); if(socket==null || socket.isClosed())continue; b.setSocket(socket); } } } class B extends Thread{ Socket socket; public void setSocket(Socket p_socket) throws IOException { if(socket!=null && !socket.isClosed()) { try{ socket.close(); socket = null; Thread.sleep(5); } catch(Exception ex) {} } socket = p_socket; inputStream = socket.getInputStream(); // 频繁出异常的地方 } }
抛出的异常栈
java.io.IOException: Socket Closed at java.net.AbstractPlainSocketImpl.getInputStream(AbstractPlainSocketImpl.java:421) at java.net.Socket$2.run(Socket.java:914) at java.net.Socket$2.run(Socket.java:912) at java.security.AccessController.doPrivileged(Native Method) at java.net.Socket.getInputStream(Socket.java:911)
可能的原因拆解
1. 线程安全的坑(最大概率的元凶)
你的代码里有个很明显的线程安全问题:setSocket方法完全没有同步机制,而且共享变量socket被两个线程(A的主线程和B的工作线程)同时操作。
- 当
A刚accept到一个新Socket,还没来得及传给B,客户端可能已经主动断开了,这时候setSocket里直接调用getInputStream就会触发异常。 - 更糟的是,
B线程可能正在用旧Socket读写,A的主线程突然调用setSocket关闭旧Socket并替换成新的,这个过程中如果B还在操作旧Socket的流,或者新Socket在替换过程中被意外关闭,都会抛出异常。 - 另外,你吞掉了
close时的异常,这会让你错过很多关键的错误信息,比如旧Socket关闭失败的原因。
2. 服务器环境差异(符合"其他服务器正常"的现象)
既然相同应用在别的服务器上没问题,那当前服务器的环境肯定是重点排查对象:
- 网络不稳定:比如当前服务器的防火墙配置了短时间的空闲连接超时,或者网络链路有丢包、波动,导致客户端连接后立刻被断开,
accept到的Socket很快处于关闭状态。 - 系统资源耗尽:服务器的文件句柄、内存、CPU被占满,JVM无法正常维护Socket连接,导致Socket被强制关闭。可以用
lsof -p <JVM进程ID>看看打开的文件数,top看资源占用。 - TCP参数不一致:不同服务器的TCP内核参数(比如
tcp_fin_timeout、tcp_keepalive_time)配置不同,当前服务器的参数可能导致Socket的生命周期被提前终止。对比一下正常服务器的参数,调整试试。
3. 客户端行为变化
如果近期客户端的逻辑有更新,比如客户端连接后立刻主动断开,或者客户端所在的网络环境变差,也会导致accept到的Socket很快失效,触发这个异常。
具体的解决和排查步骤
第一步:先把线程安全的问题补上
给setSocket加同步锁,并且在获取输入流前再次检查Socket状态,同时不要吞掉异常:
class B extends Thread{ private Socket socket; private InputStream inputStream; // 用同步锁保护共享资源的访问,避免多线程冲突 public synchronized void setSocket(Socket p_socket) throws IOException { // 关闭旧Socket的逻辑,不要吞异常 if(socket!=null && !socket.isClosed()) { try{ if(inputStream != null){ inputStream.close(); // 先关流再关Socket } socket.close(); socket = null; Thread.sleep(5); } catch(Exception ex) { ex.printStackTrace(); // 打印异常,方便排查 } } // 再次检查新Socket的状态,避免拿到已经关闭的Socket if(p_socket == null || p_socket.isClosed()){ return; } socket = p_socket; // 捕获获取输入流的异常,避免整个方法崩溃 try{ inputStream = socket.getInputStream(); } catch(IOException e){ // 处理异常:关闭无效的Socket,置空变量 if(!p_socket.isClosed()){ p_socket.close(); } socket = null; e.printStackTrace(); } } // 线程B的run方法也要加同步,或者在操作流前检查状态 @Override public void run(){ while(!isInterrupted()){ synchronized(this){ if(socket == null || socket.isClosed() || inputStream == null){ try{ Thread.sleep(100); // 没连接就等一等 }catch(InterruptedException e){ break; // 被中断就退出 } continue; } // 这里进行数据读写操作,记得也要处理IO异常 } } } }
第二步:排查服务器环境
- 查网络:用
ping测试客户端和服务器的连通性,traceroute看链路是否有问题,查看防火墙日志是否有主动断开连接的记录。 - 查资源:用
top看CPU、内存占用,lsof -p <你的JVM进程ID>看打开的文件句柄数是否达到上限,netstat -an看TCP连接状态。 - 对比TCP参数:在正常服务器上执行
sysctl -a | grep tcp,把当前服务器的参数调整成一样的,比如tcp_fin_timeout、tcp_keepalive_time这些和连接生命周期相关的参数。
第三步:加日志定位问题
在关键位置加详细日志,比如:
- 类
A中accept到Socket时,记录客户端IP、端口和Socket的状态(isClosed()的结果)。 setSocket方法中,记录每次替换Socket的时间、旧Socket的状态、新Socket的状态。- 捕获Socket异常时,记录异常发生的时间、客户端IP,这样能快速定位是特定客户端还是普遍问题。
内容的提问来源于stack exchange,提问作者Al-Alamin
相关产品推荐
相关产品推荐

