這幾天一個同事要在項目里實現用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