這幾天一個(gè)同事要在項(xiàng)目里實(shí)現(xiàn)用ftp下載文件. 遇到了很多問(wèn)題. 于是我推薦他用Jakarta-Commons項(xiàng)目中的net組件在實(shí)現(xiàn). 其實(shí)之前我也沒(méi)有實(shí)際用過(guò), 稍稍看了一下文檔,知道里面有個(gè)ftp包能完成相關(guān)的操作. 于是我的同事就興致勃勃的拿去用了. 可用了以后才發(fā)現(xiàn)有很多問(wèn)題, 搞得焦頭爛額. 經(jīng)過(guò)我們的努力, 終于把問(wèn)題都解決了, 下面我把遇到的問(wèn)題和解決方案寫下來(lái), 以備其他想要用common-net包的朋友參考.

首先把代碼貼出來(lái):
?1?public?class?ClientTest?{
?2?????public?static?void?main(String[]?args)?{
?3?????????String?url?=?"172.17.1.38";
?4?????????String?user?=?"test";
?5?????????String?pwd?=?"test";
?6?
?7?????????FTPClient?ftp?=?new?FTPClient();
?8?????????ftp.setControlEncoding("GBK");
?9?????????FTPClientConfig?conf?=?new?FTPClientConfig(FTPClientConfig.SYST_NT);
10?????????conf.setServerLanguageCode("zh");
11?????????ftp.configure(conf);
12?
13?????????try?{
14?????????????ftp.connect(url);
15?????????????if?(ftp.login(user,?pwd))?{
16?????????????????int?reply?=?ftp.getReplyCode();
17?????????????????if?(!FTPReply.isPositiveCompletion(reply))?{
18?????????????????????ftp.disconnect();
19?????????????????????System.out.println("disconnect");
20?????????????????}?else?{
21?????????????????????ftp.enterLocalPassiveMode();
22?????????????????????ftp.setFileType(FTP.BINARY_FILE_TYPE);
23?
24?????????????????????File?dir?=?new?File("down");
25?????????????????????if?(!dir.exists())?{
26?????????????????????????dir.mkdirs();
27?????????????????????}
28?
29?????????????????????String[]?names?=?ftp.listNames();
30?????????????????????for?(String?name?:?names)?{
31?????????????????????????File?file?=?new?File(dir.getPath()?+?File.separator?+?name);
32?????????????????????????if?(!file.exists())?{
33?????????????????????????????file.createNewFile();
34?????????????????????????}
35?????????????????????????long?pos?=?file.length();
36?????????????????????????RandomAccessFile?raf?=?new?RandomAccessFile(file,?"rw");
37?????????????????????????raf.seek(pos);
38?????????????????????????ftp.setRestartOffset(pos);
39?
40?????????????????????????InputStream?is?=?ftp.retrieveFileStream(name);
41?????????????????????????if?(is?==?null)?{
42?????????????????????????????System.out.println("no?such?file:"?+?name);
43?????????????????????????}?else?{
44?????????????????????????????System.out.println("start?getting?file:"?+?name);
45?
46?????????????????????????????int?b;
47?????????????????????????????while?((b?=?is.read())?!=?-1)?{
48?????????????????????????????????raf.write(b);
49?????????????????????????????}
50?????????????????????????????if?(ftp.getReply()?==?FTPReply.CODE_226)?{
51?????????????????????????????????System.out.println("done!");
52?????????????????????????????}
53?????????????????????????????is.close();
54?????????????????????????}
55?????????????????????????raf.close();
56?????????????????????}
57?????????????????}
58?????????????????ftp.logout();
59?????????????}
60?????????}?catch?(IOException?e)?{
61?????????????e.printStackTrace();
62?????????}
63?????}
64?}


一, 文件名中文亂碼問(wèn)題.
開(kāi)始知道能用FTPClient的listNames方法得到當(dāng)前目錄下所有文件的列表. 但是發(fā)現(xiàn)中文文件名是亂碼. 默認(rèn)情況下FTPClient使用UTF-8字符集作為和服務(wù)器通訊的編碼集. 而我們的ftp服務(wù)器是在中文windowsXP上裝的ServU. 所有使用GBK做為通訊編碼集. 經(jīng)過(guò)查找api文檔, 我看到了setControlEncoding方法, 試了一下,果然好使. 于是這個(gè)問(wèn)題就解決了:
第8行: ftp.setControlEncoding("GBK")
至于conf.setServerLanguageCode("zh")對(duì)這個(gè)有什么影響,我還沒(méi)有驗(yàn)證. 但是只有這句是不行的.

