http://www.blogjava.net/wodong/archive/2008/08/21/wodong.html
http://www.reader8.cn/jiaocheng/20121016/2059561.html
中文FTP環境下,使用commons-net,FTPClient.listFiles()方法返回null的問題及解決辦法
項目中需要從FTP上下載數據,采用了開源的commons-net包。在實際應用中發現了一個問題,有些服務器上調用 ftpClient.listFiles()方法可以返回包含文件名的數組,有些服務器上此方法返回NULL。但是 ftpClient.listNames()方法能返回路徑中的文件名,ftpClient.delete()方法也能刪除文件。
命令行連接FTP,執行ls -l 發現返回數據日期的地方比較奇怪。
// 在調用 ftpClient.listNames()方法前,先調用ftpClient.configure(new FTPClientConfig(package.MyFTPEntryParser));// package.MyFTPEntryParser:我們的類的全路徑
commons.net包中的FTPClient.listFiles()方法返回null的問題及其解決方案
目前開發的這個項目中需要從遠程服務器上下載數據,采用了開源的commons.net.ftp包。在實際應用中發現了一個問題,在測試服務器上調用ftpClient.listFiles()方法可以返回包含文件名的數組,而在現網服務器上此方法返回NULL。我被這個問題困擾了好久,下面把我的處理思路陳述如下:
(1)首先發現2個服務器的區別:測試服務器為solaris服務器,而現網服務器為hp服務器,會不會是平台差異所致呢?帶着這個問題,下載了common包的源碼,通過源碼進行調試。
(2)FTPListParseEngine負責處理通過socket來獲取遠程服務器的信息。大概執行了ls –l
操作,並把結果一行行放入一個linkedlist中。代碼如下:
1private void readStream(InputStream stream, String encoding) throws IOException
2 {
3 BufferedReader reader;
4 if (encoding == null)
5 {
6 reader = new BufferedReader(new InputStreamReader(stream));
7 }
8 else
9 {
10 reader = new BufferedReader(new InputStreamReader(stream, encoding));
11 }
12
13 String line = this.parser.readNextEntry(reader);
14
15 while (line != null)
16 {
17 this.entries.add(line);
18 line = this.parser.readNextEntry(reader);
19 }
20 reader.close();
21 }
22
(3)這個時候發現問題了,傳入line中的字符串中有亂碼!正常的應該為:
drwxr-xr-x 11 daladmin daladmin 1024 2004年9月18日 mqm |
其中時間那部分為亂碼。
(4)處理:在調用listFiles()之前先調用ftpClient.setControlEncoding("GBK");這樣line就能正常顯示了,但是listFiles() 返回依然為空!!! 繼續.....
(5) 發現繼續運行的時候有一個正則表達式匹配不成功,代碼如下:
1 public boolean matches(String s)
2 {
3 this.result = null;
4 if (_matcher_.matches(s.trim(), this.pattern))
5 {
6 this.result = _matcher_.getMatch();
7 }
8 return null != this.result;
9 }
10
s即為(3)中的line,追蹤正則表達式,是在具體的子類UnixFTPEntryParser中寫死的。如下:
1private static final String REGEX =
2 "([bcdlfmpSs-])"
3 +"(((r|-)(w|-)([xsStTL-]))((r|-)(w|-)([xsStTL-]))((r|-)(w|-)([xsStTL-])))\\+?\\s+"
4 + "(\\d+)\\s+"
5 + "(\\S+)\\s+"
6 + "(?:(\\S+)\\s+)?"
7 + "(\\d+)\\s+"
8
9 /*
10 numeric or standard format date
11 */
12 //問題出在此處,這個匹配只匹配2中形式:
13 //(1)2008-08-03
14 //(2)Jan 9或4月 26
15 //而出錯的hp機器下的顯示為 8月20日(沒有空格分開)
16 //故無法匹配而報錯
17 //將下面字符串改為:
18 //((?:\\d+[-/]\\d+[-/]\\d+)|(?:\\S+\\s+\\S+)|(?:\\S+))\\s+
19 //便可以成功匹配
20 + "((?:\\d+[-/]\\d+[-/]\\d+)|(?:\\S+\\s+\\S+))\\s+"
21
22 /*
23 year (for non-recent standard format)
24 or time (for numeric or recent standard format
25 */
26 + "(\\d+(?::\\d+)?)\\s+"
27
28 + "(\\S*)(\\s*.*)";
29
(6)做上面修改后,能夠解析出來,但是接着又會報異常,錯誤發生在UnixFTPEntryParser類的parseFTPEntry方法中,common.net對中文支持的實在是不夠:
1 try
2 {
3 file.setTimestamp(super.parseTimestamp(datestr));
4 }
5 catch (ParseException e)
6 {
7 //注釋掉
8 return null; // this is a parsing failure too.
9 }
10
這個錯誤的原因是創建simpleDateFormat類時(詳情請見jdkAPI文檔)
public SimpleDateFormat(String pattern, Locale locale)
locale為EN,解決方案是創建一個新類,繼承ConfigurableFTPFileEntryParserImpl。其中的屬性defaultDateFormat和recentDateFormat 用Locale.CHINA初始化。而我目前的程序用不到取文件的修改時間,所以直接省事將上段代碼中的異常吞掉,即注釋掉return null 。網上有個解決方案(http://hi.baidu.com/hzwei206/blog/item/7c901d2debf7e136359bf7cd.html),是用了另一種方案,粘貼如下:
commons-net-1.4.1.jar包中ftp應用的幾點問題 一、異常: 從http://commons.apache.com網站下載了commons-net-1.4.1包后添加到自己的工程中,調用FtpClient類的listFiles(String pathName)方法時,拋如下異常: Exception in thread "main" java.lang.NoClassDefFoundError : org/apache/oro/text/regex/MalformedPatternException at org.apache.commons.net.ftp.parser.RegexFTPFileEntryParserImpl.<init> (RegexFTPFileEntryParserImpl.java:75) at org.apache.commons.net.ftp.parser.ConfigurableFTPFileEntryParserImpl.<init>(ConfigurableFTPFileEntryParserImpl.java:57) at org.apache.commons.net.ftp.parser.UnixFTPEntryParser.<init>(UnixFTPEntryParser.java:136) at org.apache.commons.net.ftp.parser.UnixFTPEntryParser.<init>(UnixFTPEntryParser.java:119) at org.apache.commons.net.ftp.parser.DefaultFTPFileEntryParserFactory.createUnixFTPEntryParser(DefaultFTPFileEntryParserFactory.java:169) at org.apache.commons.net.ftp.parser.DefaultFTPFileEntryParserFactory.createFileEntryParser(DefaultFTPFileEntryParserFactory.java:94) at org.apache.commons.net.ftp.FTPClient.initiateListParsing(FTPClient.java:2358) at org.apache.commons.net.ftp.FTPClient.listFiles(FTPClient.java:2141) at org.apache.commons.net.ftp.FTPClient.listFiles(FTPClient.java:2188) ................. 以上異常是由於缺少輔助的包jakarta-oro-2.0.8.jar引起的,去http://commons.apache.com網站下載該包后放入工程的lib下,並加載到classpath中,重新編譯運行,OK! 二、調用FtpClient類的listFiles(String pathName)方法失效的問題: 一般是由於ftp服務器(主要是小型機)的操作系統不同語言環境的時間格式造成的,在中文環境下,文件或文件夾的時間格式為"m月d日 hh:mm"或"yyyy年m月 d",而E文環境下時間格式為"MMM d yyyy"或"MMM d HH:mm",於是,在中文環境下,ftp包中的FTPTimestampParserImpl類將時間字符串Date化時拋異常,因為commons- net-1.4.1包不支持中文。 解決辦法(兩種辦法): 1. 將ftp服務器操作系統語言環境設為英文; 2. 修改ftp包的代碼:將FTPTimestampParserImpl類進行擴展,使之支持中文 下面針對第2種解決辦法來實現: (1) 新建類FTPTimestampParserImplExZH類: 1/** 2* FTPTimestampParserImpl的擴展類,使之支持中文環境的時間格式 3* Date:2007-8-15 4*/ 5package org.apache.commons.net.ftp.parser; 6 7import java.text.ParseException; 8import java.text.ParsePosition; 9import java.text.SimpleDateFormat; 10import java.util.Calendar; 11import java.util.Date; 12 13/** 14* @author hzwei206 15* FTPTimestampParserImpl的擴展類,使之支持中文環境的時間格式 16*/ 17public class FTPTimestampParserImplExZH extends FTPTimestampParserImpl 18{ 19 private SimpleDateFormat defaultDateFormat = new SimpleDateFormat("mm d hh:mm"); 20 private SimpleDateFormat recentDateFormat = new SimpleDateFormat("yyyy mm d"); 21 22 /** 23 * @author hzwei206 24 * 將中文環境的時間格式進行轉換 25 */ 26 private String formatDate_Zh2En(String timeStrZh) 27 { 28 if (timeStrZh == null) 29 { 30 return ""; 31 } 32 33 int len = timeStrZh.length(); 34 StringBuffer sb = new StringBuffer(len); 35 char ch = ' '; 36 for (int i = 0;i < len;i++) 37 { 38 ch = timeStrZh.charAt(i); 39 if ((ch >= '0' && ch <= '9') || ch == ' ' || ch == ':') 40 { 41 sb.append(ch); 42 } 43 } 44 45 return sb.toString(); 46 } 47 48 /** 49 * Implements the one {@link FTPTimestampParser#parseTimestamp(String) method} 50 * in the {@link FTPTimestampParser FTPTimestampParser} interface 51 * according to this algorithm: 52 * 53 * If the recentDateFormat member has been defined, try to parse the 54 * supplied string with that. If that parse fails, or if the recentDateFormat 55 * member has not been defined, attempt to parse with the defaultDateFormat 56 * member. If that fails, throw a ParseException. 57 * 58 * @see org.apache.commons.net.ftp.parser.FTPTimestampParser#parseTimestamp(java.lang.String) 59 */ 60 public Calendar parseTimestamp(String timestampStr) throws ParseException 61 { 62 timestampStr = formatDate_Zh2En(timestampStr); 63 Calendar now = Calendar.getInstance(); 64 now.setTimeZone(this.getServerTimeZone()); 65 66 Calendar working = Calendar.getInstance(); 67 working.setTimeZone(this.getServerTimeZone()); 68 ParsePosition pp = new ParsePosition(0); 69 70 Date parsed = null; 71 if (this.recentDateFormat != null) 72 { 73 parsed = recentDateFormat.parse(timestampStr, pp); 74 } 75 if (parsed != null && pp.getIndex() == timestampStr.length()) 76 { 77 working.setTime(parsed); 78 working.set(Calendar.YEAR, now.get(Calendar.YEAR)); 79 if (working.after(now)) 80 { 81 working.add(Calendar.YEAR, -1); 82 } 83 } 84 else 85 { 86 pp = new ParsePosition(0); 87 parsed = defaultDateFormat.parse(timestampStr, pp); 88 // note, length checks are mandatory for us since 89 // SimpleDateFormat methods will succeed if less than 90 // full string is matched. They will also accept, 91 // despite "leniency" setting, a two-digit number as 92 // a valid year (e.g. 22:04 will parse as 22 A.D.) 93 // so could mistakenly confuse an hour with a year, 94 // if we don't insist on full length parsing. 95 if (parsed != null && pp.getIndex() == timestampStr.length()) 96 { 97 working.setTime(parsed); 98 } 99 else 100 { 101 throw new ParseException( 102 "Timestamp could not be parsed with older or recent DateFormat", 103 pp.getIndex()); 104 } 105 } 106 return working; 107 } 108} 109 110 111 (2) 修改org.apache.commons.net.ftp.parser.UnixFTPEntryParser類的parseFTPEntry方法: 1 public FTPFile parseFTPEntry(String entry) 2 { 3 .. 4 if (matches(entry)) 5 { 6 String typeStr = group(1); 7 String hardLinkCount = group(15); 8 String usr = group(16); 9 String grp = group(17); 10 String filesize = group(18); 11 String datestr = group(19) + " " + group(20); 12 String name = group(21); 13 String endtoken = group(22); 14 15 try 16 { 17 file.setTimestamp(super.parseTimestamp(datestr)); 18 } 19 catch (ParseException e) 20 { 21 /* ***mod by hzwei206 將中文時間格式轉換 2007-8-15 begin*** */ 22 //return null; // this is a parsing failure too. 23 try 24 { 25 FTPTimestampParserImplExZH Zh2En = new FTPTimestampParserImplExZH(); 26 file.setTimestamp(Zh2En.parseTimestamp(datestr)); 27 } 28 catch (ParseException e1) 29 { 30 return null; // this is a parsing failure too. 31 } 32 /* ***mod by hzwei206 將中文時間格式轉換 2007-8-15 end*** */ 33 } 34 35 .. 36 } 37 |
posted on 2008-08-21 21:30 wodong 閱讀(9265) 評論(1) 編輯 收藏
FeedBack:
# 150 Here comes the directory listing[未登錄] 2012-06-08 15:45 cnwong
還有可能存在的現象:卡死在“150 Here comes the directory listing.”一行不再往下執行,說明FTPClient.class -->socket=server.accept()被鎖死,原因是FTPClient.class -->getActivePort()返回了0,而你linux客戶端又恰巧不開放該端口。將getActivePort()的返回值設置成 linux客戶端開放的且未被占用的端口值即可(一般是80XX)。 回復 更多評論