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的配置为:
com.jcraft
jsch
0.1.51
1、jsch连接到linux的基本原理和用ssh一样,需要ip地址,端口(一般为22),用户名,密码,为了方便配置,可以把静态变量初始化在配置文件中:
#jsch配置
host.ip=192.168.1.1
host.user=root
host.port=22
host.password=123456
host.connect.timeout=5000
获取配置文件变量的工具类:
package com.personal.core.constant;
import com.personal.core.utils.PropertiesLoader;
import org.apache.commons.lang.StringUtils;
import org.springframework.core.io.DefaultResourceLoader;
import java.io.File;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* 注释:全局配置类
*
* @Author: coding99
* @Date: 16-9-2
* @Time: 下午7:33
*/
public class Global {
/**
* 当前对象实例
*/
private static Global global = new Global();
/**
* 保存全局属性值
*/
private static Map map = new HashMap();
/**
* 属性文件加载对象
*/
private static PropertiesLoader loader = new PropertiesLoader("sysConfig.properties");
/**
* 获取当前对象实例
*/
public static Global getInstance() {
return global;
}
/**
* 获取配置
* @see {fns:getConfig('adminPath')}
*/
public static String getConfig(String key) {
String value = map.get(key);
if (value == null){
value = loader.getProperty(key);
map.put(key, value != null ? value : StringUtils.EMPTY);
}
return value;
}/** * 获取工程路径 * @return */ public static String getProjectPath(){ // 如果配置了工程路径,则直接返回,否则自动获取。String projectPath = Global.getConfig("projectPath");if (StringUtils.isNotBlank(projectPath)){return projectPath;}try {File file = new DefaultResourceLoader().getResource("").getFile();if (file != null){while(true){File f = new File(file.getPath() + File.separator + "src" + File.separator + "main");if (f == null || f.exists()){break;}if (file.getParentFile() != null){file = file.getParentFile();}else{break;}}projectPath = file.toString();}} catch (IOException e) {e.printStackTrace();}return projectPath; }}
/**
* 获取工程路径
* @return
*/
public static String getProjectPath(){
// 如果配置了工程路径,则直接返回,否则自动获取。
String projectPath = Global.getConfig("projectPath");
if (StringUtils.isNotBlank(projectPath)){
return projectPath;
}
try {
File file = new DefaultResourceLoader().getResource("").getFile();
if (file != null){
while(true){
File f = new File(file.getPath() + File.separator + "src" + File.separator + "main");
if (f == null || f.exists()){
break;
}
if (file.getParentFile() != null){
file = file.getParentFile();
}else{
break;
}
}
projectPath = file.toString();
}
} catch (IOException e) {
e.printStackTrace();
}
return projectPath;
}
}
查看opt文件夹内容
ls -ltr /opt
查看opt文件夹内容
ls -ltr /opt
查看opt文件夹内容
ls -ltr /opt
3、获取脚本的方法通过一个工具类读取这个配置文件,用dom4j进行解析,获取相应指定shell name的脚本,例如
package com.personal.core.utils;
import com.personal.core.constant.Global;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import java.io.File;
import java.util.List;
/**
* 注释
*
* @Author: coding99
* @Date: 16-9-2
* @Time: 下午9:31
*/
public class ShellConfigUtil {
private static final String SHELL_ENV = Global.getConfig("environment");
private static final String DEFAULT_SHELL_CONFIG = ShellConfigUtil.class.getResource("/").getPath()+"shell.xml";
private ShellConfigUtil() {
}
/**
* 取得脚本
* @param shellName
* @return
* @throws Exception
*/
public static String getShell(String shellName)throws Exception{
String shellContent = "";
List shells = getShellEelements();
for(Element shell : shells) {
String name = shell.attributeValue("name");
String env = shell.attributeValue("env");
//把shell数组遍历
if(shellName.equals(name) && SHELL_ENV.equals(env)) {
shellContent = getShellResult(shell);
}
}
return shellContent;
}
/**
* 解析xml
* @return
* @throws Exception
*/
public static List getShellEelements() throws Exception{
SAXReader saxReader = new SAXReader();//创建读取配置文件的对象
Document document = saxReader.read(new File(DEFAULT_SHELL_CONFIG));//开始读取配置文件
Element element = document.getRootElement();
List shellElements = element.elements("shell");
return shellElements;
}
/**
* 获取脚本内容
* @param shell
* @return
*/
public static String getShellResult(Element shell) {
String result = "";
List steps = shell.elements("step");
for (Element step : steps) {
result = step.getText();
}
return result;
}
public static void main(String[] args) throws Exception{
ShellConfigUtil.getShell("ls");
}
}
4、JSCH实现原理:
jsch进行连接服务器连接时可以看做时java的jdbc连接,首先我们需要实例化一个jsch对象,再利用这个对象 根据用户名,主机ip,端口获取一个Session对象,设置好相应的参数后,就进行连接,创建连接后这个session是一直可用的,所以不需要关闭。之后我们需要在session上建立channel通道
shell - ChannelShell exec - ChannelExec
direct-tcpip - ChannelDirectTCPIP sftp - ChannelSftp subsystem - ChannelSubsystem
其中,ChannelShell和ChannelExec比较类似,都可以作为执行Shell脚本的Channel类型。它们有一个比较重要的区别:ChannelShell可以看作是执行一个交互式的Shell,而ChannelExec是执行一个Shell脚本。
实现远程命令操作我们需要创建ChannelExec对象。
实现文件上传下载我们需要实现ChannelSftp对象。
package com.personal.core.utils;
import com.jcraft.jsch.*;
import com.personal.core.constant.Global;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.nio.charset.Charset;
import java.util.*;
/**
* 注释
*
* @Author: coding99
* @Date: 16-9-2
* @Time: 下午7:33
*/
public class JschUtil {
private static final Logger logger = LoggerFactory.getLogger(JschUtil.class);
private String charset = "UTF-8"; // 设置编码格式,可以根据服务器的编码设置相应的编码格式
private JSch jsch;
private Session session;
Channel channel = null;
ChannelSftp chSftp = null;
//初始化配置参数
private String jschHost = Global.getConfig("host.ip");
private int jschPort = Integer.parseInt(Global.getConfig("host.port"));
private String jschUserName = Global.getConfig("host.user");
private String jschPassWord = Global.getConfig("host.password");
private int jschTimeOut = Integer.parseInt(Global.getConfig("host.connect.timeout"));
/**
* 静态内部类实现单例模式
*/
private static class LazyHolder {
private static final JschUtil INSTANCE = new JschUtil();
}
private JschUtil() {
}
/**
* 获取实例
* @return
*/
public static final JschUtil getInstance() {
return LazyHolder.INSTANCE;
}
/**
* 连接到指定的服务器
* @return
* @throws JSchException
*/
public boolean connect() throws JSchException {
jsch = new JSch();// 创建JSch对象
boolean result = false;
try{
long begin = System.currentTimeMillis();//连接前时间
logger.debug("Try to connect to jschHost = " + jschHost + ",as jschUserName = " + jschUserName + ",as jschPort = " + jschPort);
session = jsch.getSession(jschUserName, jschHost, jschPort);// // 根据用户名,主机ip,端口获取一个Session对象
session.setPassword(jschPassWord); // 设置密码
Properties config = new Properties();
config.put("StrictHostKeyChecking", "no");
session.setConfig(config);// 为Session对象设置properties
session.setTimeout(jschTimeOut);//设置连接超时时间
session.connect();
logger.debug("Connected successfully to jschHost = " + jschHost + ",as jschUserName = " + jschUserName + ",as jschPort = " + jschPort);
long end = System.currentTimeMillis();//连接后时间
logger.debug("Connected To SA Successful in {} ms", (end-begin));
result = session.isConnected();
}catch(Exception e){
logger.error(e.getMessage(), e);
}finally{
if(result){
logger.debug("connect success");
}else{
logger.debug("connect failure");
}
}
if(!session.isConnected()) {
logger.error("获取连接失败");
}
return session.isConnected();
}
/**
* 关闭连接
*/
public void close() {
if(channel != null && channel.isConnected()){
channel.disconnect();
channel=null;
}
if(session!=null && session.isConnected()){
session.disconnect();
session=null;
}
}
/**
* 脚本是同步执行的方式
* 执行脚本命令
* @param command
* @return
*/
public Map execCmmmand(String command) throws Exception{
Map mapResult = new HashMap();
logger.debug(command);
StringBuffer result = new StringBuffer();//脚本返回结果
BufferedReader reader = null;
int returnCode = -2;//脚本执行退出状态码
try {
channel = session.openChannel("exec");
((ChannelExec) channel).setCommand(command);
channel.setInputStream(null);
((ChannelExec) channel).setErrStream(System.err);
channel.connect();//执行命令 等待执行结束
InputStream in = channel.getInputStream();
reader = new BufferedReader(new InputStreamReader(in, Charset.forName(charset)));
String res="";
while((res=reader.readLine()) != null){
result.append(res+"\n");
logger.debug(res);
}
returnCode = channel.getExitStatus();
mapResult.put("returnCode",returnCode);
mapResult.put("result",result.toString());
} catch (IOException e) {
logger.error(e.getMessage(),e);
} catch (JSchException e) {
logger.error(e.getMessage(), e);
} finally {
try {
reader.close();
} catch (IOException e) {
logger.error(e.getMessage(), e);
}
}
return mapResult;
}
/**
* 上传文件
*
* @param directory 上传的目录,有两种写法
* 1、如/opt,拿到则是默认文件名
* 2、/opt/文件名,则是另起一个名字
* @param uploadFile 要上传的文件 如/opt/xxx.txt
*/
public void upload(String directory, String uploadFile) {
try {
logger.debug("Opening Channel.");
channel = session.openChannel("sftp"); // 打开SFTP通道
channel.connect(); // 建立SFTP通道的连接
chSftp = (ChannelSftp) channel;
File file = new File(uploadFile);
long fileSize = file.length();
/*方法一*/
OutputStream out = chSftp.put(directory, new FileProgressMonitor(fileSize), ChannelSftp.OVERWRITE); // 使用OVERWRITE模式
byte[] buff = new byte[1024 * 256]; // 设定每次传输的数据块大小为256KB
int read;
if (out != null) {
logger.debug("Start to read input stream");
InputStream is = new FileInputStream(uploadFile);
do {
read = is.read(buff, 0, buff.length);
if (read > 0) {
out.write(buff, 0, read);
}
out.flush();
} while (read >= 0);
logger.debug("input stream read done.");
}
// chSftp.put(uploadFile, directory, new FileProgressMonitor(fileSize), ChannelSftp.OVERWRITE); //方法二
// chSftp.put(new FileInputStream(src), dst, new FileProgressMonitor(fileSize), ChannelSftp.OVERWRITE); //方法三
logger.debug("成功上传文件至"+directory);
} catch (Exception e) {
e.printStackTrace();
}finally {
chSftp.quit();
if (channel != null) {
channel.disconnect();
logger.debug("channel disconnect");
}
}
}
/**
* 下载文件
*
* @param directory 下载的目录,有两种写法
* 1、如/opt,拿到则是默认文件名
* 2、/opt/文件名,则是另起一个名字
* @param downloadFile 要下载的文件 如/opt/xxx.txt
*
*/
public void download(String directory, String downloadFile) {
try {
logger.debug("Opening Channel.");
channel = session.openChannel("sftp"); // 打开SFTP通道
channel.connect(); // 建立SFTP通道的连接
chSftp = (ChannelSftp) channel;
SftpATTRS attr = chSftp.stat(downloadFile);
long fileSize = attr.getSize();
OutputStream out = new FileOutputStream(directory);
InputStream is = chSftp.get(downloadFile, new MyProgressMonitor());
byte[] buff = new byte[1024 * 2];
int read;
if (is != null) {
logger.debug("Start to read input stream");
do {
read = is.read(buff, 0, buff.length);
if (read > 0) {
out.write(buff, 0, read);
}
out.flush();
} while (read >= 0);
logger.debug("input stream read done.");
}
//chSftp.get(downloadFile, directory, new FileProgressMonitor(fileSize)); // 代码段1
//chSftp.get(downloadFile, out, new FileProgressMonitor(fileSize)); // 代码段2
logger.debug("成功下载文件至"+directory);
} catch (Exception e) {
e.printStackTrace();
} finally {
chSftp.quit();
if (channel != null) {
channel.disconnect();
logger.debug("channel disconnect");
}
}
}
/**
* 删除文件
* @param deleteFile 要删除的文件
*/
public void delete(String deleteFile) {
try {
connect();//建立服务器连接
logger.debug("Opening Channel.");
channel = session.openChannel("sftp"); // 打开SFTP通道
channel.connect(); // 建立SFTP通道的连接
chSftp = (ChannelSftp) channel;
chSftp.rm(deleteFile);
logger.debug("成功删除文件"+deleteFile);
} catch (Exception e) {
e.printStackTrace();
}
}
public String getCharset() {
return charset;
}
public void setCharset(String charset) {
this.charset = charset;
}
public static void main(String[] args) throws Exception{
JschUtil jschUtil = JschUtil.getInstance();
boolean isConnected = false;
isConnected = jschUtil.connect();
if(isConnected == true){
/*上传文件*/
jschUtil.upload("/opt/123456.png","/home/sky/Desktop/resizeApi.png");
/*执行命令*/
String command = "ls -ltr /opt";
// String command = ShellConfigUtil.getShell("ls");
Map result = jschUtil.execCmmmand(command);
System.out.println(result.get("result").toString());
/*下载文件*/
jschUtil.download("/opt/123456.png","/opt/123456.png");
jschUtil.close();
}
}
}
package com.personal.core.utils;
import java.text.DecimalFormat;
import java.util.Timer;
import java.util.TimerTask;
import com.jcraft.jsch.SftpProgressMonitor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 注释
*
* @Author: coding99
* @Date: 16-9-2
* @Time: 下午8:29
*/
public class FileProgressMonitor extends TimerTask implements SftpProgressMonitor {
private static final Logger logger = LoggerFactory.getLogger(FileProgressMonitor.class);
private long progressInterval = 5 * 1000; // 默认间隔时间为5秒
private boolean isEnd = false; // 记录传输是否结束
private long transfered; // 记录已传输的数据总大小
private long fileSize; // 记录文件总大小
private Timer timer; // 定时器对象
private boolean isScheduled = false; // 记录是否已启动timer记时器
public FileProgressMonitor(long fileSize) {
this.fileSize = fileSize;
}
@Override
public void run() {
if (!isEnd()) { // 判断传输是否已结束
logger.debug("Transfering is in progress.");
long transfered = getTransfered();
if (transfered != fileSize) { // 判断当前已传输数据大小是否等于文件总大小
logger.debug("Current transfered: " + transfered + " bytes");
sendProgressMessage(transfered);
} else {
logger.debug("File transfering is done.");
setEnd(true); // 如果当前已传输数据大小等于文件总大小,说明已完成,设置end
}
} else {
logger.debug("Transfering done. Cancel timer.");
stop(); // 如果传输结束,停止timer记时器
return;
}
}
public void stop() {
logger.debug("Try to stop progress monitor.");
if (timer != null) {
timer.cancel();
timer.purge();
timer = null;
isScheduled = false;
}
logger.debug("Progress monitor stoped.");
}
public void start() {
logger.debug("Try to start progress monitor.");
if (timer == null) {
timer = new Timer();
}
timer.schedule(this, 1000, progressInterval);
isScheduled = true;
logger.debug("Progress monitor started.");
}
/**
* 打印progress信息
* @param transfered
*/
private void sendProgressMessage(long transfered) {
if (fileSize != 0) {
double d = ((double)transfered * 100)/(double)fileSize;
DecimalFormat df = new DecimalFormat( "#.##");
logger.debug("Sending progress message: " + df.format(d) + "%");
} else {
logger.debug("Sending progress message: " + transfered);
}
}
/**
* 实现了SftpProgressMonitor接口的count方法
*/
public boolean count(long count) {
if (isEnd()) return false;
if (!isScheduled) {
start();
}
add(count);
return true;
}
/**
* 实现了SftpProgressMonitor接口的end方法
*/
public void end() {
setEnd(true);
logger.debug("transfering end.");
}
private synchronized void add(long count) {
transfered = transfered + count;
}
private synchronized long getTransfered() {
return transfered;
}
public synchronized void setTransfered(long transfered) {
this.transfered = transfered;
}
private synchronized void setEnd(boolean isEnd) {
this.isEnd = isEnd;
}
private synchronized boolean isEnd() {
return isEnd;
}
public void init(int op, String src, String dest, long max) {
// Not used for putting InputStream
}
}
package com.personal.core.utils;
import com.jcraft.jsch.SftpProgressMonitor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 注释
*
* @Author: coding99
* @Date: 16-9-2
* @Time: 下午8:36
*/
public class MyProgressMonitor implements SftpProgressMonitor {
private static final Logger logger = LoggerFactory.getLogger(MyProgressMonitor.class);
private long transfered;
@Override
public boolean count(long count) {
transfered = transfered + count;
logger.debug("Currently transferred total size: " + transfered + " bytes");
return true;
}
@Override
public void end() {
logger.debug("Transferring done.");
}
@Override
public void init(int op, String src, String dest, long max) {
logger.debug("Transferring begin.");
}
}
注
:工具类主要封装了三个方法