使用JSch库时能否自定义OutputStream?连接冻结问题求助
SSH会话连接冻结问题的原因与修复
核心问题分析
你的代码里的CustomOutputStream存在两个致命问题:
- 没有持有原始Socket的
OutputStream引用,调用super.write()实际是调用OutputStream类的默认实现,该实现直接抛出IOException,根本没有把SSH协议的数据发送到服务器。 - 处理字节数组时忽略了
off和len参数,直接使用整个b数组,会导致数据截取错误,破坏SSH协议的字节流结构。
这两个问题直接导致SSH握手阶段的数据无法正常发送,会话一直等待服务器响应,最终出现冻结。
修复步骤与代码示例
1. 修正CustomOutputStream,关联原始输出流并正确处理数据
让自定义流持有原始Socket的输出流引用,处理完Proto封装后将数据转发到原始流:
public class CustomOutputStream extends OutputStream { private final OutputStream originalOut; // 构造方法传入原始输出流 public CustomOutputStream(OutputStream originalOut) { this.originalOut = originalOut; } @Override public void write(int b) throws IOException { write(new byte[]{(byte) b}, 0, 1); } @Override public void write(byte[] b, int off, int len) throws IOException { // 截取实际需要发送的字节段,必须尊重off和len,避免数据越界 byte[] sshData = new byte[len]; System.arraycopy(b, off, sshData, 0, len); // 用Proto封装SSH数据 JschProto.JschContents jschContents = JschProto.JschContents.newBuilder() .setContent(ByteString.copyFrom(sshData)) .setType("ssh_payload") .build(); // 将封装后的Proto数据写入原始输出流 originalOut.write(jschContents.toByteArray()); } // 必须重写close和flush,确保原始流的资源被正确处理 @Override public void close() throws IOException { originalOut.close(); } @Override public void flush() throws IOException { originalOut.flush(); } }
2. 修改SocketFactory,传入原始输出流
在自定义SocketFactory的getOutputStream方法中,把Socket的原始输出流传给CustomOutputStream:
session.setSocketFactory(new SocketFactory() { @Override public Socket createSocket(String host, int port) throws IOException, UnknownHostException { return new Socket(host, port); } @Override public InputStream getInputStream(Socket socket) throws IOException { // 必须对应实现自定义InputStream来解封服务器返回的Proto数据 // 否则JSch读取到的是封装后的Proto字节,无法解析SSH协议 return new CustomInputStream(socket.getInputStream()); } @Override public OutputStream getOutputStream(Socket socket) throws IOException { return new CustomOutputStream(socket.getOutputStream()); } });
3. 补充实现自定义InputStream(必填步骤)
服务器返回的响应会被Proto封装(假设两端协议一致),因此需要自定义InputStream读取Proto数据并解析出原始SSH数据:
public class CustomInputStream extends InputStream { private final InputStream originalIn; public CustomInputStream(InputStream originalIn) { this.originalIn = originalIn; } @Override public int read() throws IOException { byte[] buf = new byte[1]; int readLen = read(buf, 0, 1); return readLen == -1 ? -1 : buf[0] & 0xFF; } @Override public int read(byte[] b, int off, int len) throws IOException { // 读取并解析Proto封装的数据(需和你的Proto协议逻辑匹配) JschProto.JschContents jschContents = JschProto.JschContents.parseFrom(originalIn); byte[] sshData = jschContents.getContent().toByteArray(); // 将解析后的SSH数据复制到目标数组 int copyLen = Math.min(sshData.length, len); System.arraycopy(sshData, 0, b, off, copyLen); return copyLen; } @Override public void close() throws IOException { originalIn.close(); } }
额外注意事项
- Proto封装和解封的逻辑必须两端完全一致,否则会导致数据解析失败。
- SSH协议基于字节流,需确保封装后的数据包不会被拆分或合并导致解析错误,必要时可在Proto中添加长度字段标识原始数据长度。
- 测试时可先去掉Proto封装逻辑,仅转发原始数据,确认SSH会话能正常连接后,再逐步加入Proto处理代码,方便排查问题。
内容的提问来源于stack exchange,提问作者seunggyu lee
相关产品推荐
相关产品推荐

