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>
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<String, String> map = new HashMap<String, String>();
- /**
- * 属性文件加载对象
- */
- 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;
- }
- }
2、为了方便脚本配置,统一管理,修改,我们可以把脚本放在一个xml里面,例如:
- <?xml version="1.0" encoding="UTF-8"?>
- <configuration>
- <!--开发环境-->
- <shell name="ls" env="dev">
- <description>查看opt文件夹内容</description>
- <step>ls -ltr /opt</step>
- </shell>
- <!--开发环境-->
- <shell name="ls" env="uat">
- <description>查看opt文件夹内容</description>
- <step>ls -ltr /opt</step>
- </shell>
- <!--生产环境-->
- <shell name="ls" env="pro">
- <description>查看opt文件夹内容</description>
- <step>ls -ltr /opt</step>
- </shell>
- </configuration>
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<Element> 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<Element> getShellEelements() throws Exception{
- SAXReader saxReader = new SAXReader();//创建读取配置文件的对象
- Document document = saxReader.read(new File(DEFAULT_SHELL_CONFIG));//开始读取配置文件
- Element element = document.getRootElement();
- List<Element> shellElements = element.elements("shell");
- return shellElements;
- }
- /**
- * 获取脚本内容
- * @param shell
- * @return
- */
- public static String getShellResult(Element shell) {
- String result = "";
- List<Element> 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<String,Object> execCmmmand(String command) throws Exception{
- Map<String,Object> mapResult = new HashMap<String,Object>();
- 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<String,Object> 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.");
- }
- }