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;
}
}