国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > Java > 正文

Java實現(xiàn)FTP批量大文件上傳下載篇1

2019-11-26 13:55:08
字體:
供稿:網(wǎng)友

本文介紹了在Java中,如何使用Java現(xiàn)有的可用的庫來編寫FTP客戶端代碼,并開發(fā)成Applet控件,做成基于Web的批量、大文件的上傳下載控件。文章在比較了一系列FTP客戶庫的基礎(chǔ)上,就其中一個比較通用且功能較強的j-ftp類庫,對一些比較常見的功能如進度條、斷點續(xù)傳、內(nèi)外網(wǎng)的映射、在Applet中回調(diào)JavaScript函數(shù)等問題進行詳細的闡述及代碼實現(xiàn),希望通過此文起到一個拋磚引玉的作用。

一、引子

筆者在實施一個項目過程中出現(xiàn)了一種基于Web的文件上傳下載需求。在全省(或全國)各地的用戶,需要將一些文件上傳至某中心的文件服務(wù)器上。這些文件是用于一些大型的工程建設(shè),可能涉及到上千萬甚至上億的建設(shè)工程。文件具有三個鮮明的特征:一是文件大,可能達到50M;二是文件數(shù)量多,有可能15個左右;三是數(shù)據(jù)安全性方面要求數(shù)字簽名及數(shù)據(jù)加密。 

首先考慮到是基于HTTP的傳輸方式。但筆者通過比較很快發(fā)現(xiàn)滿足上面的需求: 

1:用HTTP協(xié)議上傳,似乎更適合web編程的方便性;上傳小于1M文件速度要比用FTP協(xié)議上傳文件略快。但對于批量及大文件的傳輸可能無能為力。當然,它也有它的優(yōu)勢,如不像FTP那樣,必須在服務(wù)器端啟動一個FTP服務(wù)。 

2:用FTP協(xié)議上傳文件大于1M的文件速度比HTTP快。文件越大,上傳的速度就比HTTP上傳的速度快數(shù)倍。而且用java編寫程序;FTP比HTTP方便。 

筆者曾經(jīng)使用VB也寫過ActiveX控件來進行批量文件的上傳下載,其功能也很強大。只是由于沒有對CAB文件或OCX進行專門的數(shù)字簽名,因此需要進行客戶端煩瑣的設(shè)置,如設(shè)置安全站點、降低客戶端的安全級別等等,因而放棄了些方案。 

同時考慮到在需在客戶端對文件進行數(shù)字簽名及數(shù)據(jù)加密,決定采用Applet的方式實現(xiàn)。。文件上傳之前,在客戶端可以獲取本地USBKEY密鑰信息,完成對上傳文件的加密和簽名處理。雖然采用Applet要求在客戶端安裝JRE運行時環(huán)境,給客戶端的管理及使用帶來一度的不方便性,但是相對起如此大量的文件及文件的安全性,這也許已經(jīng)算是比較小的代價了。 

總結(jié)一下運行的環(huán)境為: 

FTP服務(wù)器端:Serv-U,專業(yè)的FTP服務(wù)器端程序,網(wǎng)上有現(xiàn)成的軟件下載,當然讀者也可能自己寫一個服務(wù)器端的FTP文件接收程序來進行解釋。如果沒有特殊要求或功能的話,Serv-U應(yīng)該可以滿足我們一般上傳下載的需求了; 

客戶端:Java applet,當年讓Java大火了一把的號稱與微軟的ActiveX相提并論的技術(shù)當然,現(xiàn)在Java出了JavaFX,是不是Applet的替代品呢? 

應(yīng)用環(huán)境:Internet網(wǎng),最終目的。 

二、Java FTP客戶端庫的選擇 

讓我們設(shè)想這樣一個情形--我們想寫一個純Java的從一個遠程計算機上運行的FTP服務(wù)器上傳下載文件的應(yīng)用程序;我們還希望能夠得到那些供下載的遠程文件的基本文件信息,如文件名、數(shù)據(jù)或者文件大小等。 

盡管從頭開始寫一個FTP協(xié)議處理程序是可能的,并且也許很有趣,但這項工作也是困難、漫長并且存在著潛在的危險。因為我們不愿意親自花時間、精力、或者金錢去寫這樣的一個處理程序,所以我們轉(zhuǎn)而采用那些已經(jīng)存在的可重用的組件。并且很多的庫存在于網(wǎng)上。 

找一個優(yōu)秀的適合我們需要的Java FTP 客戶端庫并不像看起來那么簡單。相反這是一項非常痛苦復(fù)雜的工作。首先找到一個FTP客戶端庫需要一些時間,其次,在我們找到所有的存在的庫后,我們該選哪一個呢?每個庫都適合不同的需求。這些庫在性能上是不等價的,并且它們的設(shè)計上有著根本上的差別。每個類庫都各具特點并使用不同的術(shù)語來描述它們。因而,評價和比較FTP客戶端庫是一件困難的事情。 

