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