Java實(shí)現(xiàn)FTP批量大文件上傳下載篇2
接著上一篇進(jìn)行學(xué)習(xí)java文件上傳下載1。
五、斷點(diǎn)續(xù)傳
對(duì)于熟用QQ的程序員,QQ的斷點(diǎn)續(xù)傳功能應(yīng)該是印象很深刻的。因?yàn)樗軐?shí)用也很方面。因此,在我們的上傳下載過(guò)程中,很實(shí)現(xiàn)了斷點(diǎn)續(xù)傳的功能。
其實(shí)斷點(diǎn)續(xù)傳的原理很簡(jiǎn)單,就在上傳的過(guò)程中,先去服務(wù)上進(jìn)行查找,是否存在此文件,如果存在些文件,則比較服務(wù)器上文件的大小與本地文件的大小,如果服務(wù)器上的文件比本地的要小,則認(rèn)為此文件上傳過(guò)程中應(yīng)該可以進(jìn)行斷點(diǎn)續(xù)傳。
在實(shí)現(xiàn)的過(guò)程中,RandomAccessFile類變得很有用。此類的實(shí)例支持對(duì)隨機(jī)存取文件的讀取和寫入。隨機(jī)存取文件的行為類似存儲(chǔ)在文件系統(tǒng)中的一個(gè)大型字節(jié)數(shù)組。存在指向該隱含數(shù)組的光標(biāo)或索引,稱為文件指針;輸入操作從文件指針開(kāi)始讀取字節(jié),并隨著對(duì)字節(jié)的讀取而前移此文件指針。如果隨機(jī)存取文件以讀取/寫入模式創(chuàng)建,則輸出操作也可用;輸出操作從文件指針開(kāi)始寫入字節(jié),并隨著對(duì)字節(jié)的寫入而前移此文件指針。寫入隱含數(shù)組的當(dāng)前末尾之后的輸出操作導(dǎo)致該數(shù)組擴(kuò)展。該文件指針可以通過(guò) getFilePointer 方法讀取,并通過(guò) seek 方法進(jìn)行設(shè)置。
RandomAccessFile類的skipBytes方法嘗試跳過(guò)輸入的 n 個(gè)字節(jié)以丟棄跳過(guò)的字節(jié)。如果從服務(wù)器上查得待上傳文件的大小n,則采用skipBytes方法可以跳過(guò)這n個(gè)字節(jié),從而開(kāi)始從新的地方開(kāi)始進(jìn)行斷點(diǎn)續(xù)傳。具體的方法說(shuō)明可以參見(jiàn)JDK5的API說(shuō)明。
可以在net.sf.jftp.net. DataConnection類的run方法中,可以看出上傳下載中斷點(diǎn)續(xù)傳的實(shí)現(xiàn),代碼如下:
public void run()
{
try
{
newLine = con.getCRLF();
if(Settings.getFtpPasvMode())
{
try
{
sock = new Socket(host, port);
sock.setSoTimeout(Settings.getSocketTimeout());
}
catch(Exception ex)
{
ok = false;
debug("Can't open Socket on port " + port);
}
}
else
{
//Log.debug("trying new server socket: "+port);
try
{
ssock = new ServerSocket(port);
}
catch(Exception ex)
{
ok = false;
Log.debug("Can't open ServerSocket on port " + port);
}
}
}
catch(Exception ex)
{
debug(ex.toString());
}
isThere = true;
boolean ok = true;
RandomAccessFile fOut = null;
BufferedOutputStream bOut = null;
RandomAccessFile fIn = null;
try
{
if(!Settings.getFtpPasvMode())
{
int retry = 0;
while((retry++ < 5) && (sock == null))
{
try
{
ssock.setSoTimeout(Settings.connectionTimeout);
sock = ssock.accept();
}
catch(IOException e)
{
sock = null;
debug("Got IOException while trying to open a socket!");
if(retry == 5)
{
debug("Connection failed, tried 5 times - maybe try a higher timeout in Settings.java");
}
finished = true;
throw e;
}
finally
{
ssock.close();
}
debug("Attempt timed out, retrying");
}
}
if(ok)
{
byte[] buf = new byte[Settings.bufferSize];
start = System.currentTimeMillis();
int buflen = 0;
//---------------download,下載----------------------
if(type.equals(GET) || type.equals(GETDIR))
{
if(!justStream)
{
try
{
if(resume)
{
File f = new File(file);
fOut = new RandomAccessFile(file, "rw");
fOut.skipBytes((int) f.length());
buflen = (int) f.length();
}
else
{
if(localfile == null)
{
localfile = file;
}
File f2 = new File(Settings.appHomeDir);
f2.mkdirs();
File f = new File(localfile);
if(f.exists())
{
f.delete();
}
bOut = new BufferedOutputStream(new FileOutputStream(localfile),
Settings.bufferSize);
}
}
catch(Exception ex)
{
debug("Can't create outputfile: " + file);
ok = false;
ex.printStackTrace();
}
}
//---------------upload,上傳----------------------
if(type.equals(PUT) || type.equals(PUTDIR))
{
if(in == null)
{
try
{
fIn = new RandomAccessFile(file, "r");
if(resume)
{
fIn.skipBytes(skiplen);
}
//fIn = new BufferedInputStream(new FileInputStream(file));
}
catch(Exception ex)
{
debug("Can't open inputfile: " + " (" + ex + ")");
ok = false;
}
}
if(ok)
{
try
{
out = new BufferedOutputStream(sock.getOutputStream());
}
catch(Exception ex)
{
ok = false;
debug("Can't get OutputStream");
}
if(ok)
{
try
{
int len = skiplen;
char b;
while(true)
{
int read;
if(in != null)
{
read = in.read(buf);
}
else
{
read = fIn.read(buf);
}
len += read;
//System.out.println(file + " " + type+ " " + len + " " + read);
if(read == -1)
{
break;
}
if(newLine != null)
{
byte[] buf2 = modifyPut(buf, read);
out.write(buf2, 0, buf2.length);
}
else
{
out.write(buf, 0, read);
}
con.fireProgressUpdate(file, type, len);
if(time())
{
// Log.debugSize(len, false, false, file);
}
if(read == StreamTokenizer.TT_EOF)
{
break;
}
}
out.flush();
//Log.debugSize(len, false, true, file);
}
catch(IOException ex)
{
ok = false;
debug("Error: Data connection closed.");
con.fireProgressUpdate(file, FAILED, -1);
ex.printStackTrace();
}
}
}
}
}
}
catch(IOException ex)
{
Log.debug("Can't connect socket to ServerSocket");
ex.printStackTrace();
}
finally
{
try
{
if(out != null)
{
out.flush();
out.close();
}
}
catch(Exception ex)
{
ex.printStackTrace();
}
try
{
if(bOut != null)
{
bOut.flush();
bOut.close();
}
}
catch(Exception ex)
{
ex.printStackTrace();
}
try
{
if(fOut != null)
{
fOut.close();
}
}
catch(Exception ex)
{
ex.printStackTrace();
}
try
{
if(in != null && !justStream)
{
in.close();
}
if(fIn != null)
{
fIn.close();
}
}
catch(Exception ex)
{
ex.printStackTrace();
}
}
try
{
sock.close();
}
catch(Exception ex)
{
debug(ex.toString());
}
if(!Settings.getFtpPasvMode())
{
try
{
ssock.close();
}
catch(Exception ex)
{
debug(ex.toString());
}
}
finished = true;
if(ok)
{
con.fireProgressUpdate(file, FINISHED, -1);
}
else
{
con.fireProgressUpdate(file, FAILED, -1);
}
}
六、FTP端口映射
FTP的數(shù)據(jù)連接有PASV和PORT兩種,如果你的FTP服務(wù)器位于內(nèi)網(wǎng)中,需要做端口映射。筆者剛開(kāi)始時(shí)對(duì)FTP的網(wǎng)外網(wǎng)映射也是不怎么了解,因此開(kāi)始走了不少的彎路,開(kāi)始一直以為是自己的程序有問(wèn)題,浪費(fèi)了不少時(shí)間,希望通過(guò)這段,能讓大家在開(kāi)發(fā)的時(shí)候少花或不花這些無(wú)謂的時(shí)間與精力。
PCD上曾經(jīng)有一篇文章介紹過(guò)一種直接訪問(wèn)內(nèi)網(wǎng)的方法,其實(shí)我們只要用端口映射工具,就可輕松實(shí)現(xiàn)穿透內(nèi)網(wǎng)的目的。“端口映射器”就是一款這樣的工具,更值得一提的是,它擺脫了命令行模式,提供了圖形界面的操作環(huán)境。
為了讓各位能更加明白,先說(shuō)一下原理。假設(shè)現(xiàn)在有一個(gè)局域網(wǎng),主機(jī)為A,局域網(wǎng)內(nèi)除了主機(jī)外,還有一臺(tái)機(jī)器為B,B機(jī)器當(dāng)然是通過(guò)主機(jī)A上網(wǎng)的。另外還有一臺(tái)可上網(wǎng)的機(jī)器為C,與A和B并不在一個(gè)局域網(wǎng)內(nèi)。通常情況下,C機(jī)器只能訪問(wèn)到A主機(jī),而無(wú)法穿透局域網(wǎng),訪問(wèn)到B。而通過(guò)端口映射后,當(dāng)C機(jī)器訪問(wèn)主機(jī)A的指定端口時(shí),主機(jī)A上的“端口映射器”就起作用了,它會(huì)把指定端口上的數(shù)據(jù)轉(zhuǎn)到局域網(wǎng)內(nèi)另一臺(tái)機(jī)器的指定端口上,從而實(shí)現(xiàn)訪問(wèn)內(nèi)網(wǎng)機(jī)器的目的。這樣說(shuō),大家明白了吧。至于具體的如何進(jìn)行配置,筆者認(rèn)為應(yīng)該不是件很難的事情,再說(shuō),網(wǎng)上這樣的圖形解釋很多,請(qǐng)大家參考網(wǎng)絡(luò)上的文章進(jìn)行設(shè)置。
當(dāng)然,實(shí)現(xiàn)直接訪問(wèn)內(nèi)網(wǎng)的優(yōu)點(diǎn)是顯而易見(jiàn)的,別的不說(shuō),起碼FTP資源是被充分利用了。不過(guò)必須提醒讀者的是,直接訪問(wèn)內(nèi)網(wǎng)可能使內(nèi)網(wǎng)的安全性受到威脅。筆者相信大部分朋友對(duì)主機(jī)安全的重要性還是重視的,但往往會(huì)忽略內(nèi)網(wǎng)機(jī)器的安全設(shè)置。一旦你實(shí)現(xiàn)了直接訪問(wèn)內(nèi)網(wǎng),那就必須像對(duì)待主機(jī)一樣對(duì)待內(nèi)網(wǎng)機(jī)器,否則你的整個(gè)網(wǎng)絡(luò)將可能處于危險(xiǎn)狀態(tài)。
訪問(wèn)客戶端資源
Java應(yīng)用程序環(huán)境的安全策略,對(duì)于不同的代碼所擁有的不同資源的許可,它由一個(gè)Policy對(duì)象來(lái)表達(dá)。為了讓Applet(或者運(yùn)行在 SecurityManager下的一個(gè)應(yīng)用程序)能夠執(zhí)行受保護(hù)的行為,例如讀寫文件,Applet(或 Java應(yīng)用程序)必須獲得那項(xiàng)操作的許可,安全策略文件就是用來(lái)實(shí)現(xiàn)這些許可。
Policy對(duì)象可能有多個(gè)實(shí)體,雖然任何時(shí)候只能有一個(gè)起作用。當(dāng)前安裝的Policy對(duì)象,在程序中可以通過(guò)調(diào)用getPolicy方法得到,也可以通過(guò)調(diào)用setPolicy方法改變。Policy對(duì)象評(píng)估整個(gè)策略,返回一個(gè)適當(dāng)?shù)腜ermissions對(duì)象,詳細(xì)說(shuō)明哪些代碼可以訪問(wèn)哪些資源。策略文件可以儲(chǔ)存在無(wú)格式的ASCII文件或Policy類的二進(jìn)制文件或數(shù)據(jù)庫(kù)中。本文僅討論無(wú)格式的ASCII文件的形式。
在實(shí)際使用中,我們可以不需要自己手動(dòng)去編寫那么復(fù)雜的java.policy文件,特別是在不使用數(shù)字簽名時(shí)。這時(shí),我們完全可以借鑒JRE提供給我們的現(xiàn)成的 C:\Program Files\Java\jre1.5.0_12\lib\security\java.policy文件,根據(jù)我們的需要做相應(yīng)的修改,本文就針對(duì)不使用數(shù)字簽名情況編寫安全策略文件。下面,是一個(gè)完整的在Windows NT/XP下使用的java.policy文件。在文件中,分別使用注釋的形式說(shuō)明了每個(gè)“permission”記錄的用途。當(dāng)然,不同的程序?qū)Y源訪問(wèn)權(quán)限的要求可能不一樣,可以根據(jù)項(xiàng)目需要進(jìn)行調(diào)整與選擇。
grant {
//對(duì)系統(tǒng)和用戶目錄“讀”的權(quán)限
permission java.util.PropertyPermission "user.dir", "read";
permission java.util.PropertyPermission "user.home", "read";
permission java.util.PropertyPermission "java.home", "read";
permission java.util.PropertyPermission "java.class.pat", "read";
permission java.util.PropertyPermission "user.name", "read";
//對(duì)線程和線程組的操作權(quán)限
permission java.lang.RuntimePermission "accessClassInPackage.sun.misc";
permission java.lang.RuntimePermission "accessClassInPackage.sun.audio";
permission java.lang.RuntimePermission "modifyThread";
permission java.lang.RuntimePermission "modifyThreadGroup";
permission java.lang.RuntimePermission "loadLibrary.*";
//讀寫文件的權(quán)限
permission java.io.FilePermission "<<ALL FILES>>", "read";
permission java.io.FilePermission "${user.dir}${/}jmf.log", "write";
permission java.io.FilePermission "${user.home}${/}.JMStudioCfg", "write";
permission java.net.SocketPermissio "*", "connect,accept";
permission java.io.FilePermission "C:\WINNT\TEMP\*", "write";
permission java.io.FilePermission "C:\WINNT\TEMP\*", "delete";
permission java.awt.AWTPermission "showWindowWithoutWarningBanner";
permission javax.sound.sampled.AudioPermission "record";
// //操作Socket端口的各種權(quán)限
permission java.net.SocketPermission "-", "listen";
permission java.net.SocketPermission "-", "accept";
permission java.net.SocketPermission "-", "connect";
permission java.net.SocketPermission "-", "resolve";
permission java.security.AllPermission;
};
grant signedBy "saili" {
permission java.net.SocketPermission "*:1024-65535", "connect,accept,resolve";
permission java.net.SocketPermission "*:80", "connect";
permission java.net.SocketPermission "-", "listen, accept, connect, listen, resolve", signedBy "ganja";
permission java.net.SocketPermission "-", "accept";
permission java.net.SocketPermission "-", "connect";
permission java.net.SocketPermission "-", "resolve";
permission java.security.AllPermission;
};
筆者在本項(xiàng)目中,為了使用客戶端的用戶設(shè)置更加的方便與簡(jiǎn)單,將上面的文件采用VB或C#來(lái)做成一個(gè)小程序來(lái)寫。然后將JRE及些exe共同打成一個(gè)EXE包,當(dāng)JRE安裝完成后,此小程序負(fù)責(zé)找到JRE在操作系統(tǒng)中的安裝路徑,并在程序中寫出此java.policy文件,覆蓋原有的文件。如此一來(lái),用戶就只需安裝一個(gè)EXE文件,從而簡(jiǎn)化了安裝的操作次數(shù)。
七、Applet回調(diào)服務(wù)器
JavaScript與Applet之間能夠相互通訊給我們帶來(lái)了很多方便,Java與JavaScript互相補(bǔ)充,以開(kāi)發(fā)功能更完美的Web應(yīng)用程序。B/S下能夠充分利用java的優(yōu)勢(shì),給我們帶來(lái)更多的網(wǎng)絡(luò)體驗(yàn),方便用戶。筆者用的比較多的是利用Swing組件開(kāi)發(fā)的應(yīng)用程序利用Applet實(shí)現(xiàn)B/s下架構(gòu),這樣能夠充分顯示Swing組件的優(yōu)勢(shì),便于系統(tǒng)升級(jí),便于維護(hù);還有就是在WEB下,有時(shí)客戶端要使用本地的硬件資源,筆者所知道的是通過(guò)Applet來(lái)實(shí)現(xiàn),通過(guò)Applet去調(diào)用javaAPI來(lái)實(shí)現(xiàn)。 我們具體來(lái)看看JavaScript與Applet之間到底是怎樣通訊的呢?
1.JavaScript訪問(wèn)Applet
<applet name="appletName" ....../>//JavaScript訪問(wèn)Applet屬性。
window.document.appletName.appletField (屬性必須是public的,"window.document."也可以不寫) //JavaScript訪問(wèn)Applet方法。
window.document.appletName.appletMethod (方法必須是public的,"window.document."也可以不寫)。
2.Applet訪問(wèn)JavaScript
Live Connect提供了Java與JavaScript的接口,可以允許在Java Applet小程序中使用JavaScript。
需要用到一個(gè)jar包,在C:\Program Files\Java\目錄下找,大概有5M多,其實(shí)就是打開(kāi)看哪個(gè)有netscape.javascript.JSObject。如果沒(méi)有裝個(gè)NetScape或從網(wǎng)上下都可以。 可以把它重命名為netscape.jar(不是必須的),一定要加入到classpath,目的是使開(kāi)發(fā)的時(shí)候能夠編譯。特別注意的是:部署時(shí)不需要包括netscape.jar,因?yàn)檎麄€(gè)包會(huì)下載到客戶端,影響速度。
//引入netscape類
import netscape.javascript.JSObject;
import netscape.javascript.JSException; //可允許在小程序中處理異常事件
public void callJavaScript(String callBackJavascript) {
JSObject window = JSObject.getWindow(this); // 獲取JavaScript窗口句柄,引用當(dāng)前文檔窗口
JSObject docment = (JSObject) window.getMember("document");
form=(JSObject)doc.getMember("textForm"); //訪問(wèn)JavaScript form對(duì)象
textField=(JSObject)form.getMember("textField");訪問(wèn)JavaScript text對(duì)象
text=(String) textField.getMember("value"); //獲取文本區(qū)的值
// 調(diào)用JavaScript的alert()方法
// window.eval("alert(\"This alert comes from Java!\")");
window.call(callBackJavascript, null);// 參數(shù)用數(shù)組的形式表示。
}
八、運(yùn)行效果
1.上傳
(1).啟動(dòng)上傳上面

