JSch Shell通道断开后无法关闭,程序陷入停滞
问题分析与解决方案
嘿,我之前也碰到过类似的坑!用JSch的Shell通道执行带su的命令后程序停滞,核心原因是Shell通道是交互式会话,和Exec通道的一次性执行逻辑完全不同,如果没正确处理流和会话关闭,程序就会卡在等待输入/输出的状态。
下面给你拆解问题点和修复方案:
为什么会出现停滞?
Shell通道打开的是一个完整的交互式Shell会话,就像你手动SSH登录服务器一样:
- 如果你只发了目标命令但没主动结束会话,Shell会一直等待新的输入,对应的输入流不会触发EOF,你的程序就会阻塞在读取流的步骤(哪怕你以为命令已经执行完成)。
- 另外,如果没按正确顺序关闭流、通道、会话,也会导致资源没彻底释放,程序挂起。
修复步骤和代码示例
1. 主动结束会话,处理完所有输出
发送完目标命令后,一定要先发送exit退出su的root会话,再发送exit关闭整个Shell会话,让Shell主动终止流。同时要读取完所有剩余输出,避免流阻塞。
2. 按顺序关闭资源
关闭顺序必须是:输出流→输入流→通道→会话,确保每个资源都被正确释放。
修正后的代码如下:
public static void sendCommandWithSudo(String user, String pass, String ip, String command) { JSch jsch = null; Session session = null; ChannelShell channel = null; InputStream in = null; OutputStream out = null; try { jsch = new JSch(); session = jsch.getSession(user, ip, 22); session.setPassword(pass); // 生产环境别这么干!建议提前配置主机密钥或者用密钥认证 session.setConfig("StrictHostKeyChecking", "no"); session.connect(); channel = (ChannelShell) session.openChannel("shell"); in = channel.getInputStream(); out = channel.getOutputStream(); channel.connect(); // 发送su切换到root,必须加换行符触发执行 out.write("su - root\n".getBytes()); out.flush(); // 这里用sleep是临时方案,更可靠的做法是读取流直到出现"Password:"提示符 Thread.sleep(1000); // 发送root密码 out.write("你的root密码\n".getBytes()); out.flush(); // 等待切换到root,最好是读取到root提示符(比如"#")再发命令 Thread.sleep(1000); // 发送你的重启命令 out.write((command + "\n").getBytes()); out.flush(); // 先退出root会话,再退出整个Shell会话 Thread.sleep(1000); out.write("exit\n".getBytes()); out.flush(); out.write("exit\n".getBytes()); out.flush(); // 读取所有剩余输出,避免流阻塞 byte[] buffer = new byte[1024]; while (in.available() > 0) { int len = in.read(buffer); if (len < 0) break; System.out.print(new String(buffer, 0, len)); } } catch (Exception e) { e.printStackTrace(); } finally { // 按顺序关闭所有资源 try { if (out != null) out.close(); if (in != null) in.close(); if (channel != null && channel.isConnected()) { channel.disconnect(); } if (session != null && session.isConnected()) { session.disconnect(); } } catch (IOException e) { e.printStackTrace(); } } }
额外的优化建议
- 替换sleep为流读取判断:硬编码
Thread.sleep()不可靠,比如网络延迟高时,sleep时间不够会导致命令发送时机错误。更好的做法是循环读取输入流,直到出现预期的字符串(比如密码提示符Password:、root提示符#),再发送下一个命令。 - 优先用sudo而非su:如果服务器允许,建议直接用
sudo 你的命令,配合免密sudo配置,这样可以用Exec通道执行,不需要处理交互式会话,逻辑更简单安全。 - 生产环境安全配置:不要跳过主机密钥检查,改用密钥登录代替明文密码,避免安全风险。
内容的提问来源于stack exchange,提问作者hmdrummer
相关产品推荐
相关产品推荐

