使用JSch连接HP路由器时session.disconnect()方法挂起求助
JSch连接HP路由器调用session.disconnect()挂起问题排查与解决
使用JSch库通过SSH连接HP路由器执行CLI命令,执行完成后调用session.disconnect()时出现挂起,无法正常断开会话。只有当路由器触发空闲超时后,会话才会自动断开。以下是相关代码及线程转储信息:
原代码
package HpRouter; import java.lang.Thread; import java.io.DataInputStream; import java.io.DataOutputStream; import com.jcraft.jsch.Channel; import com.jcraft.jsch.JSch; import com.jcraft.jsch.JSchException; import com.jcraft.jsch.Session; public class HpRouter { String CIPHER_S2C_KEY_NAME = "cipher.s2c"; String CIPHER_S2C_KEY_VALUE = "aes128-cbc,3des-cbc,blowfish-cbc"; String CIPHER_C2S_KEY_NAME = "cipher.c2s"; String CIPHER_C2S_KEY_VALUE = "aes128-cbc,3des-cbc,blowfish-cbc"; String CHECK_CIPHERS_KEY_NAME = "CheckCiphers"; String CHECK_CIPHERS_KEY_VALUE = "aes128-cbc"; String BCFNNI_STRICCTHOSTKEYCHECKING = "StrictHostKeyChecking"; String NO = "no"; String OPEN_SHELL_CHANNEL = "shell"; String USER_NAME = "username"; String PCODE = "123456"; String HOST_NAME = "1.1.1.1"; String PORT = "22"; public Session getChannelConnection() throws JSchException { String userName = USER_NAME; String password = PCODE; String hostName = HOST_NAME; int port = Integer.parseInt(PORT); JSch jsch = new JSch(); Session session = jsch.getSession(userName, hostName, port); session.setPassword(password); session.setConfig(CIPHER_S2C_KEY_NAME, CIPHER_S2C_KEY_VALUE); session.setConfig(CIPHER_C2S_KEY_NAME, CIPHER_C2S_KEY_VALUE); session.setConfig(CHECK_CIPHERS_KEY_NAME, CHECK_CIPHERS_KEY_VALUE); java.util.Properties config = new java.util.Properties(); config.put(BCFNNI_STRICCTHOSTKEYCHECKING, NO); session.setConfig(config); session.connect(); return session; } public StringBuilder getChannelOutput(DataInputStream ins, Session session, Channel channel) throws Exception { String line = null; StringBuilder sb = new StringBuilder(); byte[] tmpBuffer = new byte[1024]; try { while (true) { while (ins.available() > 0) { int tmp = ins.read(tmpBuffer, 0, 1024); if (tmp < 0) break; line = new String(tmpBuffer, 0, tmp); sb.append(line); } if (channel.isClosed()) { break; } } if (session != null) { session.disconnect(); } channel.disconnect(); } catch (Exception e) { System.out.println("Exception while getting Channel Output - "); Thread.currentThread().interrupt(); throw new Exception("Exception while getting Channel Output - ", e); } return sb; } public static void main(String[] args) { HpRouter hpObj = new HpRouter(); try { Session session = hpObj.getChannelConnection(); Channel channel = session.openChannel("shell"); channel.connect(); DataOutputStream ops = new DataOutputStream(channel.getOutputStream()); DataInputStream ins = new DataInputStream(channel.getInputStream()); ops.writeBytes("quit" + "\r\n"); ops.flush(); StringBuilder sb = hpObj.getChannelOutput(ins, session, channel); } catch (Exception e) { } } }
线程转储
"main" #1 prio=5 os_prio=0 cpu=1272.29ms elapsed=37.55s tid=0x00007feb6c410000 nid=0x20f504 runnable [0x00007feb7a452000] java.lang.Thread.State: RUNNABLE at java.net.PlainSocketImpl.socketClose0(java.base@11.0.20/Native Method) at java.net.AbstractPlainSocketImpl.socketPreClose(java.base@11.0.20/AbstractPlainSocketImpl.java:722) at java.net.AbstractPlainSocketImpl.close(java.base@11.0.20/AbstractPlainSocketImpl.java:601) - locked <0x000000043af0b0a8> (a java.lang.Object) at java.net.SocksSocketImpl.close(java.base@11.0.20/SocksSocketImpl.java:1067) at java.net.Socket.close(java.base@11.0.20/Socket.java:1513) - locked <0x000000043aef9190> (a java.lang.Object) - locked <0x000000043aef4b60> (a java.net.Socket) at java.net.SocketInputStream.close(java.base@11.0.20/SocketInputStream.java:249) at com.jcraft.jsch.Session.disconnect(Session.java:1753) at HpRouter.HpRouter.getChannelOutput(HpRouter.java:76) at HpRouter.HpRouter.main(HpRouter.java:104) Locked ownable synchronizers: - None
问题分析
从线程转储可以看到,主线程卡在java.net.PlainSocketImpl.socketClose0本地方法上,说明JSch在尝试关闭底层Socket连接时被阻塞。核心原因包括:
- 输入/输出流未被正确关闭,导致Socket资源被占用无法释放
- 路由器未正确响应
quit命令,通道未正常关闭,会话断开时IO资源仍处于等待状态 - 资源关闭顺序错误,未先关闭通道及相关流就直接断开会话
解决方案
1. 调整资源关闭顺序
先关闭输入输出流,再断开通道,最后断开会话,避免资源占用导致的阻塞。
2. 确认命令执行完成
检测路由器返回的断开标识(如特定提示符或断开信息),确保命令执行完成后再退出读取循环。
3. 使用带超时的断开方法
调用session.disconnect(int timeout)设置超时时间,强制在超时后断开会话,避免无限挂起。
4. 优化读取循环逻辑
添加短暂休眠减少空循环的CPU占用,避免无效轮询。
修改后的代码示例
package HpRouter; import java.io.DataInputStream; import java.io.DataOutputStream; import java.io.IOException; import com.jcraft.jsch.Channel; import com.jcraft.jsch.JSch; import com.jcraft.jsch.JSchException; import com.jcraft.jsch.Session; public class HpRouter { private static final String CIPHER_S2C_KEY_NAME = "cipher.s2c"; private static final String CIPHER_S2C_KEY_VALUE = "aes128-cbc,3des-cbc,blowfish-cbc"; private static final String CIPHER_C2S_KEY_NAME = "cipher.c2s"; private static final String CIPHER_C2S_KEY_VALUE = "aes128-cbc,3des-cbc,blowfish-cbc"; private static final String CHECK_CIPHERS_KEY_NAME = "CheckCiphers"; private static final String CHECK_CIPHERS_KEY_VALUE = "aes128-cbc"; private static final String STRICT_HOST_KEY_CHECKING = "StrictHostKeyChecking"; private static final String NO = "no"; private static final String USER_NAME = "username"; private static final String PCODE = "123456"; private static final String HOST_NAME = "1.1.1.1"; private static final int PORT = 22; // 根据路由器实际返回的断开信息修改此标识 private static final String DISCONNECT_MARKER = "Connection closed"; public Session getChannelConnection() throws JSchException { JSch jsch = new JSch(); Session session = jsch.getSession(USER_NAME, HOST_NAME, PORT); session.setPassword(PCODE); session.setConfig(CIPHER_S2C_KEY_NAME, CIPHER_S2C_KEY_VALUE); session.setConfig(CIPHER_C2S_KEY_NAME, CIPHER_C2S_KEY_VALUE); session.setConfig(CHECK_CIPHERS_KEY_NAME, CHECK_CIPHERS_KEY_VALUE); java.util.Properties config = new java.util.Properties(); config.put(STRICT_HOST_KEY_CHECKING, NO); session.setConfig(config); session.connect(); return session; } public StringBuilder getChannelOutput(DataInputStream ins, DataOutputStream ops, Channel channel) throws Exception { String line = null; StringBuilder sb = new StringBuilder(); byte[] tmpBuffer = new byte[1024]; try { while (true) { while (ins.available() > 0) { int tmp = ins.read(tmpBuffer, 0, 1024); if (tmp < 0) break; line = new String(tmpBuffer, 0, tmp); sb.append(line); // 检测到断开标识则退出读取 if (line.contains(DISCONNECT_MARKER)) { break; } } // 双重判断:通道关闭或已检测到断开标识 if (channel.isClosed() || sb.toString().contains(DISCONNECT_MARKER)) { break; } // 短暂休眠避免空循环占用CPU Thread.sleep(100); } } catch (Exception e) { Thread.currentThread().interrupt(); throw new Exception("Exception while getting Channel Output - ", e); } finally { // 先关闭输入输出流 try { if (ins != null) ins.close(); if (ops != null) ops.close(); } catch (IOException e) { e.printStackTrace(); } // 再断开通道 if (channel != null && channel.isConnected()) { channel.disconnect(); } } return sb; } public static void main(String[] args) { HpRouter hpObj = new HpRouter(); Session session = null; try { session = hpObj.getChannelConnection(); Channel channel = session.openChannel("shell"); channel.connect(); DataOutputStream ops = new DataOutputStream(channel.getOutputStream()); DataInputStream ins = new DataInputStream(channel.getInputStream()); ops.writeBytes("quit" + "\r\n"); ops.flush(); StringBuilder sb = hpObj.getChannelOutput(ins, ops, channel); // 最后断开会话,设置5秒超时 if (session != null && session.isConnected()) { session.disconnect(5000); } } catch (Exception e) { e.printStackTrace(); // 异常时强制断开会话 if (session != null && session.isConnected()) { session.disconnect(); } } } }
关键修改点
- 调整资源关闭顺序:在finally块中优先关闭流,再断开通道,最后在main方法中断开会话
- 添加断开标识检测,确保及时退出读取循环
- 使用带超时的
session.disconnect(5000)避免无限挂起 - 优化循环逻辑,添加短暂休眠减少CPU占用
- 修正常量名拼写错误(
BCFNNI_STRICCTHOSTKEYCHECKING改为STRICT_HOST_KEY_CHECKING)
内容的提问来源于stack exchange,提问作者mindalkar
相关产品推荐
相关产品推荐

