推薦使用Hutool的Jsch工具包(它用的連接池的技術) 一、SSH遠程連接伺服器 SSH更多見:http://t.csdnimg.cn/PrsNv 推薦連接工具:FinalShell、Xshell、secureCRT、PuTTY (https://zhuanlan.zhihu.com/p/659 ...
- 推薦使用Hutool的Jsch工具包(它用的連接池的技術)
一、SSH遠程連接伺服器
-
SSH更多見:http://t.csdnimg.cn/PrsNv
-
推薦連接工具:FinalShell、Xshell、secureCRT、PuTTY
(https://zhuanlan.zhihu.com/p/659276574)
1、SSH(Secure Shell)主要有兩大功能
1、遠程命令執行:SSH允許用戶在遠程主機上執行命令。用戶可以通過SSH連接到遠程主機,然後在命令行界面輸入命令,就像直接在遠程主機的控制臺上操作一樣。這是SSH最常用的功能,它使得用戶可以方便地管理和維護遠程主機。
2、安全的文件傳輸:SSH提供了SFTP(SSH File Transfer Protocol)和SCP(Secure Copy)兩種文件傳輸協議,用於在本地主機和遠程主機之間安全地傳輸文件。這兩種協議都使用SSH的安全機制,可以保護文件在傳輸過程中的安全性和完整性。
3、除了這兩大功能,SSH還有一些其他的功能,例如埠轉發和動態埠轉發(也稱為SOCKS代理),它們可以用來建立安全的網路連接,或者繞過網路限制。
二、JNA、Process和JSch
- JNA
JNA主要用於在Java程式中調用本地庫的函數,而不是用於遠程連接到其他系統。如果你的Java程式正在Linux系統上運行,你可以使用JNA來調用Linux的本地庫函數,包括那些可以獲取文件和文件夾路徑的函數。然而,如果你的Java程式正在一個系統(如Windows)上運行,你不能使用JNA來連接到另一個系統(如Linux)。
- JSch
如果你需要從一個運行在Windows上的Java程式連接到一個Linux系統,你可能需要使用其他的工具或庫。例如,你可以使用SSH(安全殼層)來遠程連接到Linux系統,然後執行命令來獲取文件和文件夾的路徑。在Java中,有一些庫可以幫助你使用SSH,如JSch和Apache MINA SSHD。
簡單理解JNA、Process和JSch
-
JNA(Java Native Access)和Process類都是Java中與本地系統交互的工具。JNA允許Java代碼直接調用本地(C/C++)庫的函數,而Process類則允許Java代碼啟動和控制操作系統的進程,例如執行shell命令。(JNA和Process是Java調用系統(Windows、Linux等)的本地函數,或者三方程式)
-
JSch是一個Java庫,它提供了SSH(Secure Shell)的Java實現,允許Java程式通過SSH協議連接到遠程系統(如Linux)。一旦連接成功,你可以通過JSch執行遠程命令,上傳和下載文件,就像直接在遠程系統上操作一樣。(JSch則是Java連接系統(Windows、Linux等)的工具,比如連接上Linux後,相當於直接操作Linux一樣)
三、Java使用SSH的包
3.1、JSch和Apache MINA SSHD
JSch和Apache MINA SSHD都是優秀的SSH庫,它們各有優點,選擇哪一個主要取決於你的具體需求。
JSch是一個成熟且廣泛使用的庫,它提供了SSH2的完整實現,包括SFTP,SCP,埠轉發等功能。JSch的API相對簡單,易於使用,而且JSch的社區活躍,有大量的教程和示例代碼可供參考。
Apache MINA SSHD則是一個更現代的庫,它基於Apache MINA,一個高性能的網路應用框架。MINA SSHD提供了SSH2的完整實現,包括SFTP,SCP,埠轉發等功能。MINA SSHD的API設計更現代,更符合Java的編程習慣,而且MINA SSHD支持非同步非阻塞IO,對於需要處理大量併發連接的應用來說,可能會有更好的性能。
總的來說,如果你需要一個簡單易用,社區支持好的SSH庫,JSch可能是一個不錯的選擇。如果你需要一個設計現代,支持非同步非阻塞IO的SSH庫,或者你已經在使用Apache MINA,那麼MINA SSHD可能更適合你。
3.2、JSch的四種認證機制:
-
密碼(本文使用):這是最常見的身份驗證方式,用戶需要提供用戶名和密碼來進行身份驗證。
-
公鑰:在這種方式中,用戶需要提供一個私鑰,JSch會使用這個私鑰來進行身份驗證。這種方式通常比基於密碼的身份驗證更安全,因為私鑰通常比密碼更難被猜測或者破解。
-
鍵盤交互:這種方式允許伺服器發送一個或多個提問給客戶端,客戶端需要回答這些問題來進行身份驗證。這種方式可以用來實現一些複雜的身份驗證流程,例如一次性密碼,或者多因素身份驗證。
-
GSSAPI:GSSAPI是一種用於安全通信的API,它支持各種不同的身份驗證機制,例如Kerberos。JSch可以使用GSSAPI來進行身份驗證,但這需要額外的庫支持。
四、JSch實現登錄Linux,遠程命令執行、SFTP下載和上傳文件
4.1、導包Jsch
- 官方的包上次更新18年(本文使用)
// jsch包
implementation 'com.jcraft:jsch:0.1.55'
- 長期維護的jsch:https://github.com/mwiede/jsch
4.2、Jsch工具類
package com.cc.jschdemo.utils;
import com.jcraft.jsch.*;
import lombok.Data;
import org.apache.commons.lang3.StringUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.stereotype.Component;
import javax.annotation.PreDestroy;
import javax.servlet.ServletOutputStream;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.*;
/**
* <p>JSch工具類</p>
* <li>交給spring管理:每個使用的地方都是單例,都是單獨的這個類。(new 也可以)</li>
*
* <li>所有方法都沒有關閉(連接、會話),需要使用方自己關閉</li>
*
* @author CC
* @since 2023/11/8
*/
@Data
@Component
public class JSchUtil {
//緩存session會話
private Session session;
//通道:執行命令
private ChannelExec channelExec;
//通道:SFTP
private ChannelSftp channelSftp;
//通道:執行複雜Shell命令
private ChannelShell channelShell;
//登陸Linux伺服器
public void loginLinux(String username, String password, String host, Integer port) {
try {
//每次都會重新初始化session
if (Objects.isNull(session) || !session.isConnected()) {
JSch jsch = new JSch();
session = jsch.getSession(username, host, port);
session.setPassword(password);
// 配置Session參數
Properties config = new Properties();
// 不進行公鑰的檢查
config.put("StrictHostKeyChecking", "no");
session.setConfig(config);
// 設置連接超時時間(s/秒)
session.setTimeout(300);
}
if (!session.isConnected()) {
// 連接到遠程伺服器
session.connect();
}
}catch(Exception e){
throw new RuntimeException("連接Linux失敗:" + e.getMessage());
}
}
//執行命令:可以多次執行,然後必須調用關閉介面
public String executeCommand(String command) {
StringBuilder result = new StringBuilder();
BufferedReader buf = null;
try {
//每次執行都創建新的通道
channelExec = (ChannelExec) session.openChannel("exec");
channelExec.setCommand(command);
//正確的流中沒有數據就走錯誤流中去拿。
InputStream in = channelExec.getInputStream();
InputStream errStream = channelExec.getErrStream();
channelExec.connect();
buf = new BufferedReader(new InputStreamReader(in));
String msg;
while ((msg = buf.readLine()) != null) {
result.append(msg);
}
if (StringUtils.isBlank(result.toString())) {
buf = new BufferedReader(new InputStreamReader(errStream));
String msgErr;
while ((msgErr = buf.readLine()) != null) {
result.append(msgErr);
}
}
}catch(Exception e){
throw new RuntimeException("關閉連接失敗(執行命令):" + e.getMessage());
}finally {
if (Objects.nonNull(buf)) {
try {
buf.close();
}catch(Exception e){
e.printStackTrace();
}
}
}
return result.toString();
}
/**
* 執行複雜shell命令
*
* @param cmds 多條命令
* @return 執行結果
* @throws Exception 連接異常
*/
public String execCmdByShell(List<String> cmds) {
String result = "";
try {
channelShell = (ChannelShell) session.openChannel("shell");
InputStream inputStream = channelShell.getInputStream();
channelShell.setPty(true);
channelShell.connect();
OutputStream outputStream = channelShell.getOutputStream();
PrintWriter printWriter = new PrintWriter(outputStream);
for (String cmd : cmds) {
printWriter.println(cmd);
}
printWriter.flush();
byte[] tmp = new byte[1024];
while (true) {
while (inputStream.available() > 0) {
int i = inputStream.read(tmp, 0, 1024);
if (i < 0) {
break;
}
String s = new String(tmp, 0, i);
if (s.contains("--More--")) {
outputStream.write((" ").getBytes());
outputStream.flush();
}
System.out.println(s);
}
if (channelShell.isClosed()) {
System.out.println("exit-status:" + channelShell.getExitStatus());
break;
}
//間隔1s後再執行
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
}
outputStream.close();
inputStream.close();
}catch(Exception e){
e.printStackTrace();
}
return result;
}
//下載除了雲伺服器的文件(你自己的伺服器):因為雲伺服器,像阿裡雲伺服器下載文件好像是一段一段給你的,不是一起給你。
public void downloadOtherFile(String remoteFileAbsolutePath, String fileName, HttpServletResponse response) {
try {
channelSftp = (ChannelSftp) session.openChannel("sftp");
channelSftp.connect();
//獲取輸入流
InputStream inputStream = channelSftp.get(remoteFileAbsolutePath);
//直接下載到本地文件
// channelSftp.get(remoteFileAbsolutePath, "D:\\Develop\\Test\\studio-3t-x64.zip");
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
response.setContentType("application/octet-stream;charset=".concat(StandardCharsets.UTF_8.name()));
response.setHeader(HttpHeaders.ACCESS_CONTROL_EXPOSE_HEADERS, HttpHeaders.CONTENT_DISPOSITION);
response.setHeader(HttpHeaders.CONTENT_DISPOSITION,
"attachment; filename=".concat(
URLEncoder.encode(fileName, StandardCharsets.UTF_8.name())
));
ServletOutputStream out = response.getOutputStream();
// 從InputStream輸入流讀取數據 並寫入到ServletOutputStream輸出流
byte[] buffer = new byte[4096];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
out.write(buffer, 0, bytesRead);
}
out.flush();
out.close();
}catch(Exception e){
throw new RuntimeException("關閉連接失敗(下載文件):" + e.getMessage());
}
}
//下載雲伺服器的文件(因為雲伺服器傳文件是一段一段的,所以不能直接像操作我們的伺服器一樣直接下載)(阿裡雲為例)
public void downloadCloudServerFile(String remoteFileAbsolutePath, String fileName, HttpServletResponse response) {
try {
channelSftp = (ChannelSftp) session.openChannel("sftp");
channelSftp.connect();
//獲取輸入流
InputStream inputStream = channelSftp.get(remoteFileAbsolutePath);
//阿裡雲應該是斷點續傳,後面研究……
}catch(Exception e){
throw new RuntimeException("關閉連接失敗(下載文件):" + e.getMessage());
}
}
//ls命令:獲取文件夾的信息
public String ls(String path){
StringBuilder sb = new StringBuilder();
try {
channelSftp = (ChannelSftp) session.openChannel("sftp");
channelSftp.connect();
Vector ls = channelSftp.ls(path);
Iterator iterator = ls.iterator();
while (iterator.hasNext()) {
Object next = iterator.next();
System.out.println(next);
sb.append(next);
}
} catch (Exception e){
throw new RuntimeException(e.getMessage());
}
return sb.toString();
}
//關閉通道:釋放資源
private void closeChannel(){
//不為空,且已經連接:關閉
if (Objects.nonNull(channelExec)) {
channelExec.disconnect();
}
if (Objects.nonNull(channelSftp)) {
channelSftp.disconnect();
}
if (Objects.nonNull(channelShell)) {
channelShell.disconnect();
}
}
/** 關閉通道、關閉會話:釋放資源
* spring銷毀前,關閉 所有會話 及 所有通道
*/
@PreDestroy
public void closeAll(){
System.out.println("我被銷毀了。。。。。。。。。。。。。。。。。。。。。。");
this.closeChannel();
if (Objects.nonNull(session) && session.isConnected()) {
session.disconnect();
}
}
}
4.2、使用Jsch工具類:執行命令
4.2.1、執行簡單命令
package com.cc.jschdemo.web.controller;
import com.cc.jschdemo.utils.JSchUtil;
import com.jcraft.jsch.ChannelExec;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
import java.io.InputStream;
import java.util.Arrays;
/**
* <p></p>
*
* @author CC
* @since 2023/11/8
*/
@RestController
@RequestMapping("/jsch")
public class JSchController {
@Resource
private JSchUtil jSchUtil;
/** <p>執行命令<p>
**/
@GetMapping
public String executeCommand() {
//登陸(預設只連接5分鐘,5分鐘後銷毀)
jSchUtil.loginLinux("伺服器賬號", "伺服器密碼", "伺服器IP", 伺服器埠);
//一、執行命令
String mkdir = jSchUtil.executeCommand("mkdir ccc");
String docker = jSchUtil.executeCommand("docker");
String dockerPs = jSchUtil.executeCommand("docker ps");
System.out.println(mkdir);
System.out.println(docker);
System.out.println(dockerPs);
//執行完,關閉連接
jSchUtil.closeAll();
return docker;
}
}
- 結果:
- 多了一個文件夾
4.2.1、執行複雜的shell命令
/** <p>執行命令<p>
**/
@PostMapping
public String execCmdByShell() {
//登陸(預設只連接5分鐘,5分鐘後銷毀)
jSchUtil.loginLinux("伺服器賬號", "伺服器密碼", "伺服器IP", 伺服器埠);
//二、執行shell腳本(可以改造成傳入的shell腳步)
jSchUtil.execCmdByShell(Arrays.asList("cd /", "ll" , "cd cc/", "mkdir ccccc222", "ll"));
//執行完,關閉連接
jSchUtil.closeAll();
return "docker";
}
- 結果
4.3、使用Jsch工具類:下載文件
4.3.1、普通伺服器下載
//下載普通伺服器的文件
@PutMapping
public void downloadOtherFile(HttpServletResponse response) {
//登陸(預設只連接5分鐘,5分鐘後銷毀)
jSchUtil.loginLinux("伺服器賬號", "伺服器密碼", "伺服器IP", 伺服器埠);
//下載文件
jSchUtil.downloadOtherFile(
"/dev/libbb/studio-3t-x64.zip",
"studio-3t-x64.zip",
response
);
//執行完,關閉連接
jSchUtil.closeAll();
}
4.3.2、阿裡雲伺服器下載
- 博主很懶,沒留下什麼……
五、Hutool工具封裝的JSch(推薦)
- Hutool使用的是JSch連接池,推薦使用……
- 博主比較懶,還沒實現……
六、總結
參考:
https://www.jb51.net/article/264152.htm
https://www.jb51.net/article/264148.htm