Java實(shí)現(xiàn)FTP批量大文件上傳下載篇1
本文介紹了在Java中,如何使用Java現(xiàn)有的可用的庫來編寫FTP客戶端代碼,并開發(fā)成Applet控件,做成基于Web的批量、大文件的上傳下載控件。文章在比較了一系列FTP客戶庫的基礎(chǔ)上,就其中一個(gè)比較通用且功能較強(qiáng)的j-ftp類庫,對一些比較常見的功能如進(jìn)度條、斷點(diǎn)續(xù)傳、內(nèi)外網(wǎng)的映射、在Applet中回調(diào)JavaScript函數(shù)等問題進(jìn)行詳細(xì)的闡述及代碼實(shí)現(xiàn),希望通過此文起到一個(gè)拋磚引玉的作用。
一、引子
筆者在實(shí)施一個(gè)項(xiàng)目過程中出現(xiàn)了一種基于Web的文件上傳下載需求。在全?。ɑ蛉珖└鞯氐挠脩?,需要將一些文件上傳至某中心的文件服務(wù)器上。這些文件是用于一些大型的工程建設(shè),可能涉及到上千萬甚至上億的建設(shè)工程。文件具有三個(gè)鮮明的特征:一是文件大,可能達(dá)到50M;二是文件數(shù)量多,有可能15個(gè)左右;三是數(shù)據(jù)安全性方面要求數(shù)字簽名及數(shù)據(jù)加密。
首先考慮到是基于HTTP的傳輸方式。但筆者通過比較很快發(fā)現(xiàn)滿足上面的需求:
1:用HTTP協(xié)議上傳,似乎更適合web編程的方便性;上傳小于1M文件速度要比用FTP協(xié)議上傳文件略快。但對于批量及大文件的傳輸可能無能為力。當(dāng)然,它也有它的優(yōu)勢,如不像FTP那樣,必須在服務(wù)器端啟動(dòng)一個(gè)FTP服務(wù)。
2:用FTP協(xié)議上傳文件大于1M的文件速度比HTTP快。文件越大,上傳的速度就比HTTP上傳的速度快數(shù)倍。而且用java編寫程序;FTP比HTTP方便。
筆者曾經(jīng)使用VB也寫過ActiveX控件來進(jìn)行批量文件的上傳下載,其功能也很強(qiáng)大。只是由于沒有對CAB文件或OCX進(jìn)行專門的數(shù)字簽名,因此需要進(jìn)行客戶端煩瑣的設(shè)置,如設(shè)置安全站點(diǎn)、降低客戶端的安全級別等等,因而放棄了些方案。
同時(shí)考慮到在需在客戶端對文件進(jìn)行數(shù)字簽名及數(shù)據(jù)加密,決定采用Applet的方式實(shí)現(xiàn)。。文件上傳之前,在客戶端可以獲取本地USBKEY密鑰信息,完成對上傳文件的加密和簽名處理。雖然采用Applet要求在客戶端安裝JRE運(yùn)行時(shí)環(huán)境,給客戶端的管理及使用帶來一度的不方便性,但是相對起如此大量的文件及文件的安全性,這也許已經(jīng)算是比較小的代價(jià)了。
總結(jié)一下運(yùn)行的環(huán)境為:
FTP服務(wù)器端:Serv-U,專業(yè)的FTP服務(wù)器端程序,網(wǎng)上有現(xiàn)成的軟件下載,當(dāng)然讀者也可能自己寫一個(gè)服務(wù)器端的FTP文件接收程序來進(jìn)行解釋。如果沒有特殊要求或功能的話,Serv-U應(yīng)該可以滿足我們一般上傳下載的需求了;
客戶端:Java applet,當(dāng)年讓Java大火了一把的號稱與微軟的ActiveX相提并論的技術(shù)當(dāng)然,現(xiàn)在Java出了JavaFX,是不是Applet的替代品呢?
應(yīng)用環(huán)境:Internet網(wǎng),最終目的。
二、Java FTP客戶端庫的選擇
讓我們設(shè)想這樣一個(gè)情形--我們想寫一個(gè)純Java的從一個(gè)遠(yuǎn)程計(jì)算機(jī)上運(yùn)行的FTP服務(wù)器上傳下載文件的應(yīng)用程序;我們還希望能夠得到那些供下載的遠(yuǎn)程文件的基本文件信息,如文件名、數(shù)據(jù)或者文件大小等。
盡管從頭開始寫一個(gè)FTP協(xié)議處理程序是可能的,并且也許很有趣,但這項(xiàng)工作也是困難、漫長并且存在著潛在的危險(xiǎn)。因?yàn)槲覀儾辉敢庥H自花時(shí)間、精力、或者金錢去寫這樣的一個(gè)處理程序,所以我們轉(zhuǎn)而采用那些已經(jīng)存在的可重用的組件。并且很多的庫存在于網(wǎng)上。
找一個(gè)優(yōu)秀的適合我們需要的Java FTP 客戶端庫并不像看起來那么簡單。相反這是一項(xiàng)非常痛苦復(fù)雜的工作。首先找到一個(gè)FTP客戶端庫需要一些時(shí)間,其次,在我們找到所有的存在的庫后,我們該選哪一個(gè)呢?每個(gè)庫都適合不同的需求。這些庫在性能上是不等價(jià)的,并且它們的設(shè)計(jì)上有著根本上的差別。每個(gè)類庫都各具特點(diǎn)并使用不同的術(shù)語來描述它們。因而,評價(jià)和比較FTP客戶端庫是一件困難的事情。
使用可重用組件是一種值得提倡的方法,但是在這種情況下,剛開始往往是令人氣餒的。后來或許有點(diǎn)慚愧:在選擇了一個(gè)好的FTP庫后,其后的工作就非常簡單了,按簡單的規(guī)則來就行了。目前,已經(jīng)有很多公開免費(fèi)的ftp客戶端類庫,如simpleftp、J-ftp等,還有很多其他的ftpclient。如下表所示,表中未能全部列出,如讀者有更好的客戶端FTP類庫,請進(jìn)行進(jìn)一步的補(bǔ)充。

