本文隻介紹如何使用JSch實作的SFTP功能。
JSch是Java Secure Channel的縮寫。 JSch是一個SSH2的純Java實作。它允許你連接配接到一個SSH伺服器,并且可以使用端口轉發,X11轉發,檔案傳輸等,當然你也可以內建它的功能到你自己的應用程式。 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的各種操作方法。
SFTPChannel.java

package com.longyg.sftp;
import java.util.Map;
import java.util.Properties;
import org.apache.log4j.Logger;
import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
public class SFTPChannel {
Session session = null;
Channel channel = null;
private static final Logger LOG = Logger.getLogger(SFTPChannel.class.getName());
public ChannelSftp getChannel(Map<String, String> sftpDetails, int timeout) throws JSchException {
String ftpHost = sftpDetails.get(SFTPConstants.SFTP_REQ_HOST);
String port = sftpDetails.get(SFTPConstants.SFTP_REQ_PORT);
String ftpUserName = sftpDetails.get(SFTPConstants.SFTP_REQ_USERNAME);
String ftpPassword = sftpDetails.get(SFTPConstants.SFTP_REQ_PASSWORD);
int ftpPort = SFTPConstants.SFTP_DEFAULT_PORT;
if (port != null && !port.equals("")) {
ftpPort = Integer.valueOf(port);
}
JSch jsch = new JSch(); // 建立JSch對象
session = jsch.getSession(ftpUserName, ftpHost, ftpPort); // 根據使用者名,主機ip,端口擷取一個Session對象
LOG.debug("Session created.");
if (ftpPassword != null) {
session.setPassword(ftpPassword); // 設定密碼
}
Properties config = new Properties();
config.put("StrictHostKeyChecking", "no");
session.setConfig(config); // 為Session對象設定properties
session.setTimeout(timeout); // 設定timeout時間
session.connect(); // 通過Session建立連結
LOG.debug("Session connected.");
LOG.debug("Opening Channel.");
channel = session.openChannel("sftp"); // 打開SFTP通道
channel.connect(); // 建立SFTP通道的連接配接
LOG.debug("Connected successfully to ftpHost = " + ftpHost + ",as ftpUserName = " + ftpUserName
+ ", returning: " + channel);
return (ChannelSftp) channel;
}
public void closeChannel() throws Exception {
if (channel != null) {
channel.disconnect();
}
if (session != null) {
session.disconnect();
}
}
}

SFTPConstants是一個靜态成員變量類:
SFTPConstans.java

package com.longyg.sftp;
public class SFTPConstants {
public static final String SFTP_REQ_HOST = "host";
public static final String SFTP_REQ_PORT = "port";
public static final String SFTP_REQ_USERNAME = "username";
public static final String SFTP_REQ_PASSWORD = "password";
public static final int SFTP_DEFAULT_PORT = 22;
public static final String SFTP_REQ_LOC = "location";
}

檔案上傳 |
實作檔案上傳可以調用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開始寫入資料。 |
應用執行個體:
SFTPTest.java

package com.longyg.sftp;
import java.util.HashMap;
import java.util.Map;
import com.jcraft.jsch.ChannelSftp;
public class SFTPTest {
public SFTPChannel getSFTPChannel() {
return new SFTPChannel();
}
/**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
SFTPTest test = new SFTPTest();
Map<String, String> sftpDetails = new HashMap<String, String>();
// 設定主機ip,端口,使用者名,密碼
sftpDetails.put(SFTPConstants.SFTP_REQ_HOST, "10.9.167.55");
sftpDetails.put(SFTPConstants.SFTP_REQ_USERNAME, "root");
sftpDetails.put(SFTPConstants.SFTP_REQ_PASSWORD, "arthur");
sftpDetails.put(SFTPConstants.SFTP_REQ_PORT, "22");
String src = "D:\\DevSoft\\HB-SnagIt1001.rar"; // 本地檔案名
String dst = "/home/omc/ylong/sftp/HB-SnagIt1001.rar"; // 目标檔案名
SFTPChannel channel = test.getSFTPChannel();
ChannelSftp chSftp = channel.getChannel(sftpDetails, 60000);
/**
* 代碼段1
OutputStream out = chSftp.put(dst, ChannelSftp.OVERWRITE); // 使用OVERWRITE模式
byte[] buff = new byte[1024 * 256]; // 設定每次傳輸的資料塊大小為256KB
int read;
if (out != null) {
System.out.println("Start to read input stream");
InputStream is = new FileInputStream(src);
do {
read = is.read(buff, 0, buff.length);
if (read > 0) {
out.write(buff, 0, read);
}
out.flush();
} while (read >= 0);
System.out.println("input stream read done.");
}
**/
chSftp.put(src, dst, ChannelSftp.OVERWRITE); // 代碼段2
// chSftp.put(new FileInputStream(src), dst, ChannelSftp.OVERWRITE); // 代碼段3
chSftp.quit();
channel.closeChannel();
}
}

