You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.11 04:05:06