目前开发的这个项目中需要从远程服务器上下载数据,采用了开源的commons.net.ftp包。在实际应用中发现了一个问题,在测试服务器上调用ftpClient.listFiles()方法可以返回包含文件名的数组,而在现网服务器上此方法返回NULL。我被这个问题困扰了好久,下面把我的处理思路陈述如下:
(1)首先发现2个服务器的区别:测试服务器为solaris服务器,而现网服务器为hp服务器,会不会是平台差异所致呢?带着这个问题,下载了common包的源码,通过源码进行调试。
(2)FTPListParseEngine负责处理通过socket来获取远程服务器的信息。大概执行了ls –l
操作,并把结果一行行放入一个linkedlist中。代码如下:
1 private 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中写死的。如下:
1 private 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./网站下载了commons-net-1.4.1包后添加到自己的工程中,调用FtpClient类的listFiles(String pathName)方法时,抛如下异常: Exception in thread "main" java.lang.NoClassDefFoundError : org/apache/oro/text/regex/MalformedPatternException at org.mons.net.ftp.parser.RegexFTPFileEntryParserImpl.<init> (RegexFTPFileEntryParserImpl.java:75) at org.mons.net.ftp.parser.ConfigurableFTPFileEntryParserImpl.<init>(ConfigurableFTPFileEntryParserImpl.java:57) at org.mons.net.ftp.parser.UnixFTPEntryParser.<init>(UnixFTPEntryParser.java:136) at org.mons.net.ftp.parser.UnixFTPEntryParser.<init>(UnixFTPEntryParser.java:119) at org.mons.net.ftp.parser.DefaultFTPFileEntryParserFactory.createUnixFTPEntryParser(DefaultFTPFileEntryParserFactory.java:169) at org.mons.net.ftp.parser.DefaultFTPFileEntryParserFactory.createFileEntryParser(DefaultFTPFileEntryParserFactory.java:94) at org.mons.net.ftp.FTPClient.initiateListParsing(FTPClient.java:2358) at org.mons.net.ftp.FTPClient.listFiles(FTPClient.java:2141) at org.mons.net.ftp.FTPClient.listFiles(FTPClient.java:2188) ................. 以上异常是由于缺少辅助的包jakarta-oro-2.0.8.jar引起的,去http://commons./网站下载该包后放入工程的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 */ 5 package org.mons.net.ftp.parser; 6 7 import java.text.ParseException; 8 import java.text.ParsePosition; 9 import java.text.SimpleDateFormat; 10 import java.util.Calendar; 11 import java.util.Date; 12 13 /** 14 * @author hzwei206 15 * FTPTimestampParserImpl的扩展类,使之支持中文环境的时间格式 16 */ 17 public 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.mons.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.mons.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
JAVA中使用FTPClient上传下载
在JAVA程序中,经常需要和FTP打交道,比如向FTP服务器上传文件、下载文件,本文简单介绍如何利用jakarta commons中的FTPClient(在commons-net包中)实现上传下载文件。
一、上传文件
原理就不介绍了,大家直接看代码吧
-
-
-
-
-
-
-
-
-
-
-
-
- public static boolean uploadFile(String url,int port,String username, String password, String path, String filename, InputStream input) {
- boolean success = false;
- FTPClient ftp = new FTPClient();
- try {
- int reply;
- ftp.connect(url, port);
-
- ftp.login(username, password);
- reply = ftp.getReplyCode();
- if (!FTPReply.isPositiveCompletion(reply)) {
- ftp.disconnect();
- return success;
- }
- ftp.changeWorkingDirectory(path);
- ftp.storeFile(filename, input);
- input.close();
- ftp.logout();
- success = true;
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- if (ftp.isConnected()) {
- try {
- ftp.disconnect();
- } catch (IOException ioe) {
- }
- }
- }
- return success;
- }<PRE>
下面我们写两个小例子:
1.将本地文件上传到FTP服务器上,代码如下:
- @Test
- public void testUpLoadFromDisk(){
- try {
- FileInputStream in=new FileInputStream(new File("D:/test.txt"));
- boolean flag = uploadFile("127.0.0.1", 21, "test", "test", "D:/ftp", "test.txt", in);
- System.out.println(flag);
- } catch (FileNotFoundException e) {
- e.printStackTrace();
- }
- }<PRE>
2.在FTP服务器上生成一个文件,并将一个字符串写入到该文件中
- @Test
- public void testUpLoadFromString(){
- try {
- InputStream input = new ByteArrayInputStream("test ftp".getBytes("utf-8"));
- boolean flag = uploadFile("127.0.0.1", 21, "test", "test", "D:/ftp", "test.txt", input);
- System.out.println(flag);
- } catch (UnsupportedEncodingException e) {
- e.printStackTrace();
- }
- }<PRE>
二、下载文件
从FTP服务器下载文件的代码也很简单,参考如下:
-
-
-
-
-
-
-
-
-
-
-
-
- public static boolean downFile(String url, int port,String username, String password, String remotePath,String fileName,String localPath) {
- boolean success = false;
- FTPClient ftp = new FTPClient();
- try {
- int reply;
- ftp.connect(url, port);
-
- ftp.login(username, password);
- reply = ftp.getReplyCode();
- if (!FTPReply.isPositiveCompletion(reply)) {
- ftp.disconnect();
- return success;
- }
- ftp.changeWorkingDirectory(remotePath);
- FTPFile[] fs = ftp.listFiles();
- for(FTPFile ff:fs){
- if(ff.getName().equals(fileName)){
- File localFile = new File(localPath+"/"+ff.getName());
- OutputStream is = new FileOutputStream(localFile);
- ftp.retrieveFile(ff.getName(), is);
- is.close();
- }
- }
- ftp.logout();
- success = true;
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- if (ftp.isConnected()) {
- try {
- ftp.disconnect();
- } catch (IOException ioe) {
- }
- }
- }
- return success;
- }<PRE>
|
|