在本文中,筆者采用是J-ftp。這個(gè)是個(gè)開源的且功能十分強(qiáng)大的客戶端FTP類庫。筆者很喜歡,同時(shí)也向各位讀者推薦一下。算了免費(fèi)為它做一個(gè)廣告。
三、基本功能
1、 登陸
采用FTP進(jìn)行文件傳輸,其實(shí)本質(zhì)上還是采用Java.net.socket進(jìn)行通信。以下代碼只是類net.sf.jftp.net.FtpConnection其中一個(gè)login方法。當(dāng)然在下面的代碼,為了節(jié)省版面,以及將一些原理闡述清楚,筆者將一些沒必要的代碼去掉了,如日志等代碼。完整的代碼請參考J-ftp的源代碼或是筆者所以的示例源代碼,后面的代碼示例也同理:
public int login(String username, String password)
{
this.username = username;
this.password = password;
int status = LOGIN_OK;
jcon = new JConnection(host, port);
if(jcon.isThere())
{
in = jcon.getReader();
if(getLine(POSITIVE) == null)//FTP220_SERVICE_READY) == null)
{
ok = false;
status = OFFLINE;
}
if(!getLine(loginAck).startsWith(POSITIVE))//FTP230_LOGGED_IN))
{
if(success(POSITIVE))//FTP230_LOGGED_IN))
{
}
else
{
ok = false;
status = WRONG_LOGIN_DATA;
}
}
}
else
{
if(msg)
{
Log.debug("FTP not available!");
ok = false;
status = GENERIC_FAILED;
}
}
if(ok)
{
connected = true;
system();
binary();
String[] advSettings = new String[6];
if(getOsType().indexOf("OS/2") >= 0)
{
LIST_DEFAULT = "LIST";
}
if(LIST.equals("default"))
{
//just get the first item (somehow it knows first is the
//FTP list command)
advSettings = LoadSet.loadSet(Settings.adv_settings);
//*** IF FILE NOT FOUND, CREATE IT AND SET IT TO LIST_DEFAULT
if(advSettings == null)
{
LIST = LIST_DEFAULT;
SaveSet s = new SaveSet(Settings.adv_settings, LIST);
}
else
{
LIST = advSettings[0];
if(LIST == null)
{
LIST = LIST_DEFAULT;
}
}
}
if(getOsType().indexOf("MVS") >= 0)
{
LIST = "LIST";
}
//***
fireDirectoryUpdate(this);
fireConnectionInitialized(this);
}
else
{
fireConnectionFailed(this, new Integer(status).toString());
}
return status;
}
此登陸方法中,有一個(gè)JConnection類,此類負(fù)責(zé)建立socket套接字 ,同時(shí),此類是一種單獨(dú)的線程,這樣的好處是為了配合界面的變化,而將網(wǎng)絡(luò)的套接字連接等工作做為單獨(dú)的線程來處理,有利于界面的友好性。下面是net.sf.jftp.net.JConnection類的run方法,當(dāng)然,此線程的啟動(dòng)是在JConnection類的構(gòu)造方法中啟動(dòng)的。
public void run()
{
try
{
s = new Socket(host, port);
localPort = s.getLocalPort();
//if(time > 0) s.setSoTimeout(time);
out = new PrintStream(new BufferedOutputStream(s.getOutputStream(),
Settings.bufferSize));
in = new BufferedReader(new InputStreamReader(s.getInputStream()),
Settings.bufferSize);
isOk = true;
// }
}
catch(Exception ex)
{
ex.printStackTrace();
Log.out("WARNING: connection closed due to exception (" + host +
":" + port + ")");
isOk = false;
try
{
if((s != null) && !s.isClosed())
{
s.close();
}
if(out != null)
{
out.close();
}
if(in != null)
{
in.close();
}
}
catch(Exception ex2)
{
ex2.printStackTrace();
Log.out("WARNING: got more errors trying to close socket and streams");
}
}
established = true;
}
此run方法中的socket這里說明一下,此類實(shí)現(xiàn)客戶端套接字(也可以就叫“套接字”),套接字是兩臺機(jī)器之間的通信端點(diǎn)。套接字的實(shí)際工作由 SocketImpl 類的實(shí)例執(zhí)行。應(yīng)用程序通過更改創(chuàng)建套接字實(shí)現(xiàn)的套接字工廠可以配置它自身,以創(chuàng)建適合本地防火墻的套接字。具體的說明請參考JDK5 的API說明,最好是中文的。呵呵。
2 上傳下載
文件的上傳可以分成多線程及單線程,在單線程情況下比較簡單,而在多線程的情況下,要處理的事情要多點(diǎn),同時(shí)也要小心很多。下面是net.sf.jftp.net.FtpConnection的上傳handleUpload方法。已經(jīng)考慮了單線程及多線程兩種不同的類型。
public int handleUpload(String file, String realName)
{
if(Settings.getEnableMultiThreading() &&
(!Settings.getNoUploadMultiThreading()))
{
Log.out("spawning new thread for this upload.");
FtpTransfer t;
if(realName != null)
{
t = new FtpTransfer(host, port, getLocalPath(), getCachedPWD(),
file, username, password, Transfer.UPLOAD,
handler, listeners, realName, crlf);
}
else
{
t = new FtpTransfer(host, port, getLocalPath(), getCachedPWD(),
file, username, password, Transfer.UPLOAD,
handler, listeners, crlf);
}
lastTransfer = t;
return NEW_TRANSFER_SPAWNED;
}
else
{
if(Settings.getNoUploadMultiThreading())
{
Log.out("upload multithreading is disabled.");
}
else
{
Log.out("multithreading is completely disabled.");
}
return (realName == null) ? upload(file) : upload(file, realName);
}
}
在多線程的情況下,有一個(gè)單獨(dú)的類net.sf.jftp.net .FtpTransfer,當(dāng)然,多線程情況下,此類肯定是一個(gè)單獨(dú)的線程了。與JConnection相似,其線程的啟動(dòng)也是在構(gòu)造方法中啟動(dòng)。而在它的run方法中,進(jìn)行文件的讀取及傳輸。
public void run()
{
if(handler.getConnections().get(file) == null)
{
handler.addConnection(file, this);
}
else if(!pause)
{
Log.debug("Transfer already in progress: " + file);
work = false;
stat = 2;
return;
}
boolean hasPaused = false;
while(pause)
{
try
{
runner.sleep(100);
if(listeners != null)
{
for(int i = 0; i < listeners.size(); i++)
{
((ConnectionListener) listeners.elementAt(i)).updateProgress(file,
PAUSED,
-1);
}
}
if(!work)
{
if(listeners != null)
{
for(int i = 0; i < listeners.size(); i++)
{
((ConnectionListener) listeners.elementAt(i)).updateProgress(file,
REMOVED,
-1);
}
}
}
}
catch(Exception ex)
{
}
hasPaused = true;
}
while((handler.getConnectionSize() >= Settings.getMaxConnections()) &&
(handler.getConnectionSize() > 0) && work)
{
try
{
stat = 4;
runner.sleep(400);
if(!hasPaused && (listeners != null))
{
for(int i = 0; i < listeners.size(); i++)
{
((ConnectionListener) listeners.elementAt(i)).updateProgress(file,
QUEUED,
-1);
}
}
else
{
break;
}
}
catch(Exception ex)
{
ex.printStackTrace();
}
}
if(!work)
{
if(listeners != null)
{
for(int i = 0; i < listeners.size(); i++)
{
((ConnectionListener) listeners.elementAt(i)).updateProgress(file,
REMOVED,
-1);
}
}
handler.removeConnection(file);
stat = 3;
return;
}
started = true;
try
{
runner.sleep(Settings.ftpTransferThreadPause);
}
catch(Exception ex)
{
}
con = new FtpConnection(host, port, remotePath, crlf);
con.setConnectionHandler(handler);
con.setConnectionListeners(listeners);
int status = con.login(user, pass);
if(status == FtpConnection.LOGIN_OK)
{
File f = new File(localPath);
con.setLocalPath(f.getAbsolutePath());
if(type.equals(UPLOAD))
{
if(newName != null)
{
transferStatus = con.upload(file, newName);
}
else
{
transferStatus = con.upload(file);
}
}
else
{
transferStatus = con.download(file,this.newName);
}
}
if(!pause)
{
handler.removeConnection(file);
}
}
至于下載的過程,因?yàn)樗巧蟼鞯哪孢^程,與上傳的方法及寫法大同小異,在些出于篇幅的考慮,并沒有將代碼列出,但其思想及思路完全一樣。請讀者參考源代碼。
四、 進(jìn)度條
可以想象,如果在上傳或是下載的過程中,沒有任何的提示,用戶根本沒法判斷任務(wù)是否完成或是任務(wù)是否死了,常常由于上傳時(shí)間或下載時(shí)間過長而誤導(dǎo)用戶。因此,進(jìn)度條就顯得非常的重要與實(shí)用。
進(jìn)度條的實(shí)現(xiàn),其實(shí)說起來很簡單。就是在程序中開啟兩個(gè)線程,第一個(gè)線程用于動(dòng)態(tài)的改變界面上進(jìn)度條的value值,而第二個(gè)線程則在上傳或是下載的過程中,做成一個(gè)循環(huán),在此循環(huán)中,每次讀取一定數(shù)量如8192字節(jié)數(shù)的數(shù)據(jù)。然后傳完此數(shù)據(jù)后,調(diào)用第一個(gè)線程中的updateProgress方法,來更新界面進(jìn)度條的value值。
而上傳或下載的過程中(見上一節(jié)的FtpTransfer類的run方法),可以查看,con.upload(file, newName)方法,代碼如下所示,
public int upload(String file, String realName, InputStream in)
{
hasUploaded = true;
Log.out("ftp upload started: " + this);
int stat;
if((in == null) && new File(file).isDirectory())
{
shortProgress = true;
fileCount = 0;
baseFile = file;
dataType = DataConnection.PUTDIR;
isDirUpload = true;
stat = uploadDir(file);
shortProgress = false;
//System.out.println(fileCount + ":" + baseFile);
fireProgressUpdate(baseFile,
DataConnection.DFINISHED + ":" + fileCount, -1);
fireActionFinished(this);
fireDirectoryUpdate(this);
}
else
{
dataType = DataConnection.PUT;
stat = rawUpload(file, realName, in);
try
{
Thread.sleep(100);
}
catch(Exception ex)
{
}
fireActionFinished(this);
fireDirectoryUpdate(this);
}
try
{
Thread.sleep(500);
}
catch(Exception ex)
{
}
return stat;
}
此方法進(jìn)行負(fù)責(zé)上傳一定字節(jié)數(shù)量的內(nèi)容,其實(shí)就是調(diào)用rawUpload方法,這里沒列出,請參考源代碼,而當(dāng)傳完此字節(jié)數(shù)據(jù)后,通過調(diào)用fireActionFinished()方法來調(diào)用主線程中的updateProgressBar()方法。其實(shí)代碼如下:
protected void updateProgressBar() {
int percent = (int) (((float) lFileCompleteSize / (float) lFileSize) * 10000F);
pbFile.setValue(percent);
// System.out.println("================================================="+percent);
pbFile.setString(lFileCompleteSize / 1024L + "/" + lFileSize / 1024L
+ " kB");
percent = (int) (((float) lTotalCompleteSize / (float) lTotalSize) * 10000F);
pbTotal.setString(lTotalCompleteSize / 1024L + "/" + lTotalSize / 1024L
+ " kB");
pbTotal.setValue(percent);
repaint();
}
上面用了兩個(gè)進(jìn)度條,第一個(gè)進(jìn)度條表示當(dāng)前文件的上傳或下載進(jìn)度,第二個(gè)進(jìn)度條表示所有文件下載或上傳的進(jìn)度。同時(shí),為了產(chǎn)生進(jìn)度條的移動(dòng)或變化進(jìn)度幅度比較明顯,通過pbFile.setMaximum(10000)及pbTotal.setMaximum(10000)將進(jìn)度條的最大值設(shè)置成10000,而不是平時(shí)我們所設(shè)置的100。筆者認(rèn)為這樣比較好看,因?yàn)橛械臅r(shí)候上傳或下載的時(shí)候由于網(wǎng)絡(luò)原因,可能變化比較小。若設(shè)置成100則變化不是特別明顯。
以上就是FTP批量大文件上傳下載的基礎(chǔ)篇,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
- JAVA中使用FTPClient實(shí)現(xiàn)文件上傳下載實(shí)例代碼
- java實(shí)現(xiàn)FTP文件上傳與文件下載
- JAVA技術(shù)實(shí)現(xiàn)上傳下載文件到FTP服務(wù)器(完整)
- Java中FTPClient上傳中文目錄、中文文件名亂碼問題解決方法
- Java通過FTP服務(wù)器上傳下載文件的方法
- JAVA SFTP文件上傳、下載及批量下載實(shí)例
- Java實(shí)現(xiàn)FTP文件與文件夾的上傳和下載
- java實(shí)現(xiàn)ftp上傳 如何創(chuàng)建文件夾
- java使用ftp上傳文件示例分享
- java實(shí)現(xiàn)上傳文件到FTP
相關(guān)文章
SpringBoot使用GraphQL開發(fā)Web API實(shí)現(xiàn)方案示例講解
這篇文章主要介紹了SpringBoot使用GraphQL開發(fā)Web API實(shí)現(xiàn)方案,GraphQL是一個(gè)從服務(wù)端檢數(shù)據(jù)的查詢語言。某種程度上,是REST、SOAP、或者gRPC的替代品2023-04-04
SpringBoot基于數(shù)據(jù)庫的定時(shí)任務(wù)統(tǒng)一管理的實(shí)現(xiàn)
這篇文章主要介紹了SpringBoot基于數(shù)據(jù)庫的定時(shí)任務(wù)統(tǒng)一管理的實(shí)現(xiàn),文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧2019-12-12
SpringCloud?Nacos?+?Ribbon?調(diào)用服務(wù)的實(shí)現(xiàn)方式(兩種)
這篇文章主要介紹了SpringCloud?Nacos?+?Ribbon?調(diào)用服務(wù)的兩種方法,分別是通過代碼的方式調(diào)用服務(wù)和通過注解方式調(diào)用服務(wù),每種方式給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-03-03
詳解spring中使用solr的代碼實(shí)現(xiàn)
本篇文章主要介紹了詳解spring中使用solr的代碼實(shí)現(xiàn),小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-05-05
在java poi導(dǎo)入Excel通用工具類示例詳解
這篇文章主要給大家介紹了關(guān)于在java poi導(dǎo)入Excel通用工具類的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧。2017-09-09
Java swing讀取txt文件實(shí)現(xiàn)學(xué)生考試系統(tǒng)
這篇文章主要為大家詳細(xì)介紹了Java swing讀取txt文件實(shí)現(xiàn)學(xué)生考試系統(tǒng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2020-06-06
Java中的ArrayList和contains函數(shù)和擴(kuò)容機(jī)制(源碼詳解)
這篇文章主要介紹了Java中的ArrayList和contains函數(shù)和擴(kuò)容機(jī)制,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-10-10