二, 傳輸binary文件, 由于FTPClient默認(rèn)使用ASCII作為傳輸模式, 所有不能傳輸二進(jìn)制文件. 通過(guò)
ftp.setFileType(FTP.BINARY_FILE_TYPE)個(gè)可以解決這個(gè)問(wèn)題, 但是要在login以后執(zhí)行. 因?yàn)檫@個(gè)方法要向服務(wù)器發(fā)送"TYPE I"命令.
開(kāi)始的時(shí)候用的是setFileTransferMode, 不過(guò)不好使. 它會(huì)執(zhí)行 MODE I命令, 服務(wù)器不接受.

三, 用被動(dòng)模式傳輸: enterLocalPassiveMode()這個(gè)到不用在login之后執(zhí)行, 因?yàn)樗桓淖僃TPClient實(shí)例的內(nèi)部屬性.

四, 斷點(diǎn)續(xù)傳. 心想應(yīng)該有支持吧, 于是查API結(jié)果找到了setRestartOffset()方法, 試了一下,果真好使. 用RandomAccessFile配合使用, 實(shí)現(xiàn)起來(lái)還是蠻簡(jiǎn)單的.

五, 只能傳一個(gè)文件!!
不知道大家有沒(méi)有遇到這個(gè)問(wèn)題, 傳輸?shù)谝粋€(gè)文件好使, 后面的的retrieveFileStream(name)都是返回null. 這個(gè)實(shí)在是令人頭痛的問(wèn)題, 難不成要傳一個(gè)文件重新建立一次連接? 那樣也太土了吧. 但是文檔里也沒(méi)有寫, 來(lái)點(diǎn)狠的,debug它的源碼, 看看它究竟做了什么事情. 首先看一下ftp服務(wù)器的日志, 發(fā)現(xiàn)日志沒(méi)問(wèn)題, 過(guò)來(lái)的命令和reply都是正確的, 但是發(fā)現(xiàn)第一個(gè)文件以后沒(méi)有執(zhí)行RETR命令. 于是跟蹤PASV命令的reply代碼,發(fā)現(xiàn)不是227,而服務(wù)器上的日志明明返回的是227. 難道是FTPClient解析Reply出問(wèn)題了. 進(jìn)一步跟蹤發(fā)現(xiàn)了問(wèn)題, 原來(lái)在一個(gè)文件傳輸過(guò)程中會(huì)產(chǎn)生兩個(gè)Reply:
150 Opening BINARY mode data connection for a.sql (19890 Bytes).
226 Transfer complete.
而FTPClient自動(dòng)消費(fèi)掉一個(gè),于是解析Reply就發(fā)生了錯(cuò)位, 下一個(gè)命令的會(huì)解析266那條. 接下來(lái)的命令都不是解析自己的Reply而是前一次命令的. 所有在PASV命令的Reply碼就不對(duì)了, FTPClient也就不會(huì)執(zhí)行接下來(lái)本應(yīng)該執(zhí)行RETR命令.
他不消費(fèi),我們來(lái)消費(fèi)吧. 于是在文件傳輸完成以后, 主動(dòng)調(diào)用一次getReply()把接下來(lái)的226消費(fèi)掉. 這樣做是可以解決這個(gè)暫時(shí)的問(wèn)題, 但不知道在其他的ftp操作上會(huì)不會(huì)也有類似的情況. FTPClient這點(diǎn)可做的不大好.

對(duì)于上面這個(gè)問(wèn)題, 我本來(lái)想修改一下FTPClient這個(gè)類來(lái)徹底解決問(wèn)題. 結(jié)果發(fā)現(xiàn)自己也想不出好辦法. 最后還是放棄了.

今天才發(fā)現(xiàn),原來(lái)FTPClient有個(gè)completePendingCommand()方法就是用來(lái)干這件事情的!

完成的程序,上傳,下載,刪除
http://www.aygfsteel.com/Files/mstar/ClientTest.zip