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
相关产品推荐
相关产品推荐

