使用 SSHJ 在 Java 中实现远程命令执行和文件传输
在 Java 开发中,经常需要远程连接服务器并执行一些 Shell 命令或进行文件传输。SSHJ 是一个功能强大的 Java 库,可以轻松实现这些功能。本文将介绍如何使用 SSHJ 编写一个 SSH 工具类来处理远程命令执行和文件传输。
1. 引入依赖
首先,我们需要在 pom.xml
中添加 SSHJ 的依赖:
<dependencies>
<dependency>
<groupId>com.hierynomus</groupId>
<artifactId>sshj</artifactId>
<version>0.31.0</version>
</dependency>
</dependencies>
2. 建立工具类
创建一个名为 SSHClientUtil
的工具类,该类包含以下功能:
- 连接和断开 SSH 会话
- 执行单条或多条命令
- 上传和下载文件
- 验证文件的完整性
- 列出远程目录文件
import lombok.extern.slf4j.Slf4j;
import lombok.var;
import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.common.IOUtils;
import net.schmizz.sshj.sftp.FileAttributes;
import net.schmizz.sshj.sftp.RemoteResourceInfo;
import net.schmizz.sshj.sftp.SFTPClient;
import net.schmizz.sshj.transport.verification.PromiscuousVerifier;
import net.schmizz.sshj.xfer.FileSystemFile;
import java.io.*;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.TimeUnit;
@Slf4j
public class SSHClientUtil {
private String ip, username, password;
private Integer port;
private SSHClient sshClient;
// 构造函数初始化连接参数
public SSHClientUtil(String ip, String username, String password, Integer port) {
this.ip = ip;
this.username = username;
this.password = password;
this.port = port;
}
// 打开SSH连接
public void openConnection() throws IOException {
sshClient = new SSHClient();
sshClient.addHostKeyVerifier(new PromiscuousVerifier());
sshClient.connect(ip, port);
sshClient.authPassword(username, password);
sshClient.setConnectTimeout(30000); // 设置连接超时时间
sshClient.setTimeout(1200000); // 设置读取超时时间
sshClient.getConnection().getKeepAlive().setKeepAliveInterval(5); // 设置保持活动间隔
log.info("Connected to {}", ip);
}
// 关闭SSH连接
public void closeConnection() {
if (sshClient != null) {
try {
sshClient.disconnect();
sshClient.close();
log.info("Connection closed");
} catch (IOException e) {
log.error("Error closing SSH connection: {}", e.getMessage());
}
}
}
// 执行单条命令
public String executeCommand(String command) throws IOException {
try (var session = sshClient.startSession()) {
try (var cmd = session.exec(command)) {
String output = IOUtils.readFully(cmd.getInputStream()).toString(); // 读取命令输出
cmd.join(5, TimeUnit.SECONDS); // 等待命令执行完成
log.info("Executed command: {}. Output: {}", command, output);
return output;
}
}
}
// 执行多条命令
public void executeCommands(List<String> commands) throws IOException {
for (String command : commands) {
executeCommand(command);
}
}
// 上传文件并可选验证哈希值
public boolean uploadFile(String localFilePath, String remoteFilePath, String localFileHash) throws IOException {
try (SFTPClient sftp = sshClient.newSFTPClient()) {
sftp.put(new FileSystemFile(localFilePath), remoteFilePath); // 上传文件
log.info("Uploaded file from {} to {}", localFilePath, remoteFilePath);
if (localFileHash != null && !localFileHash.isEmpty()) {
return verifyRemoteFileHash(remoteFilePath, localFileHash); // 验证远程文件哈希值
}
return true;
}
}
// 下载文件并验证文件大小
public boolean downloadFile(String remoteFilePath, String localFilePath) throws IOException {
try (SFTPClient sftp = sshClient.newSFTPClient()) {
sftp.get(remoteFilePath, new FileSystemFile(localFilePath)); // 下载文件
log.info("Downloaded file from {} to {}", remoteFilePath, localFilePath);
return verifyFileSize(localFilePath, remoteFilePath); // 验证文件大小
}
}
// 验证远程文件哈希值
public boolean verifyRemoteFileHash(String remoteFilePath, String localFileHash) throws IOException {
String remoteCommand = String.format("md5sum %s | awk '{ print $1 }'", remoteFilePath);
String remoteFileHash = executeCommand(remoteCommand).trim(); // 执行远程命令获取哈希值
boolean result = localFileHash.equals(remoteFileHash); // 比较哈希值
if (result) {
log.info("File hash verification successful for file: {}", remoteFilePath);
} else {
log.error("File hash mismatch for file: {}. Local hash: {}, Remote hash: {}", remoteFilePath, localFileHash, remoteFileHash);
}
return result;
}
// 验证文件大小
private boolean verifyFileSize(String localFilePath, String remoteFilePath) throws IOException {
try (SFTPClient sftp = sshClient.newSFTPClient()) {
File localFile = new File(localFilePath);
long localFileSize = localFile.length(); // 获取本地文件大小
FileAttributes remoteFile = sftp.stat(remoteFilePath);
long remoteFileSize = remoteFile.getSize(); // 获取远程文件大小
boolean result = localFileSize == remoteFileSize; // 比较文件大小
if (result) {
log.info("File size verification successful for file: {}", localFilePath);
} else {
log.error("File size mismatch for file: {}. Local size: {}, Remote size: {}", localFilePath, localFileSize, remoteFileSize);
}
return result;
}
}
// 列出远程目录文件
public List<RemoteResourceInfo> listFiles(String remoteDirectory) throws IOException {
try (SFTPClient sftp = sshClient.newSFTPClient()) {
List<RemoteResourceInfo> files = sftp.ls(remoteDirectory); // 列出远程目录文件
log.info("Listed files in directory: {}", remoteDirectory);
return files;
}
}
}
3. 使用示例
以下是如何使用 SSHClientUtil
工具类进行远程命令执行和文件传输的示例:
public static void main(String[] args) {
SSHClientUtil sshClientUtil = new SSHClientUtil("192.168.xx.xx", "root", "*******", 22);
try {
sshClientUtil.openConnection();
// 执行单条命令
String output = sshClientUtil.executeCommand("ls -l");
System.out.println(output);
// 执行多条命令
sshClientUtil.executeCommands(Arrays.asList("mkdir testDir", "touch testDir/testFile"));
// 上传文件
boolean isUploadSuccessful = sshClientUtil.uploadFile("local/path/to/file.txt", "remote/path/to/file.txt", "localFileHashValue");
if (isUploadSuccessful) {
System.out.println("File uploaded successfully and hash verified.");
} else {
System.out.println("File upload failed or hash mismatch.");
}
// 下载文件
boolean isDownloadSuccessful = sshClientUtil.downloadFile("remote/path/to/file.txt", "local/path/to/file.txt");
if (isDownloadSuccessful) {
System.out.println("File downloaded successfully and size verified.");
} else {
System.out.println("File download failed or size mismatch.");
}
// 列出远程目录文件
var files = sshClientUtil.listFiles("remote/path/to/directory");
files.forEach(file -> System.out.println(file.getName()));
} catch (IOException e) {
e.printStackTrace();
} finally {
sshClientUtil.closeConnection();
}
}
4. 使用优化
在实际开发过程中,性能是一个重要的考量因素。对于 SSHJ 库,以下是一些性能测试和优化的建议:
- 连接复用:对于频繁的操作,建议复用 SSH 连接,而不是每次操作都新建连接。
- 并发执行:使用多线程或异步编程来处理并发任务,提高效率。
- 批量操作:对于文件传输,可以使用批量传输来减少开销。
总结
通过本文的介绍,了解了如何使用 SSHJ 库来实现远程命令执行和文件传输。希望本文能对您有所帮助。在生产环境中,建议根据具体需求进一步优化和扩展工具类,以满足性能和安全性的要求。