(2).上傳中

(3).上傳中

(4).上傳成功

2.下載
(1)下載文件的保存路徑

(2)下載中

(3)下載中

(4)下載成功

九、小結(jié)
在本文中,筆者將在實(shí)際項(xiàng)目中的上傳下載問(wèn)題的解決方案進(jìn)行了闡述,通過(guò)采用FTP協(xié)議,來(lái)達(dá)到批量的,基本W(wǎng)eb的大文件的上傳下載。同時(shí)通過(guò)Applet技術(shù)實(shí)現(xiàn)在客戶端對(duì)本地資源進(jìn)行訪問(wèn)。就一些大家經(jīng)常遇到的實(shí)際功能如進(jìn)度條、斷點(diǎn)續(xù)傳、FTP內(nèi)外網(wǎng)映射等問(wèn)題進(jìn)行了初步的探討。這是筆者基于一些FTP的Java客戶端庫(kù)的基礎(chǔ)應(yīng)用。希望對(duì)讀者有一定的借鑒作用。對(duì)其中一些未盡事宜進(jìn)行補(bǔ)充。還有一些比較容易而且網(wǎng)上都有說(shuō)明或?qū)嵗膬?nèi)容在此沒(méi)有一一列舉。如FTP在服務(wù)器端Serv-U軟件如何建立FTP服務(wù)、Applet在JSP頁(yè)面中的嵌入方式及參數(shù)傳遞方法、在Eclipse或是NetBeans下開(kāi)始Applet等內(nèi)容,由于篇幅的限制,并沒(méi)有詳盡描述,請(qǐng)讀者參考網(wǎng)上的例子或其他參考資料。
下載地址:http://xiazai.jb51.net/201608/yuanma/FTPTransfer(jb51.net).rar
注釋,考慮到版權(quán)的問(wèn)題,沒(méi)有把JAVA類文件發(fā)上來(lái),不過(guò)這樣的JAR文件如何還原成java文件,我想大家已經(jīng)是很熟悉了吧,呵呵.
以上就是本文的全部?jī)?nèi)容,希望對(duì)大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
MybatisPlus EntityWrapper如何自定義SQL
這篇文章主要介紹了MybatisPlus EntityWrapper如何自定義SQL,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2022-03-03
Spring cloud Eureka注冊(cè)中心搭建的方法
這篇文章主要介紹了Spring cloud Eureka注冊(cè)中心搭建的方法,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-04-04
Java利用遞歸算法實(shí)現(xiàn)查詢斐波那契數(shù)
今天小編就為大家分享一篇關(guān)于Java利用遞歸算法實(shí)現(xiàn)查詢斐波那契數(shù),小編覺(jué)得內(nèi)容挺不錯(cuò)的,現(xiàn)在分享給大家,具有很好的參考價(jià)值,需要的朋友一起跟隨小編來(lái)看看吧2018-12-12
spring boot @ResponseBody轉(zhuǎn)換JSON 時(shí) Date 類型處理方法【兩種方法】
這篇文章主要介紹了spring boot @ResponseBody轉(zhuǎn)換JSON 時(shí) Date 類型處理方法,主要給大家介紹Jackson和FastJson兩種方式,每一種方法給大家介紹的都非常詳細(xì),需要的朋友可以參考下2018-08-08
SpringBoot自定義消息轉(zhuǎn)換器的實(shí)現(xiàn)與應(yīng)用
Spring Boot 默認(rèn)提供了多種消息轉(zhuǎn)換器,如 Jackson 用于 JSON 格式轉(zhuǎn)換、JAXB 用于 XML 格式轉(zhuǎn)換等,然而,實(shí)際開(kāi)發(fā)中往往需要根據(jù)業(yè)務(wù)需求進(jìn)行自定義的消息轉(zhuǎn)換器,文將詳細(xì)介紹如何在 Spring Boot 中實(shí)現(xiàn)自定義消息轉(zhuǎn)換器,探討其工作原理、配置方式以及實(shí)際應(yīng)用場(chǎng)景2025-01-01

