commons.net包中的FTPClient.listFiles()方法返回null的問題及其解決方案


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)。  回復  更多評論

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM