Java自学者论坛

 找回密码
 立即注册

手机号码,快捷登录

恭喜Java自学者论坛(https://www.javazxz.com)已经为数万Java学习者服务超过8年了!积累会员资料超过10000G+
成为本站VIP会员,下载本站10000G+会员资源,会员资料板块,购买链接:点击进入购买VIP会员

JAVA高级面试进阶训练营视频教程

Java架构师系统进阶VIP课程

分布式高可用全栈开发微服务教程Go语言视频零基础入门到精通Java架构师3期(课件+源码)
Java开发全终端实战租房项目视频教程SpringBoot2.X入门到高级使用教程大数据培训第六期全套视频教程深度学习(CNN RNN GAN)算法原理Java亿级流量电商系统视频教程
互联网架构师视频教程年薪50万Spark2.0从入门到精通年薪50万!人工智能学习路线教程年薪50万大数据入门到精通学习路线年薪50万机器学习入门到精通教程
仿小米商城类app和小程序视频教程深度学习数据分析基础到实战最新黑马javaEE2.1就业课程从 0到JVM实战高手教程MySQL入门到精通教程
查看: 828|回复: 0

commons.net包中的FTPClient.listFiles()方法返回null的问题及其解决方案

[复制链接]
  • TA的每日心情
    奋斗
    2024-11-24 15:47
  • 签到天数: 804 天

    [LV.10]以坛为家III

    2053

    主题

    2111

    帖子

    72万

    积分

    管理员

    Rank: 9Rank: 9Rank: 9

    积分
    726782
    发表于 2021-7-21 10:37:48 | 显示全部楼层 |阅读模式

    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)。  回复  更多评论

     

    哎...今天够累的,签到来了1...
    回复

    使用道具 举报

    您需要登录后才可以回帖 登录 | 立即注册

    本版积分规则

    QQ|手机版|小黑屋|Java自学者论坛 ( 声明:本站文章及资料整理自互联网,用于Java自学者交流学习使用,对资料版权不负任何法律责任,若有侵权请及时联系客服屏蔽删除 )

    GMT+8, 2024-12-22 11:29 , Processed in 0.058990 second(s), 27 queries .

    Powered by Discuz! X3.4

    Copyright © 2001-2021, Tencent Cloud.

    快速回复 返回顶部 返回列表