目錄
1 需求背景
最近工作內容變化的很快,不到一個月的時間,已經交接了三個項目。目前遇到了一個新的需求,實現ftp的斷點傳輸功能。
那麼什麼叫斷點傳輸呢?理解起來其實很簡單。斷點,就是上次傳輸的時候突然斷了,這次接着上次的傳輸的進度繼續傳。
2 原理介紹
那麼字面意思,我們已經理解了,實際上ftp斷點傳輸是怎麼實現的呢?
實現起來也很簡單,每次傳輸前,先檢查下遠程文件是否已經存在,如果存在了,那麼我們就讀取這個文件的大小。然後比較下帶傳輸文件和現在的文件的大小。如果沒有現在的文件大,那麼可以認爲之前上傳的文件是完成的,本次不需要上傳,但是,現在的文件要是比待上傳的文件小的話,就需要重新或者繼續上傳了。選擇繼續上傳的話,就是斷點傳輸了。
3 代碼實現
那麼怎麼實現這次上傳接着上次上傳呢?我們都知道,文件操作是有一個文件指針的,每次操作的時候,指針都會移動。因此,如果想實現繼續接着上次上傳文件,只需要將待上傳的文件指針移動一個已經上傳的文件的大小即可。明白了原理,代碼也就好寫了。
package ftpTest;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.RandomAccessFile;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;
public class FtpTransTest {
// 枚舉類UploadStatus代碼
public enum UploadStatus {
Create_Directory_Fail, // 遠程服務器相應目錄創建失敗
Create_Directory_Success, // 遠程服務器闖將目錄成功
Upload_New_File_Success, // 上傳新文件成功
Upload_New_File_Failed, // 上傳新文件失敗
File_Exits, // 文件已經存在
Remote_Bigger_Local, // 遠程文件大於本地文件
Upload_From_Break_Success, // 斷點續傳成功
Upload_From_Break_Failed, // 斷點續傳失敗
Delete_Remote_Faild; // 刪除遠程文件失敗
}
// 枚舉類DownloadStatus代碼
public enum DownloadStatus {
Remote_File_Noexist, // 遠程文件不存在
Local_Bigger_Remote, // 本地文件大於遠程文件
Download_From_Break_Success, // 斷點下載文件成功
Download_From_Break_Failed, // 斷點下載文件失敗
Download_New_Success, // 全新下載文件成功
Download_New_Failed; // 全新下載文件失敗
}
public FTPClient ftpClient = new FTPClient();
private String ftpURL, username, pwd, ftpport, file1, file2;
public FtpTransTest(String _ftpURL, String _username, String _pwd,
String _ftpport, String _file1, String _file2) {
// 設置將過程中使用到的命令輸出到控制檯
ftpURL = _ftpURL;
username = _username;
pwd = _pwd;
ftpport = _ftpport;
file1 = _file1;
file2 = _file2;
}
/**
* 連接到FTP服務器
*
* @param hostname
* 主機名
* @param port
* 端口
* @param username
* 用戶名
* @param password
* 密碼
* @return 是否連接成功
* @throws IOException
*/
public boolean connect(String hostname, int port, String username,
String password) throws IOException {
ftpClient.connect(hostname, port);
ftpClient.setControlEncoding("GBK");
System.out.println("[connect] begin to connect");
if (FTPReply.isPositiveCompletion(ftpClient.getReplyCode())) {
if (ftpClient.login(username, password)) {
System.out.println("[connect] login success");
return true;
}
}
System.out.println("[connect] faild to connect.");
disconnect();
return false;
}
/**
* 從FTP服務器上下載文件,支持斷點續傳,上傳百分比彙報
*
* @param remote
* 遠程文件路徑
* @param local
* 本地文件路徑
* @return 上傳的狀態
* @throws IOException
*/
public DownloadStatus download(String remote, String local)
throws IOException {
// 設置被動模式
ftpClient.enterLocalPassiveMode();
// 設置以二進制方式傳輸
ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
DownloadStatus result;
// 檢查遠程文件是否存在
FTPFile[] files = ftpClient.listFiles(new String(
remote.getBytes("GBK"), "iso-8859-1"));
if (files.length != 1) {
System.out.println("遠程文件不存在");
return DownloadStatus.Remote_File_Noexist;
}
long remoteSize = files[0].getSize();
File f = new File(local);
// 本地存在文件,進行斷點下載
if (f.exists()) {
long localSize = f.length();
// 判斷本地文件大小是否大於遠程文件大小
if (localSize >= remoteSize) {
System.out.println("本地文件大於遠程文件,下載中止");
return DownloadStatus.Local_Bigger_Remote;
}
// 進行斷點續傳,並記錄狀態
FileOutputStream out = new FileOutputStream(f, true);
ftpClient.setRestartOffset(localSize);
InputStream in = ftpClient.retrieveFileStream(new String(remote
.getBytes("GBK"), "iso-8859-1"));
byte[] bytes = new byte[1024];
long step = remoteSize / 100;
long process = localSize / step;
int c;
while ((c = in.read(bytes)) != -1) {
out.write(bytes, 0, c);
localSize += c;
long nowProcess = localSize / step;
if (nowProcess > process) {
process = nowProcess;
if (process % 10 == 0)
System.out.println("下載進度:" + process);
// TODO 更新文件下載進度,值存放在process變量中
}
}
in.close();
out.close();
boolean isDo = ftpClient.completePendingCommand();
if (isDo) {
result = DownloadStatus.Download_From_Break_Success;
} else {
result = DownloadStatus.Download_From_Break_Failed;
}
} else {
OutputStream out = new FileOutputStream(f);
InputStream in = ftpClient.retrieveFileStream(new String(remote
.getBytes("GBK"), "iso-8859-1"));
byte[] bytes = new byte[1024];
long step = remoteSize / 100;
long process = 0;
long localSize = 0L;
int c;
while ((c = in.read(bytes)) != -1) {
out.write(bytes, 0, c);
localSize += c;
long nowProcess = localSize / step;
if (nowProcess > process) {
process = nowProcess;
if (process % 10 == 0)
System.out.println("下載進度:" + process);
// TODO 更新文件下載進度,值存放在process變量中
}
}
in.close();
out.close();
boolean upNewStatus = ftpClient.completePendingCommand();
if (upNewStatus) {
result = DownloadStatus.Download_New_Success;
} else {
result = DownloadStatus.Download_New_Failed;
}
}
return result;
}
/**
* 上傳文件到FTP服務器,支持斷點續傳
*
* @param local
* 本地文件名稱,絕對路徑
* @param remote
* 遠程文件路徑,使用/home/directory1/subdirectory/file.ext或是
* http://www.guihua.org /subdirectory/file.ext
* 按照Linux上的路徑指定方式,支持多級目錄嵌套,支持遞歸創建不存在的目錄結構
* @return 上傳結果
* @throws IOException
*/
public UploadStatus upload(String local, String remote) throws IOException {
// 設置PassiveMode傳輸
ftpClient.enterLocalPassiveMode();
// 設置以二進制流的方式傳輸
ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
ftpClient.setControlEncoding("GBK");
UploadStatus result;
// 對遠程目錄的處理
String remoteFileName = remote;
if (remote.contains("/")) {
remoteFileName = remote.substring(remote.lastIndexOf("/") + 1);
// 創建服務器遠程目錄結構,創建失敗直接返回
if (CreateDirecroty(remote, ftpClient) == UploadStatus.Create_Directory_Fail) {
return UploadStatus.Create_Directory_Fail;
}
}
System.out.println("[upload]begin to check file exists or not ");
// 檢查遠程是否存在文件
FTPFile[] files = ftpClient.listFiles(new String(remoteFileName
.getBytes("GBK"), "iso-8859-1"));
System.out.println("[upload] check over");
if (files.length == 1) {
long remoteSize = files[0].getSize();
File f = new File(local);
long localSize = f.length();
if (remoteSize == localSize) {
return UploadStatus.File_Exits;
} else if (remoteSize > localSize) {
return UploadStatus.Remote_Bigger_Local;
}
// 嘗試移動文件內讀取指針,實現斷點續傳
result = uploadFile(remoteFileName, f, ftpClient, remoteSize);
// 如果斷點續傳沒有成功,則刪除服務器上文件,重新上傳
if (result == UploadStatus.Upload_From_Break_Failed) {
if (!ftpClient.deleteFile(remoteFileName)) {
return UploadStatus.Delete_Remote_Faild;
}
result = uploadFile(remoteFileName, f, ftpClient, 0);
}
} else {
result = uploadFile(remoteFileName, new File(local), ftpClient, 0);
}
return result;
}
/**
* 斷開與遠程服務器的連接
*
* @throws IOException
*/
public void disconnect() throws IOException {
if (ftpClient.isConnected()) {
ftpClient.disconnect();
}
}
/**
* 遞歸創建遠程服務器目錄
*
* @param remote
* 遠程服務器文件絕對路徑
* @param ftpClient
* FTPClient 對象
* @return 目錄創建是否成功
* @throws IOException
*/
public UploadStatus CreateDirecroty(String remote, FTPClient ftpClient)
throws IOException {
UploadStatus status = UploadStatus.Create_Directory_Success;
String directory = remote.substring(0, remote.lastIndexOf("/") + 1);
if (!directory.equalsIgnoreCase("/")
&& !ftpClient.changeWorkingDirectory(new String(directory
.getBytes("GBK"), "iso-8859-1"))) {
// 如果遠程目錄不存在,則遞歸創建遠程服務器目錄
int start = 0;
int end = 0;
if (directory.startsWith("/")) {
start = 1;
} else {
start = 0;
}
end = directory.indexOf("/", start);
while (true) {
String subDirectory = new String(remote.substring(start, end)
.getBytes("GBK"), "iso-8859-1");
if (!ftpClient.changeWorkingDirectory(subDirectory)) {
if (ftpClient.makeDirectory(subDirectory)) {
ftpClient.changeWorkingDirectory(subDirectory);
} else {
System.out.println("創建目錄失敗");
return UploadStatus.Create_Directory_Fail;
}
}
start = end + 1;
end = directory.indexOf("/", start);
// 檢查所有目錄是否創建完畢
if (end <= start) {
break;
}
}
}
return status;
}
/** */
/**
* 上傳文件到服務器,新上傳和斷點續傳
*
* @param remoteFile
* 遠程文件名,在上傳之前已經將服務器工作目錄做了改變
* @param localFile
* 本地文件 File句柄,絕對路徑
* @param processStep
* 需要顯示的處理進度步進值
* @param ftpClient
* FTPClient 引用
* @return
* @throws IOException
*/
public UploadStatus uploadFile(String remoteFile, File localFile,
FTPClient ftpClient, long remoteSize) throws IOException {
UploadStatus status;
// 顯示進度的上傳
System.out.println("[uploadFile]begin to upload file");
long step = localFile.length() / 100;
long process = 0;
long localreadbytes = 0L;
RandomAccessFile raf = new RandomAccessFile(localFile, "r");
OutputStream out = ftpClient.appendFileStream(new String(remoteFile
.getBytes("GBK"), "iso-8859-1"));
// 斷點續傳
if (remoteSize > 0) {
ftpClient.setRestartOffset(remoteSize);
process = remoteSize / step;
raf.seek(remoteSize);
localreadbytes = remoteSize;
}
byte[] bytes = new byte[1024];
int c;
while ((c = raf.read(bytes)) != -1) {
out.write(bytes, 0, c);
localreadbytes += c;
if (localreadbytes / step != process) {
process = localreadbytes / step;
System.out.println("上傳進度:" + process);
}
}
out.flush();
raf.close();
out.close();
boolean result = ftpClient.completePendingCommand();
if (remoteSize > 0) {
status = result ? UploadStatus.Upload_From_Break_Success
: UploadStatus.Upload_From_Break_Failed;
} else {
status = result ? UploadStatus.Upload_New_File_Success
: UploadStatus.Upload_New_File_Failed;
}
return status;
}
public void run() {
try {
System.out.println("begin to connect");
this.connect(ftpURL, new java.lang.Integer(ftpport), username, pwd);
System.out.println("[run] begin to upload file.");
this.upload(file1, file2);
this.disconnect();
} catch (IOException e) {
System.out.println("連接FTP出錯:" + e.getMessage());
}
}
public static void main(String[] args) {
// TODO Auto-generated method stub
String _ftpURL = "ip";
String _ftpport = "21";
String _username = "賬戶";
String _pwd = "密碼";
String _file1 = "本地文件";
String _file2 = "遠程保存的文件和文件名";
FtpTransTest ftpTransTes = new FtpTransTest(_ftpURL, _username, _pwd, _ftpport, _file1, _file2);
ftpTransTes.run();
}
}
關注公衆號:學習更多技能哦!