注:請分别将代碼段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接口類的定義為:

package com.jcraft.jsch;
public interface SftpProgressMonitor{
public static final int PUT=0;
public static final int GET=1;
void init(int op, String src, String dest, long max);
boolean count(long count);
void end();
}

init(): 當檔案開始傳輸時,調用init方法。
count(): 當每次傳輸了一個資料塊後,調用count方法,count方法的參數為這一次傳輸的資料塊大小。
end(): 當傳輸結束時,調用end方法。
下面是一個簡單的實作:
MyProgressMonitor.java

package com.longyg.sftp;
import com.jcraft.jsch.SftpProgressMonitor;
public class MyProgressMonitor implements SftpProgressMonitor {
private long transfered;
@Override
public boolean count(long count) {
transfered = transfered + count;
System.out.println("Currently transferred total size: " + transfered + " bytes");
return true;
}
@Override
public void end() {
System.out.println("Transferring done.");
}
@Override
public void init(int op, String src, String dest, long max) {
System.out.println("Transferring begin.");
}
}

此時如果改變SFTPTest main方法裡調用的put方法,即可實作監控傳輸進度:
SFTPTest.java

package com.longyg.sftp;
import java.util.HashMap;
import java.util.Map;
import com.jcraft.jsch.ChannelSftp;
public class SFTPTest {
public SFTPChannel getSFTPChannel() {
return new SFTPChannel();
}
/**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
SFTPTest test = new SFTPTest();
Map<String, String> sftpDetails = new HashMap<String, String>();
// 設定主機ip,端口,使用者名,密碼
sftpDetails.put(SFTPConstants.SFTP_REQ_HOST, "10.9.167.55");
sftpDetails.put(SFTPConstants.SFTP_REQ_USERNAME, "root");
sftpDetails.put(SFTPConstants.SFTP_REQ_PASSWORD, "arthur");
sftpDetails.put(SFTPConstants.SFTP_REQ_PORT, "22");
String src = "D:\\DevSoft\\HB-SnagIt1001.rar"; // 本地檔案名
String dst = "/home/omc/ylong/sftp/HB-SnagIt1001.rar"; // 目标檔案名
SFTPChannel channel = test.getSFTPChannel();
ChannelSftp chSftp = channel.getChannel(sftpDetails, 60000);
/**
* 代碼段1
OutputStream out = chSftp.put(dst, new MyProgressMonitor(), ChannelSftp.OVERWRITE); // 使用OVERWRITE模式
byte[] buff = new byte[1024 * 256]; // 設定每次傳輸的資料塊大小為256KB
int read;
if (out != null) {
System.out.println("Start to read input stream");
InputStream is = new FileInputStream(src);
do {
read = is.read(buff, 0, buff.length);
if (read > 0) {
out.write(buff, 0, read);
}
out.flush();
} while (read >= 0);
System.out.println("input stream read done.");
}
**/
chSftp.put(src, dst, new MyProgressMonitor(), ChannelSftp.OVERWRITE); // 代碼段2
// chSftp.put(new FileInputStream(src), dst, new MyProgressMonitor(), ChannelSftp.OVERWRITE); // 代碼段3
chSftp.quit();
channel.closeChannel();
}
}

注意修改的内容僅僅是put方法,在put方法中增加了SftpProgressMonitor的實作類對象monitor作為參數,即添加了對進度監控的支援。
運作,輸出結果如下:

Start to read input stream
Currently transferred total size: 262144 bytes
Currently transferred total size: 524288 bytes
Currently transferred total size: 786432 bytes
Currently transferred total size: 1048576 bytes
Currently transferred total size: 1310720 bytes
Currently transferred total size: 1572864 bytes
Currently transferred total size: 1835008 bytes
Currently transferred total size: 2097152 bytes
Currently transferred total size: 2359296 bytes
Currently transferred total size: 2621440 bytes
Currently transferred total size: 2883584 bytes
Currently transferred total size: 3145728 bytes
Currently transferred total size: 3407872 bytes
Currently transferred total size: 3670016 bytes
Currently transferred total size: 3848374 bytes
input stream read done.

當然這個SftpProgressMonitor的實作實在太簡單。JSch每次傳輸一個資料塊,就會調用count方法來實作主動進度通知。
現在我們希望每間隔一定的時間才擷取一下檔案傳輸的進度。。。看看下面的SftpProgressMonitor實作:

