JSch是Java Secure Channel的缩写。JSch是一个SSH2的纯Java实现。它允许你连接到一个SSH服务器,并且可以使用端口转发,X11转发,文件传输等,当然你也可以集成它的功能到你自己的应用程序。
本文只介绍如何使用JSch实现的SFTP功能。
SFTP是Secure File Transfer Protocol的缩写,安全文件传送协议。可以为传输文件提供一种安全的加密方法。SFTP 为 SSH的一部份,是一种传输文件到服务器的安全方式。SFTP是使用加密传输认证信息和传输的数据,所以,使用SFTP是非常安全的。但是,由于这种传输方式使用了加密/解密技术,所以传输效率比普通的FTP要低得多,如果您对网络安全性要求更高时,可以使用SFTP代替FTP。(来自百度的解释)
要使用JSch,需要下载它的jar包,请从官网下载它:http://www.jcraft.com/jsch/
ChannelSftp类是JSch实现SFTP核心类,它包含了所有SFTP的方法,如:
put(): 文件上传
get(): 文件下载
cd(): 进入指定目录
ls(): 得到指定目录下的文件列表
rename(): 重命名指定文件或目录
rm(): 删除指定文件
mkdir(): 创建目录
rmdir(): 删除目录
等等(这里省略了方法的参数,put和get都有多个重载方法,具体请看源代码,这里不一一列出。)
JSch支持三种文件传输模式:
OVERWRITE
完全覆盖模式,这是JSch的默认文件传输模式,即如果目标文件已经存在,传输的文件将完全覆盖目标文件,产生新的文件。
RESUME
恢复模式,如果文件已经传输一部分,这时由于网络或其他任何原因导致文件传输中断,如果下一次传输相同的文件,
则会从上一次中断的地方续传。
APPEND
追加模式,如果目标文件已存在,传输的文件将在目标文件后追加。
创建ChannelSftp对象
编写一个工具类,根据ip,用户名及密码得到一个SFTP channel对象,即ChannelSftp的实例对象,在应用程序中就可以使用该对象来调用SFTP的各种操作方法。
1 packagecom.longyg.sftp;2
3 importjava.util.Map;4 importjava.util.Properties;5 importorg.apache.log4j.Logger;6 importcom.jcraft.jsch.Channel;7 importcom.jcraft.jsch.ChannelSftp;8 importcom.jcraft.jsch.JSch;9 importcom.jcraft.jsch.JSchException;10 importcom.jcraft.jsch.Session;11 public classSFTPChannel {12 Session session = null;13 Channel channel = null;14 private static final Logger LOG = Logger.getLogger(SFTPChannel.class.getName());15 public ChannelSftp getChannel(Map sftpDetails, int timeout) throwsJSchException {16 String ftpHost =sftpDetails.get(SFTPConstants.SFTP_REQ_HOST);17 String port =sftpDetails.get(SFTPConstants.SFTP_REQ_PORT);18 String ftpUserName =sftpDetails.get(SFTPConstants.SFTP_REQ_USERNAME);19 String ftpPassword =sftpDetails.get(SFTPConstants.SFTP_REQ_PASSWORD);20 int ftpPort =SFTPConstants.SFTP_DEFAULT_PORT;21 if (port != null && !port.equals("")) {22 ftpPort =Integer.valueOf(port);23 }24 JSch jsch = new JSch(); //创建JSch对象
25 session = jsch.getSession(ftpUserName, ftpHost, ftpPort); //根据用户名,主机ip,端口获取一个Session对象
26 LOG.debug("Session created.");27 if (ftpPassword != null) {28 session.setPassword(ftpPassword); //设置密码
29 }30 Properties config = newProperties();31 config.put("StrictHostKeyChecking", "no");32 session.setConfig(config); //为Session对象设置properties
33 session.setTimeout(timeout); //设置timeout时间
34 session.connect(); //通过Session建立链接
35 LOG.debug("Session connected.");36 LOG.debug("Opening Channel.");37 channel = session.openChannel("sftp"); //打开SFTP通道
38 channel.connect(); //建立SFTP通道的连接
39 LOG.debug("Connected successfully to ftpHost = " + ftpHost + ",as ftpUserName = " +ftpUserName40 + ", returning: " +channel);41 return(ChannelSftp) channel;42 }43 public void closeChannel() throwsException {44 if (channel != null) {45 channel.disconnect();46 }47 if (session != null) {48 session.disconnect();49 }50 }51 }
SFTPChannel.java
SFTPConstants是一个静态成员变量类:
1 packagecom.longyg.sftp;2
3 public classSFTPConstants {4 public static final String SFTP_REQ_HOST = "host";5 public static final String SFTP_REQ_PORT = "port";6 public static final String SFTP_REQ_USERNAME = "username";7 public static final String SFTP_REQ_PASSWORD = "password";8 public static final int SFTP_DEFAULT_PORT = 22;9 public static final String SFTP_REQ_LOC = "location";10 }
SFTPConstants.java
文件上传
实现文件上传可以调用ChannelSftp对象的put方法。ChannelSftp中有12个put方法的重载方法:
public void put(String src, String dst)
将本地文件名为src的文件上传到目标服务器,目标文件名为dst,若dst为目录,则目标文件名将与src文件名相同。
采用默认的传输模式:OVERWRITE
public void put(String src, String dst, int mode)
将本地文件名为src的文件上传到目标服务器,目标文件名为dst,若dst为目录,则目标文件名将与src文件名相同。
指定文件传输模式为mode(mode可选值为:ChannelSftp.OVERWRITE,ChannelSftp.RESUME,
ChannelSftp.APPEND)
public void put(String src, String dst, SftpProgressMonitor monitor)
将本地文件名为src的文件上传到目标服务器,目标文件名为dst,若dst为目录,则目标文件名将与src文件名相同。
采用默认的传输模式:OVERWRITE
并使用实现了SftpProgressMonitor接口的monitor对象来监控文件传输的进度。
public void put(String src, String dst,
SftpProgressMonitor monitor, int mode)
将本地文件名为src的文件上传到目标服务器,目标文件名为dst,若dst为目录,则目标文件名将与src文件名相同。
指定传输模式为mode
并使用实现了SftpProgressMonitor接口的monitor对象来监控文件传输的进度。
public void put(InputStream src, String dst)
将本地的input stream对象src上传到目标服务器,目标文件名为dst,dst不能为目录。
采用默认的传输模式:OVERWRITE
public void put(InputStream src, String dst, int mode)
将本地的input stream对象src上传到目标服务器,目标文件名为dst,dst不能为目录。
指定文件传输模式为mode
public void put(InputStream src, String dst, SftpProgressMonitor monitor)
将本地的input stream对象src上传到目标服务器,目标文件名为dst,dst不能为目录。
采用默认的传输模式:OVERWRITE
并使用实现了SftpProgressMonitor接口的monitor对象来监控传输的进度。
public void put(InputStream src, String dst,
SftpProgressMonitor monitor, int mode)
将本地的input stream对象src上传到目标服务器,目标文件名为dst,dst不能为目录。
指定文件传输模式为mode
并使用实现了SftpProgressMonitor接口的monitor对象来监控传输的进度。
public OutputStream put(String dst)
该方法返回一个输出流,可以向该输出流中写入数据,最终将数据传输到目标服务器,目标文件名为dst,dst不能为目录。
采用默认的传输模式:OVERWRITE
public OutputStream put(String dst, final int mode)
该方法返回一个输出流,可以向该输出流中写入数据,最终将数据传输到目标服务器,目标文件名为dst,dst不能为目录。
指定文件传输模式为mode
public OutputStream put(String dst, final SftpProgressMonitor monitor, final int mode)
该方法返回一个输出流,可以向该输出流中写入数据,最终将数据传输到目标服务器,目标文件名为dst,dst不能为目录。
指定文件传输模式为mode
并使用实现了SftpProgressMonitor接口的monitor对象来监控传输的进度。
public OutputStream put(String dst, final SftpProgressMonitor monitor, final int mode, long offset)
该方法返回一个输出流,可以向该输出流中写入数据,最终将数据传输到目标服务器,目标文件名为dst,dst不能为目录。
指定文件传输模式为mode
并使用实现了SftpProgressMonitor接口的monitor对象来监控传输的进度。
offset指定了一个偏移量,从输出流偏移offset开始写入数据。
应用实例:
1 packagecom.longyg.sftp;2
3 importjava.util.HashMap;4 importjava.util.Map;5 importcom.jcraft.jsch.ChannelSftp;6 public classSFTPTest {7 publicSFTPChannel getSFTPChannel() {8 return newSFTPChannel();9 }10 /**
11 *@paramargs12 *@throwsException13 */
14 public static void main(String[] args) throwsException {15 SFTPTest test = newSFTPTest();16 Map sftpDetails = new HashMap();17 //设置主机ip,端口,用户名,密码
18 sftpDetails.put(SFTPConstants.SFTP_REQ_HOST, "10.9.167.55");19 sftpDetails.put(SFTPConstants.SFTP_REQ_USERNAME, "root");20 sftpDetails.put(SFTPConstants.SFTP_REQ_PASSWORD, "arthur");21 sftpDetails.put(SFTPConstants.SFTP_REQ_PORT, "22");22
23 String src = "D:\\DevSoft\\HB-SnagIt1001.rar"; //本地文件名
24 String dst = "/home/omc/ylong/sftp/HB-SnagIt1001.rar"; //目标文件名
25
26 SFTPChannel channel =test.getSFTPChannel();27 ChannelSftp chSftp = channel.getChannel(sftpDetails, 60000);28
29 /**
30 * 代码段131 OutputStream out = chSftp.put(dst, ChannelSftp.OVERWRITE); // 使用OVERWRITE模式32 byte[] buff = new byte[1024 * 256]; // 设定每次传输的数据块大小为256KB33 int read;34 if (out != null) {35 System.out.println("Start to read input stream");36 InputStream is = new FileInputStream(src);37 do {38 read = is.read(buff, 0, buff.length);39 if (read > 0) {40 out.write(buff, 0, read);41 }42 out.flush();43 } while (read >= 0);44 System.out.println("input stream read done.");45 }46 **/
47
48 chSftp.put(src, dst, ChannelSftp.OVERWRITE); //代码段249
50 //chSftp.put(new FileInputStream(src), dst, ChannelSftp.OVERWRITE);//代码段3
51
52 chSftp.quit();53 channel.closeChannel();54 }55 }
SFTPTest.java
注:请分别将代码段1,代码段2,代码段3取消注释,运行程序来进行测试。这三段代码分别演示了如何使用JSch的不同的put方法来进行文件上传。
代码段1:采用向put方法返回的输出流中写入数据的方式来传输文件。 需要由程序来决定写入什么样的数据,这里是将本地文件的输入流写入输出流。采用这种方式的好处是,可以自行设定每次写入输出流的数据块大小,如本示例中的语句:
byte[] buff = new byte[1024 * 256]; //设定每次传输的数据块大小为256KB
代码段2:直接将本地文件名为src的文件上传到目标服务器,目标文件名为dst。(注:使用这个方法时,dst可以是目录,当dst是目录时,上传后的目标文件名将与src文件名相同)
代码段3:将本地文件名为src的文件输入流上传到目标服务器,目标文件名为dst。
这三段代码实现的功能是一样的,都是将本地的文件src上传到了服务器的dst文件。使用时可根据具体情况选择使用哪种实现方式。
监控传输进度
从前面的介绍中知道,JSch支持在文件传输时对传输进度的监控。可以实现JSch提供的SftpProgressMonitor接口来完成这个功能。
SftpProgressMonitor接口类的定义为:
1 packagecom.jcraft.jsch;2
3 public interfaceSftpProgressMonitor{4 public static final int PUT=0;5 public static final int GET=1;6 void init(int op, String src, String dest, longmax);7 boolean count(longcount);8 voidend();9 }
SftpProgressMonitor.java
init():当文件开始传输时,调用init方法。
count(): 当每次传输了一个数据块后,调用count方法,count方法的参数为这一次传输的数据块大小。
end():当传输结束时,调用end方法。
下面是一个简单的实现:
1 packagecom.longyg.sftp;2
3 importcom.jcraft.jsch.SftpProgressMonitor;4
5 public class MyProgressMonitor implementsSftpProgressMonitor {6 private longtransfered;7 @Override8 public boolean count(longcount) {9 transfered = transfered +count;10 System.out.println("Currently transferred total size: " + transfered + " bytes");11 return true;12 }13 @Override14 public voidend() {15 System.out.println("Transferring done.");16 }17 @Override18 public void init(int op, String src, String dest, longmax) {19 System.out.println("Transferring begin.");20 }21 }
MyProgressMonitor.java
此时如果改变SFTPTest main方法里调用的put方法,即可实现监控传输进度:
1 packagecom.longyg.sftp;2
3 importjava.util.HashMap;4 importjava.util.Map;5 importcom.jcraft.jsch.ChannelSftp;6
7 public classSFTPTest {8 publicSFTPChannel getSFTPChannel() {9 return newSFTPChannel();10 }11 /**
12 *@paramargs13 *@throwsException14 */
15 public static void main(String[] args) throwsException {16 SFTPTest test = newSFTPTest();17 Map sftpDetails = new HashMap();18 //设置主机ip,端口,用户名,密码
19 sftpDetails.put(SFTPConstants.SFTP_REQ_HOST, "10.9.167.55");20 sftpDetails.put(SFTPConstants.SFTP_REQ_USERNAME, "root");21 sftpDetails.put(SFTPConstants.SFTP_REQ_PASSWORD, "arthur");22 sftpDetails.put(SFTPConstants.SFTP_REQ_PORT, "22");23
24 String src = "D:\\DevSoft\\HB-SnagIt1001.rar"; //本地文件名
25 String dst = "/home/omc/ylong/sftp/HB-SnagIt1001.rar"; //目标文件名
26
27 SFTPChannel channel =test.getSFTPChannel();28 ChannelSftp chSftp = channel.getChannel(sftpDetails, 60000);29
30 /**
31 * 代码段132 OutputStream out = chSftp.put(dst, new MyProgressMonitor(), ChannelSftp.OVERWRITE); // 使用OVERWRITE模式33 byte[] buff = new byte[1024 * 256]; // 设定每次传输的数据块大小为256KB34 int read;35 if (out != null) {36 System.out.println("Start to read input stream");37 InputStream is = new FileInputStream(src);38 do {39 read = is.read(buff, 0, buff.length);40 if (read > 0) {41 out.write(buff, 0, read);42 }43 out.flush();44 } while (read >= 0);45 System.out.println("input stream read done.");46 }47 **/
48
49 chSftp.put(src, dst, new MyProgressMonitor(), ChannelSftp.OVERWRITE); //代码段250
51 //chSftp.put(new FileInputStream(src), dst, new MyProgressMonitor(), ChannelSftp.OVERWRITE);//代码段3
52
53 chSftp.quit();54 channel.closeChannel();55 }56 }
SFTPTest.java
注意修改的内容仅仅是put方法,在put方法中增加了SftpProgressMonitor的实现类对象monitor作为参数,即添加了对进度监控的支持。
运行,输出结果如下:
1 Start to read input stream2 Currently transferred total size: 262144bytes3 Currently transferred total size: 524288bytes4 Currently transferred total size: 786432bytes5 Currently transferred total size: 1048576bytes6 Currently transferred total size: 1310720bytes7 Currently transferred total size: 1572864bytes8 Currently transferred total size: 1835008bytes9 Currently transferred total size: 2097152bytes10 Currently transferred total size: 2359296bytes11 Currently transferred total size: 2621440bytes12 Currently transferred total size: 2883584bytes13 Currently transferred total size: 3145728bytes14 Currently transferred total size: 3407872bytes15 Currently transferred total size: 3670016bytes16 Currently transferred total size: 3848374bytes17 input stream read done.
当然这个SftpProgressMonitor的实现实在太简单。JSch每次传输一个数据块,就会调用count方法来实现主动进度通知。
现在我们希望每间隔一定的时间才获取一下文件传输的进度。。。看看下面的SftpProgressMonitor实现:
1 packagecom.longyg.sftp;2
3 importjava.text.DecimalFormat;4 importjava.util.Timer;5 importjava.util.TimerTask;6 importcom.jcraft.jsch.SftpProgressMonitor;7
8 public class FileProgressMonitor extends TimerTask implementsSftpProgressMonitor {9
10 private long progressInterval = 5 * 1000; //默认间隔时间为5秒
11
12 private boolean isEnd = false; //记录传输是否结束
13
14 private long transfered; //记录已传输的数据总大小
15
16 private long fileSize; //记录文件总大小
17
18 private Timer timer; //定时器对象
19
20 private boolean isScheduled = false; //记录是否已启动timer记时器
21
22 public FileProgressMonitor(longfileSize) {23 this.fileSize =fileSize;24 }25
26 @Override27 public voidrun() {28 if (!isEnd()) { //判断传输是否已结束
29 System.out.println("Transfering is in progress.");30 long transfered =getTransfered();31 if (transfered != fileSize) { //判断当前已传输数据大小是否等于文件总大小
32 System.out.println("Current transfered: " + transfered + " bytes");33 sendProgressMessage(transfered);34 } else{35 System.out.println("File transfering is done.");36 setEnd(true); //如果当前已传输数据大小等于文件总大小,说明已完成,设置end
37 }38 } else{39 System.out.println("Transfering done. Cancel timer.");40 stop(); //如果传输结束,停止timer记时器
41 return;42 }43 }44
45 public voidstop() {46 System.out.println("Try to stop progress monitor.");47 if (timer != null) {48 timer.cancel();49 timer.purge();50 timer = null;51 isScheduled = false;52 }53 System.out.println("Progress monitor stoped.");54 }55
56 public voidstart() {57 System.out.println("Try to start progress monitor.");58 if (timer == null) {59 timer = newTimer();60 }61 timer.schedule(this, 1000, progressInterval);62 isScheduled = true;63 System.out.println("Progress monitor started.");64 }65
66 /**
67 * 打印progress信息68 *@paramtransfered69 */
70 private void sendProgressMessage(longtransfered) {71 if (fileSize != 0) {72 double d = ((double)transfered * 100)/(double)fileSize;73 DecimalFormat df = new DecimalFormat( "#.##");74 System.out.println("Sending progress message: " + df.format(d) + "%");75 } else{76 System.out.println("Sending progress message: " +transfered);77 }78 }79 /**
80 * 实现了SftpProgressMonitor接口的count方法81 */
82 public boolean count(longcount) {83 if (isEnd()) return false;84 if (!isScheduled) {85 start();86 }87 add(count);88 return true;89 }90 /**
91 * 实现了SftpProgressMonitor接口的end方法92 */
93 public voidend() {94 setEnd(true);95 System.out.println("transfering end.");96 }97
98 private synchronized void add(longcount) {99 transfered = transfered +count;100 }101
102 private synchronized longgetTransfered() {103 returntransfered;104 }105
106 public synchronized void setTransfered(longtransfered) {107 this.transfered =transfered;108 }109
110 private synchronized void setEnd(booleanisEnd) {111 this.isEnd =isEnd;112 }113
114 private synchronized booleanisEnd() {115 returnisEnd;116 }117 public void init(int op, String src, String dest, longmax) {118 //Not used for putting InputStream
119 }120 }
FileProgressMonitor.java
再次修改SFTPTest main方法里的put方法,改为使用新的SftpProgressMonitor的实现类对象monitor作为参数,注意新的monitor对象的构造函数需要传入文件大小作为参数:
1 packagecom.longyg.sftp;2
3 importjava.io.File;4 importjava.util.HashMap;5 importjava.util.Map;6 importcom.jcraft.jsch.ChannelSftp;7
8 public classSFTPTest {9 publicSFTPChannel getSFTPChannel() {10 return newSFTPChannel();11 }12 /**
13 *@paramargs14 *@throwsException15 */
16 public static void main(String[] args) throwsException {17 SFTPTest test = newSFTPTest();18 Map sftpDetails = new HashMap();19 //设置主机ip,端口,用户名,密码
20 sftpDetails.put(SFTPConstants.SFTP_REQ_HOST, "10.9.167.55");21 sftpDetails.put(SFTPConstants.SFTP_REQ_USERNAME, "root");22 sftpDetails.put(SFTPConstants.SFTP_REQ_PASSWORD, "arthur");23 sftpDetails.put(SFTPConstants.SFTP_REQ_PORT, "22");24
25 String src = "D:\\DevSoft\\HB-SnagIt1001.rar"; //本地文件名
26 String dst = "/home/omc/ylong/sftp/HB-SnagIt1001.rar"; //目标文件名
27
28 SFTPChannel channel =test.getSFTPChannel();29 ChannelSftp chSftp = channel.getChannel(sftpDetails, 60000);30
31 File file = newFile(src);32 long fileSize =file.length();33
34 /**
35 * 代码段136 OutputStream out = chSftp.put(dst, new FileProgressMonitor(fileSize), ChannelSftp.OVERWRITE); // 使用OVERWRITE模式37 byte[] buff = new byte[1024 * 256]; // 设定每次传输的数据块大小为256KB38 int read;39 if (out != null) {40 System.out.println("Start to read input stream");41 InputStream is = new FileInputStream(src);42 do {43 read = is.read(buff, 0, buff.length);44 if (read > 0) {45 out.write(buff, 0, read);46 }47 out.flush();48 } while (read >= 0);49 System.out.println("input stream read done.");50 }51 **/
52
53 chSftp.put(src, dst, new FileProgressMonitor(fileSize), ChannelSftp.OVERWRITE); //代码段254
55 //chSftp.put(new FileInputStream(src), dst, new FileProgressMonitor(fileSize), ChannelSftp.OVERWRITE);//代码段3
56
57 chSftp.quit();58 channel.closeChannel();59 }60 }
SFTPTest.java
再次运行,结果输出为:
1 Try to start progress monitor.2 Progress monitor started.3 Transfering is in progress.4 Current transfered: 98019bytes5 Sending progress message: 2.55%
6 Transfering is in progress.7 Current transfered: 751479bytes8 Sending progress message: 19.53%
9 Transfering is in progress.10 Current transfered: 1078209bytes11 Sending progress message: 28.02%
12 ......13 Transfering is in progress.14 Current transfered: 3430665bytes15 Sending progress message: 89.15%
16 transfering end.17 Transfering done. Cancel timer.18 Try to stop progress monitor.19 Progress monitor stoped.
现在,程序每隔5秒钟才会打印一下进度信息。可以修改FileProgressMonitor类里的progressInterval变量的值,来修改默认的间隔时间。