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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.14 17:15:10