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

首先把代碼貼出來:
?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?}


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

二, 傳輸binary文件, 由于FTPClient默認使用ASCII作為傳輸模式, 所有不能傳輸二進制文件. 通過
ftp.setFileType(FTP.BINARY_FILE_TYPE)個可以解決這個問題, 但是要在login以后執行. 因為這個方法要向服務器發送"TYPE I"命令.
開始的時候用的是setFileTransferMode, 不過不好使. 它會執行 MODE I命令, 服務器不接受.

三, 用被動模式傳輸: enterLocalPassiveMode()這個到不用在login之后執行, 因為它只改變FTPClient實例的內部屬性.

四, 斷點續傳. 心想應該有支持吧, 于是查API結果找到了setRestartOffset()方法, 試了一下,果真好使. 用RandomAccessFile配合使用, 實現起來還是蠻簡單的.

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

對于上面這個問題, 我本來想修改一下FTPClient這個類來徹底解決問題. 結果發現自己也想不出好辦法. 最后還是放棄了.

今天才發現,原來FTPClient有個completePendingCommand()方法就是用來干這件事情的!

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