使用可重用組件是一種值得提倡的方法,但是在這種情況下,剛開始往往是令人氣餒的。后來或許有點慚愧:在選擇了一個好的FTP庫后,其后的工作就非常簡單了,按簡單的規(guī)則來就行了。目前,已經(jīng)有很多公開免費的ftp客戶端類庫,如simpleftp、J-ftp等,還有很多其他的ftpclient。如下表所示,表中未能全部列出,如讀者有更好的客戶端FTP類庫,請進行進一步的補充。

在本文中,筆者采用是J-ftp。這個是個開源的且功能十分強大的客戶端FTP類庫。筆者很喜歡,同時也向各位讀者推薦一下。算了免費為它做一個廣告。

三、基本功能

 1、 登陸 
采用FTP進行文件傳輸,其實本質(zhì)上還是采用Java.net.socket進行通信。以下代碼只是類net.sf.jftp.net.FtpConnection其中一個login方法。當然在下面的代碼,為了節(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; }

此登陸方法中,有一個JConnection類,此類負責(zé)建立socket套接字    ,同時,此類是一種單獨的線程,這樣的好處是為了配合界面的變化,而將網(wǎng)絡(luò)的套接字連接等工作做為單獨的線程來處理,有利于界面的友好性。下面是net.sf.jftp.net.JConnection類的run方法,當然,此線程的啟動是在JConnection類的構(gòu)造方法中啟動的。

 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這里說明一下,此類實現(xiàn)客戶端套接字(也可以就叫“套接字”),套接字是兩臺機器之間的通信端點。套接字的實際工作由 SocketImpl 類的實例執(zhí)行。應(yīng)用程序通過更改創(chuàng)建套接字實現(xiàn)的套接字工廠可以配置它自身,以創(chuàng)建適合本地防火墻的套接字。具體的說明請參考JDK5 的API說明,最好是中文的。呵呵。 

2 上傳下載
 文件的上傳可以分成多線程及單線程,在單線程情況下比較簡單,而在多線程的情況下,要處理的事情要多點,同時也要小心很多。下面是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);  }}

在多線程的情況下,有一個單獨的類net.sf.jftp.net .FtpTransfer,當然,多線程情況下,此類肯定是一個單獨的線程了。與JConnection相似,其線程的啟動也是在構(gòu)造方法中啟動。而在它的run方法中,進行文件的讀取及傳輸。

 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);  } }

 至于下載的過程,因為它是上傳的逆過程,與上傳的方法及寫法大同小異,在些出于篇幅的考慮,并沒有將代碼列出,但其思想及思路完全一樣。請讀者參考源代碼。

四、 進度條

可以想象,如果在上傳或是下載的過程中,沒有任何的提示,用戶根本沒法判斷任務(wù)是否完成或是任務(wù)是否死了,常常由于上傳時間或下載時間過長而誤導(dǎo)用戶。因此,進度條就顯得非常的重要與實用。 

進度條的實現(xiàn),其實說起來很簡單。就是在程序中開啟兩個線程,第一個線程用于動態(tài)的改變界面上進度條的value值,而第二個線程則在上傳或是下載的過程中,做成一個循環(huán),在此循環(huán)中,每次讀取一定數(shù)量如8192字節(jié)數(shù)的數(shù)據(jù)。然后傳完此數(shù)據(jù)后,調(diào)用第一個線程中的updateProgress方法,來更新界面進度條的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; }

此方法進行負責(zé)上傳一定字節(jié)數(shù)量的內(nèi)容,其實就是調(diào)用rawUpload方法,這里沒列出,請參考源代碼,而當傳完此字節(jié)數(shù)據(jù)后,通過調(diào)用fireActionFinished()方法來調(diào)用主線程中的updateProgressBar()方法。其實代碼如下: 

 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(); } 

上面用了兩個進度條,第一個進度條表示當前文件的上傳或下載進度,第二個進度條表示所有文件下載或上傳的進度。同時,為了產(chǎn)生進度條的移動或變化進度幅度比較明顯,通過pbFile.setMaximum(10000)及pbTotal.setMaximum(10000)將進度條的最大值設(shè)置成10000,而不是平時我們所設(shè)置的100。筆者認為這樣比較好看,因為有的時候上傳或下載的時候由于網(wǎng)絡(luò)原因,可能變化比較小。若設(shè)置成100則變化不是特別明顯。

以上就是FTP批量大文件上傳下載的基礎(chǔ)篇,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持武林網(wǎng)。

發(fā)表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發(fā)表
主站蜘蛛池模板: 淮南市| 西贡区| 南召县| 白银市| 寿宁县| 墨竹工卡县| 南投市| 东台市| 体育| 长兴县| 荣昌县| 南郑县| 浪卡子县| 赞皇县| 临汾市| 克东县| 南皮县| 罗平县| 禄劝| 弥渡县| 江源县| 龙里县| 辉南县| 大埔县| 铜陵市| 惠东县| 田东县| 安塞县| 雷波县| 磴口县| 岳普湖县| 靖宇县| 米易县| 马关县| 资源县| 会昌县| 碌曲县| 桃园县| 星子县| 潞西市| 兴宁市|