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

Apache MINA模拟SSH服务器命令响应读取问题及自定义命令需求

解决Apache MINA SSHD单元测试的两个核心问题

看起来你遇到了两个关键问题:密钥交换算法不匹配导致连接失败,以及无法获取命令响应。咱们一步步来解决:

1. 修复密钥交换算法不匹配错误

你手动连接时收到的Unable to negotiate with ::1 port 22999: no matching key exchange method found. Their offer: diffie-hellman-group1-sha1错误,是因为默认的SSHD配置没有启用这个旧的密钥交换算法。需要在服务器初始化时添加对应的工厂:

修改你的setUpSSHd()方法,添加diffie-hellman-group1-sha1的KeyExchange工厂:

@Before
public void setUpSSHd() {
    sshd = SshServer.setUpDefaultServer();
    sshd.setPort(22999);
    sshd.setKeyPairProvider(new SimpleGeneratorHostKeyProvider("hostkey.ser"));
    sshd.setPasswordAuthenticator((username, password, session) -> true); // 简化lambda写法

    // 添加缺失的密钥交换算法
    List<NamedFactory<KeyExchange>> keyExchangeFactories = new ArrayList<>(sshd.getKeyExchangeFactories());
    keyExchangeFactories.add(new BuiltinDHFactories.DHG1());
    sshd.setKeyExchangeFactories(keyExchangeFactories);

    try {
        sshd.start();
    } catch (Exception e) {
        e.printStackTrace();
        fail("Failed to start SSH server");
    }
}

2. 自定义命令响应(替代Shell模式,更适合单元测试)

你当前用的是CHANNEL_SHELL,但服务器端没有配置任何Shell实现,所以无法处理命令输入。对于单元测试来说,直接自定义命令的响应更简单高效——不用模拟完整的Shell环境,只需要对特定命令返回预期结果即可。

步骤1:实现自定义CommandFactory

创建一个自定义的CommandFactory,用来处理你要测试的命令(比如dir):

private class CustomCommandFactory implements CommandFactory {
    @Override
    public Command createCommand(String command) {
        return new Command() {
            private OutputStream out;
            private OutputStream err;
            private InputStream in;
            private ExitCallback exitCallback;

            @Override
            public void setInputStream(InputStream in) {
                this.in = in;
            }

            @Override
            public void setOutputStream(OutputStream out) {
                this.out = out;
            }

            @Override
            public void setErrorStream(OutputStream err) {
                this.err = err;
            }

            @Override
            public void setExitCallback(ExitCallback exitCallback) {
                this.exitCallback = exitCallback;
            }

            @Override
            public void start(Environment env) throws IOException {
                // 处理命令,返回自定义响应
                String response;
                if (command.trim().equalsIgnoreCase("dir")) {
                    response = "file1.txt\nfile2.log\nsubdir\n";
                } else {
                    response = "Unknown command: " + command + "\n";
                }

                // 写入响应到输出流
                out.write(response.getBytes());
                out.flush();

                // 命令执行完成,退出
                exitCallback.onExit(0);
            }

            @Override
            public void destroy() {
                // 清理资源
                try {
                    in.close();
                    out.close();
                    err.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        };
    }
}

步骤2:在服务器配置中设置这个CommandFactory

在setUpSSHd()方法中添加一行:

sshd.setCommandFactory(new CustomCommandFactory());

3. 调整客户端代码,使用EXEC模式执行命令

CHANNEL_SHELL适合交互式Shell,而CHANNEL_EXEC专门用于执行单个命令并获取输出,更适合你的单元测试场景。修改testCommands()方法:

@Test
public void testCommands() throws Exception {
    SshClient client = SshClient.setUpDefaultClient();
    // 客户端也需要启用对应的密钥交换算法
    List<NamedFactory<KeyExchange>> clientKeyExchanges = new ArrayList<>(client.getKeyExchangeFactories());
    clientKeyExchanges.add(new BuiltinDHFactories.DHG1());
    client.setKeyExchangeFactories(clientKeyExchanges);

    client.start();
    ClientSession session = null;
    try {
        // 建立连接并认证
        session = client.connect("localhost", 22999).await().getSession();
        boolean authSuccess = session.authPassword("none", "none").await().isSuccess();
        assertTrue("Authentication failed", authSuccess);
        System.out.println("Connection established");

        // 使用EXEC模式执行命令
        try (ClientChannel channel = session.createChannel(ClientChannel.CHANNEL_EXEC)) {
            // 设置要执行的命令
            ((ChannelExec) channel).setCommand("dir");

            ByteArrayOutputStream out = new ByteArrayOutputStream();
            ByteArrayOutputStream err = new ByteArrayOutputStream();
            channel.setOut(out);
            channel.setErr(err);

            channel.open();
            // 等待命令执行完成
            channel.waitFor(ClientChannel.CLOSED, 5000);

            // 输出并验证结果
            String output = out.toString();
            System.out.println("Command output:\n" + output);
            assertTrue("Expected 'file1.txt' in output", output.contains("file1.txt"));
            assertTrue("Expected 'subdir' in output", output.contains("subdir"));
        }
    } catch (Exception e) {
        e.printStackTrace();
        fail("Test failed due to exception: " + e.getMessage());
    } finally {
        if (session != null) session.close(true);
        client.stop();
    }
}

关键说明

  • 密钥交换算法:客户端和服务器都需要启用diffie-hellman-group1-sha1才能匹配,所以两边都要添加对应的工厂。
  • CommandFactory vs Shell:自定义CommandFactory让你完全控制命令的响应,避免了模拟完整Shell的复杂性,非常适合单元测试场景。
  • EXEC模式:相比SHELL模式,EXEC模式更直接,执行单个命令后会自动关闭通道,不需要处理提示符、输入流的复杂交互。

这样修改后,你应该能成功连接服务器,执行dir命令并获取预期的响应,轻松完成单元测试的验证。

内容的提问来源于stack exchange,提问作者JohnnyGat

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 06:38:06