package com.longyg.sftp;
import java.text.DecimalFormat;
import java.util.Timer;
import java.util.TimerTask;
import com.jcraft.jsch.SftpProgressMonitor;
public class FileProgressMonitor extends TimerTask implements SftpProgressMonitor {
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()) { // 判斷傳輸是否已結束
System.out.println("Transfering is in progress.");
long transfered = getTransfered();
if (transfered != fileSize) { // 判斷目前已傳輸資料大小是否等于檔案總大小
System.out.println("Current transfered: " + transfered + " bytes");
sendProgressMessage(transfered);
} else {
System.out.println("File transfering is done.");
setEnd(true); // 如果目前已傳輸資料大小等于檔案總大小,說明已完成,設定end
}
} else {
System.out.println("Transfering done. Cancel timer.");
stop(); // 如果傳輸結束,停止timer記時器
return;
}
}
public void stop() {
System.out.println("Try to stop progress monitor.");
if (timer != null) {
timer.cancel();
timer.purge();
timer = null;
isScheduled = false;
}
System.out.println("Progress monitor stoped.");
}
public void start() {
System.out.println("Try to start progress monitor.");
if (timer == null) {
timer = new Timer();
}
timer.schedule(this, 1000, progressInterval);
isScheduled = true;
System.out.println("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( "#.##");
System.out.println("Sending progress message: " + df.format(d) + "%");
} else {
System.out.println("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);
System.out.println("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
}
}

再次修改SFTPTest main方法裡的put方法,改為使用新的SftpProgressMonitor的實作類對象monitor作為參數,注意新的monitor對象的構造函數需要傳入檔案大小作為參數:

package com.longyg.sftp;
import java.io.File;
import java.util.HashMap;
import java.util.Map;
import com.jcraft.jsch.ChannelSftp;
public class SFTPTest {
public SFTPChannel getSFTPChannel() {
return new SFTPChannel();
}
/**
* @param args
* @throws Exception
*/
public static void main(String[] args) throws Exception {
SFTPTest test = new SFTPTest();
Map<String, String> sftpDetails = new HashMap<String, String>();
// 設定主機ip,端口,使用者名,密碼
sftpDetails.put(SFTPConstants.SFTP_REQ_HOST, "10.9.167.55");
sftpDetails.put(SFTPConstants.SFTP_REQ_USERNAME, "root");
sftpDetails.put(SFTPConstants.SFTP_REQ_PASSWORD, "arthur");
sftpDetails.put(SFTPConstants.SFTP_REQ_PORT, "22");
String src = "D:\\DevSoft\\HB-SnagIt1001.rar"; // 本地檔案名
String dst = "/home/omc/ylong/sftp/HB-SnagIt1001.rar"; // 目标檔案名
SFTPChannel channel = test.getSFTPChannel();
ChannelSftp chSftp = channel.getChannel(sftpDetails, 60000);
File file = new File(src);
long fileSize = file.length();
/**
* 代碼段1
OutputStream out = chSftp.put(dst, new FileProgressMonitor(fileSize), ChannelSftp.OVERWRITE); // 使用OVERWRITE模式
byte[] buff = new byte[1024 * 256]; // 設定每次傳輸的資料塊大小為256KB
int read;
if (out != null) {
System.out.println("Start to read input stream");
InputStream is = new FileInputStream(src);
do {
read = is.read(buff, 0, buff.length);
if (read > 0) {
out.write(buff, 0, read);
}
out.flush();
} while (read >= 0);
System.out.println("input stream read done.");
}
**/
chSftp.put(src, dst, new FileProgressMonitor(fileSize), ChannelSftp.OVERWRITE); // 代碼段2
// chSftp.put(new FileInputStream(src), dst, new FileProgressMonitor(fileSize), ChannelSftp.OVERWRITE); // 代碼段3
chSftp.quit();
channel.closeChannel();
}
}

再次運作,結果輸出為:

Try to start progress monitor.
Progress monitor started.
Transfering is in progress.
Current transfered: 98019 bytes
Sending progress message: 2.55%
Transfering is in progress.
Current transfered: 751479 bytes
Sending progress message: 19.53%
Transfering is in progress.
Current transfered: 1078209 bytes
Sending progress message: 28.02%
......
Transfering is in progress.
Current transfered: 3430665 bytes
Sending progress message: 89.15%
transfering end.
Transfering done. Cancel timer.
Try to stop progress monitor.
Progress monitor stoped.

現在,程式每隔5秒鐘才會列印一下進度資訊。可以修改FileProgressMonitor類裡的progressInterval變量的值,來修改預設的間隔時間。