jsch 是ssh2的一個純Java實現。它允許你連接到一個sshd 服務器,使用端口轉發,X11轉發,文件傳輸等等。你可以將它的功能集成到你自己的 程序中。同時該項目也提供一個J2ME版本用來在手機上直連SSHD服務器。
一般連接到服務器有兩種方式:
1、通過用戶名和密碼連接,缺點(出於安全需要,一般服務器的密碼會定期修改,程序部署後將不得不經常更新配置文件中的密 碼。)
2、通過用戶名和ssh private key file連接,缺點(因爲Java程序必須和private key file在同一臺機器上,將服務器的private key file複製到本地後,本地機器的安全措施可能會使private key file被竊取,威脅服務器安全。)
jsch官網地址爲http://www.jcraft.com/jsch/,實現jsch功能需要添加一個jsch-0.1.51.jar包,官網有一些例子可直接下載參考。
maven的配置爲:
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jsch</artifactId>
<version>0.1.51</version>
</dependency>
jsch中ChannelShell與ChannelExec區別
ChannelShell
對於ChannelShell,以輸入流的形式,提供命令和輸入這些命令,這就像在本地計算機上使用交互式shell
(它通常用於:交互式使用)
缺點:
建立的是shell管道,並且我們又使用readLine方法,當命令全部執行完畢後,遠程端並不知道執行完畢,還在等待接受數據,所以呢reandLine就一直阻塞在那裏。
即便你換成read方法還是一樣的,因爲shell管道本身就是交互模式的。要想停止,有兩種方式:
①人爲的發送一個exit命令,告訴程序本次交互結束啦
②使用字節流中的available方法,來獲取數據的總大小,然後循環去讀。
InputStream inputStream = channel.getInputStream();//從遠程端到達的所有數據都能從這個流中讀取到
OutputStream outputStream = channel.getOutputStream();//寫入該流的所有數據都將發送到遠程端。
//使用PrintWriter流的目的就是爲了使用println這個方法
//好處就是不需要每次手動給字符串加\n
PrintWriter printWriter = new PrintWriter(outputStream);
String cmd = "ls";
printWriter.println(cmd);
String cmd2 = "cd /home/jenkins/workspace/ggservice";
printWriter.println(cmd2);
String cmd3 = "ls";
printWriter.println(cmd3);
printWriter.println("exit");//加上個就是爲了,結束本次交互
printWriter.flush();
BufferedReader in = new BufferedReader(new InputStreamReader(inputStream));
String msg = null;
while((msg = in.readLine())!=null){
System.out.println(msg);
}
in.close();
ChannelShell channel = (ChannelShell) session.openChannel("shell");
channel.connect();
//從遠程端到達的所有數據都能從這個流中讀取到
InputStream in = channel.getInputStream();
//寫入該流的所有數據都將發送到遠程端。
OutputStream outputStream = channel.getOutputStream();
byte[] tmp=new byte[1024];
while(true){
while(in.available()>0){
int i=in.read(tmp, 0, 1024);
if(i<0)break;
System.out.print(new String(tmp, 0, i));
}
if(channel.isClosed()){
if(in.available()>0) continue;
System.out.println("exit-status: "+channel.getExitStatus());
break;
}
}
ChannelExec
對於ChannelExec,在調用connect()方法之前這個命令提供了setCommand()方法,
並且這些命令作爲輸入將以輸入流的形式被髮送出去。
(通常,你只能有調用setCommand()方法一次,多次調用只有最後一次生效),
但是你可以使用普通shell的分隔符(&,&&,|,||,; , \n, 複合命令)來提供多個命令。
這就像在你本機上執行一個shell腳本一樣(當然,如果一個命令本身就是個交互式shell,這樣就像ChannelShell)
明顯:使用命令通道更容易,因爲您不需要處理命令提示符。
問題:只能執行一條語句,有些在shell中的語句還不能執行,比如配置了環境變量:JAVA_HOME,
在shell模式下面執行:java -version,是可以的
在ChannelExec模式下面執行:java -version,是會報錯 bash: java: command not found
ChannelExec是不會去獲取環境變量裏面的(剛剛學習,也不知道爲什麼),只能進入到目錄下面執行纔可以:
/usr/jdk1.8.0_121/bin/java -version
下面是測試的全量代碼(提交spark的時候一定要在spark-env.sh裏面把環境變量配置全,java、hadoop、spark、sacla的):
package com.spark.demo.sub;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.log4j.Logger;
import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.Session;
public class Exec {
private static Logger logger = Logger.getLogger(Exec.class);
private static final String USER = "root";
private static final String PASSWORD = "******";
private static final String HOST = "192.168.03.13";
private static final int DEFAULT_SSH_PORT = 22;
public static void main(final String[] arg) {
try {
JSch jsch = new JSch();
Session session = jsch.getSession(USER, HOST, DEFAULT_SSH_PORT);
session.setPassword(PASSWORD);
session.setConfig("StrictHostKeyChecking", "no");// 第一次訪問服務器不用輸入yes
session.connect();
String command = "/usr/dh/spark/spark-2.3.4/bin/spark-submit --class wordcount.JavaWordCount --master yarn --deploy-mode cluster --driver-memory 1G --num-executors 3 --executor-memory 1G --executor-cores 1 hdfs://10.121.55.67:9000/dh/wordcount-two-1.0.0.jar";
//
command = "/usr/jdk1.8.0_121/bin/java -version";
Channel channel = session.openChannel("exec");
((ChannelExec) channel).setCommand(command);
channel.setInputStream(null);
((ChannelExec) channel).setErrStream(System.err);
InputStream in = channel.getInputStream();
channel.connect();
getIn(in, channel);
//getIn2(in, channel);
channel.disconnect();
session.disconnect();
} catch (Exception e) {
logger.info(e);
}
}
private static void getIn(final InputStream in, final Channel channel) throws IOException {
byte[] tmp = new byte[1024];
while (true) {
while (in.available() > 0) {
int i = in.read(tmp, 0, 1024);
if (i < 0) {
break;
}
logger.info(new String(tmp, 0, i));
}
if (channel.isClosed()) {
if (in.available() > 0) {
continue;
}
logger.info("exit-status: " + channel.getExitStatus());
break;
}
try {
Thread.sleep(1000);
} catch (Exception ee) {
}
}
}
/**
*獲取sub提交時候的appId
*/
private static String getIn2(final InputStream in, final Channel channel) throws IOException {
// 返回結果存在ExtInputStream中,所以需要合併兩個返回結果
BufferedReader input2 = new BufferedReader(new InputStreamReader(in));
// 接收遠程服務器執行命令的結果
String line;
String pattern = "Submitted application application_(.*)";
String appid = null;
Pattern p = Pattern.compile(pattern);
boolean isDealed = false;
while ((line = input2.readLine()) != null) {
Matcher m = p.matcher(line);
if (!isDealed && m.find()) {
// 這裏雖然拿到了Yarn Id,但是不能立即返回,因爲該通道還是處於暫用狀態,必須阻塞線程
appid = "application_" + m.group(1);
isDealed = true;
}
}
System.out.println("appid:" + appid);
return appid;
}
}