java对ftp操作遇到的一系列的坑

首先让我先讲一下最近项目中遇到的服务器主要使用的ftp服务器构建工具 (IIS 、ser-u、filezilla)

IIS(摘录自百度百科)

  • IIS 是widow中IIS是一个World Wide Web server。Gopher server和FTP server全部包容在里面。 IIS意味着你能发布网页,并且有ASP(Active Server Pages)、JAVA、VBscript产生页面,有着一些扩展功能。IIS支持一些有趣的东西,像有编辑环境的界面(FRONTPAGE)、有全文检索功能的(INDEX SERVER)、有多媒体功能的(NET SHOW) 其次,IIS是随Windows NT Server 4.0一起提供的文件和应用程序服务器,是在Windows NT Server上建立Internet服务器的基本组件。它与Windows NT Server完全集成,允许使用Windows NT Server内置的安全性以及NTFS文件系统建立强大灵活的Internet/Intranet站点。IIS(Internet Information Server,互联网信息服务)是一种Web(网页)服务组件,其中包括Web服务器、FTP服务器、NNTP服务器和SMTP服务器,分别用于网页浏览、文件传输、新闻服务和邮件发送等方面,它使得在网络(包括互联网和局域网)上发布信息成了一件很容易的事。

ser-u(摘录自百度百科)

  • Serv-U™ - Windows平台的FTP服务器软件Serv-U 是目前众多的FTP 服务器软件之一。通过使用Serv-U,用户能够将任何一台PC 设置成一个FTP 服务器,这样,用户或其他使用者就能够使用FTP 协议,通过在同一网络上的任何一台PC与FTP 服务器连接,进行文件或目录的复制,移动,创建,和删除等。这里提到的FTP 协议是专门被用来规定计算机之间进行文件传输的标准和规则,正是因为有了像FTP 这样的专门协议,才使得人们能够通过不同类型的计算机,使用不同类型的操作系统,对不同类型的文件进行相互传递。

filezilla(摘录百度百科)

  • FileZilla是一个免费开源的FTP软件,分为客户端版本和服务器版本,具备所有的FTP软件功能。可控性、有条理的界面和管理多站点的简化方式使得Filezilla客户端版成为一个方便高效的FTP客户端工具,而FileZilla Server则是一个小巧并且可靠的支持FTP&SFTP的FTP服务器软件。

java中操作ftp api-ftpclient

  • 相关的的api在下面
    http://commons.apache.org/proper/commons-net/apidocs/org/apache/commons/net/ftp/FTPClient.html

开始记录在使用这两个ftp构建工具遇到的一些问题(win7 平台)

  • ftp 某些方法 如listFile 等方法无效,卡死
    这个问题是两个工具中我都遇到了 ,这个问题是关于主动模和被动模式有关,这里我贴出我在看了相关的博文的总结:
    1. 主动模式
      是可以这样想服务端的端口确定 比如命令端口21端口,还有数据端口20端口 主动链接放在客户端,客户端会开启一个大于1023端口去链接服务器21端口,还有开启N+1端口去链接服务器的数据端口,并监听这两个端口
      而客户端又要向服务端开放端口 ,这个是客户端开发由外向内的端口链接(在数据端口上面),,,所以一般会被防火墙拦截 (端口随机)
      所以一般会出现可以连接上,但是访问数据会出现异常的现象,就比如今天我用eclipse 的时候出现的问题,调用listFile 会出现卡死在创建socket上面了
    2. 被动模式
      被动方式与主动方式不同的是,先是一样的21端口链接,但是链接成功后,服务器端先开启一个数据端口,告知客户端通过本地的端口来连接自己的这个开放的端口,一般的ftp服务器都允许客户端通过高端口位链接自己。

卡死,由于ftpclient 操作默认是是通过主动模式 ,可以设置为被动模式(但是自己当时使用eclipse 会出现这个问题 ,使用idea却没有问题 ,跟踪代码是在卡死在了创建socket上面)

            ftpClient.enterLocalPassiveMode();
  • ftp通过ser-u构建,出现的问题
    ftpclient listFile方法无法返回正确的数据,一般返回时null ,使用listNames 返回的也是只有文件名,这个是ftpclient 工具包的一个bug,这个有专门的大神分析了源码,并给出了多种的解决办法,比如我使用了下面这种(分析太多,,有机会仔细看下)
ftpClient.configure(new FTPClientConfig("com.zznode.tnms.ra.c11n.nj.resource.ftp.UnixFTPEntryParser"));

奇怪的是使用IIS 构建的服务器并不会出现这个问题

  • 使用ser-u构建 ftp服务器获取文件列表,会获取当前目录和上一级目录
    估计是为了兼容linux 和window不同的平台吧 ,,不是吧权限模型也改成了和linux一样的rwx嘛,但是IIS 不会反悔上一级和当前的目录,所以如果是使用ser-u 构建的ftp,那么在进行ftp文件无限层级检索的时候要记得过滤
.   ..

这两个目录 ,不然会死循环~~~

  • filezilla 出现listfile为空的情况(好像是之前使用ser-u也出现类似的情况)
    网上说是解决ftp支持系统中文环境,创建下面两个类文件:

    package com.xpp.util.filezillapatch;
    import java.text.ParseException;
    import java.text.ParsePosition;
    import java.text.SimpleDateFormat;
    import java.util.Calendar;
    import java.util.Date;
    import org.apache.commons.net.ftp.parser.FTPTimestampParserImpl;
    public class FTPTimestampParserImplExZH extends FTPTimestampParserImpl {
        private SimpleDateFormat defaultDateFormat = new SimpleDateFormat("mm d hh:mm");
        private SimpleDateFormat recentDateFormat = new SimpleDateFormat("yyyy mm d");
        private String formatDate_Zh2En(String timeStrZh) {
            if (timeStrZh == null) {
                return "";
            }
            int len = timeStrZh.length();
            StringBuffer sb = new StringBuffer(len);
            char ch = ' ';
            for (int i = 0; i < len; i++) {
                ch = timeStrZh.charAt(i);
                if ((ch >= '0' && ch <= '9') || ch == ' ' || ch == ':') {
                    sb.append(ch);
                }
            }
            return sb.toString();
        }
        /**
         * Implements the one {@link FTPTimestampParser#parseTimestamp(String) method} in the {@link FTPTimestampParser
         * FTPTimestampParser} interface according to this algorithm: If the recentDateFormat member has been defined, try
         * to parse the supplied string with that. If that parse fails, or if the recentDateFormat member has not been
         * defined, attempt to parse with the defaultDateFormat member. If that fails, throw a ParseException.
         *
         * @see org.apache.commons.net.ftp.parser.FTPTimestampParser#parseTimestamp(String)
         */
        public Calendar parseTimestamp(String timestampStr) throws ParseException {
            timestampStr = formatDate_Zh2En(timestampStr);
            Calendar now = Calendar.getInstance();
            now.setTimeZone(this.getServerTimeZone());
    
            Calendar working = Calendar.getInstance();
            working.setTimeZone(this.getServerTimeZone());
            ParsePosition pp = new ParsePosition(0);
    
            Date parsed = null;
            if (this.recentDateFormat != null) {
                parsed = recentDateFormat.parse(timestampStr, pp);
            }
            if (parsed != null && pp.getIndex() == timestampStr.length()) {
                working.setTime(parsed);
                working.set(Calendar.YEAR, now.get(Calendar.YEAR));
                if (working.after(now)) {
                    working.add(Calendar.YEAR, -1);
                }
            } else {
                pp = new ParsePosition(0);
                parsed = defaultDateFormat.parse(timestampStr, pp);
                // note, length checks are mandatory for us since
                // SimpleDateFormat methods will succeed if less than
                // full string is matched. They will also accept,
                // despite "leniency" setting, a two-digit number as
                // a valid year (e.g. 22:04 will parse as 22 A.D.)
                // so could mistakenly confuse an hour with a year,
                // if we don't insist on full length parsing.
                if (parsed != null && pp.getIndex() == timestampStr.length()) {
                    working.setTime(parsed);
                } else {
                    throw new ParseException("Timestamp could not be parsed with older or recent DateFormat", pp.getIndex());
                }
            }
            return working;
        }
    }
    

第二个文件

    package com.xpp.util.filezillapatch;
    /*
     * Copyright 2001-2005 The Apache Software Foundation
     *
     * Licensed under the Apache License, Version 2.0 (the "License");
     * you may not use this file except in compliance with the License.
     * You may obtain a copy of the License at
     *
     *     http://www.apache.org/licenses/LICENSE-2.0
     *
     * Unless required by applicable law or agreed to in writing, software
     * distributed under the License is distributed on an "AS IS" BASIS,
     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
     * See the License for the specific language governing permissions and
     * limitations under the License.
     */

    import java.text.ParseException;
    import java.util.Calendar;

    import org.apache.commons.net.ftp.FTPClientConfig;
    import org.apache.commons.net.ftp.FTPFile;
    import org.apache.commons.net.ftp.parser.ConfigurableFTPFileEntryParserImpl;
    import org.apache.log4j.Logger;

    /**
     * 注:common-net-1.4.1.jar源码,修改对于日期中文格式的支持,从而解决FTPClient.listFiles()返回为空问题
     * Implementation FTPFileEntryParser and FTPFileListParser for standard
     * Unix Systems.
     *
     * This class is based on the logic of Daniel Savarese's
     * DefaultFTPListParser, but adapted to use regular expressions and to fit the
     * new FTPFileEntryParser interface.
     * @version $Id: UnixFTPEntryParser.java 161712 2005-04-18 02:57:04Z scohen $
     * @see org.apache.commons.net.ftp.FTPFileEntryParser FTPFileEntryParser (for usage instructions)
     */
    public class UnixFTPEntryParser extends ConfigurableFTPFileEntryParserImpl
    {
        private static Logger logger = Logger.getLogger(UnixFTPEntryParser.class);
        /**
         * months abbreviations looked for by this parser.  Also used
         * to determine which month is matched by the parser
         */
        private static final String DEFAULT_MONTHS =
                "(Jan|Feb|Mar|Apr|May|Jun|Jul|Aug|Sep|Oct|Nov|Dec)";

        static final String DEFAULT_DATE_FORMAT
                = "MMM d yyyy"; //Nov 9 2001

        static final String DEFAULT_RECENT_DATE_FORMAT
                = "MMM d HH:mm"; //Nov 9 20:06

        static final String NUMERIC_DATE_FORMAT
                = "yyyy-MM-dd HH:mm"; //2001-11-09 20:06

        /**
         * Some Linux distributions are now shipping an FTP server which formats
         * file listing dates in an all-numeric format:
         * "yyyy-MM-dd HH:mm.
         * This is a very welcome development,  and hopefully it will soon become
         * the standard.  However, since it is so new, for now, and possibly
         * forever, we merely accomodate it, but do not make it the default.
         * 

* For now end users may specify this format only via * UnixFTPEntryParser(FTPClientConfig). * Steve Cohen - 2005-04-17 */ public static final FTPClientConfig NUMERIC_DATE_CONFIG = new FTPClientConfig( FTPClientConfig.SYST_UNIX, NUMERIC_DATE_FORMAT, null, null, null, null); /** * this is the regular expression used by this parser. * * Permissions: * r the file is readable * w the file is writable * x the file is executable * - the indicated permission is not granted * L mandatory locking occurs during access (the set-group-ID bit is * on and the group execution bit is off) * s the set-user-ID or set-group-ID bit is on, and the corresponding * user or group execution bit is also on * S undefined bit-state (the set-user-ID bit is on and the user * execution bit is off) * t the 1000 (octal) bit, or sticky bit, is on [see chmod(1)], and * execution is on * T the 1000 bit is turned on, and execution is off (undefined bit- * state) */ private static final String REGEX = "([bcdlfmpSs-])" +"(((r|-)(w|-)([xsStTL-]))((r|-)(w|-)([xsStTL-]))((r|-)(w|-)([xsStTL-])))\\+?\\s+" + "(\\d+)\\s+" + "(\\S+)\\s+" + "(?:(\\S+)\\s+)?" + "(\\d+)\\s+" /* numeric or standard format date */ //问题出在此处,这个匹配只匹配2中形式: //(1)2008-08-03 //(2)Jan 9或4月 26 //而出错的hp机器下的显示为 8月20日(没有空格分开) //故无法匹配而报错 //将下面字符串改为: + "((?:\\d+[-/]\\d+[-/]\\d+)|(?:\\S+\\s+\\S+)|(?:\\S+))\\s+" //+ "((?:\\d+[-/]\\d+[-/]\\d+)|(?:\\S+\\s+\\S+))\\s+" /* year (for non-recent standard format) or time (for numeric or recent standard format */ + "(\\d+(?::\\d+)?)\\s+" + "(\\S*)(\\s*.*)"; /** * The default constructor for a UnixFTPEntryParser object. * * @exception IllegalArgumentException * Thrown if the regular expression is unparseable. Should not be seen * under normal conditions. It it is seen, this is a sign that * REGEX is not a valid regular expression. */ public UnixFTPEntryParser() { this(null); } /** * This constructor allows the creation of a UnixFTPEntryParser object with * something other than the default configuration. * * @param config The {@link FTPClientConfig configuration} object used to * configure this parser. * @exception IllegalArgumentException * Thrown if the regular expression is unparseable. Should not be seen * under normal conditions. It it is seen, this is a sign that * REGEX is not a valid regular expression. * @since 1.4 */ public UnixFTPEntryParser(FTPClientConfig config) { super(REGEX); configure(config); } /** * Parses a line of a unix (standard) FTP server file listing and converts * it into a usable format in the form of an FTPFile * instance. If the file listing line doesn't describe a file, * null is returned, otherwise a FTPFile * instance representing the files in the directory is returned. *

* @param entry A line of text from the file listing * @return An FTPFile instance corresponding to the supplied entry */ public FTPFile parseFTPEntry(String entry) { FTPFile file = new FTPFile(); file.setRawListing(entry); int type; boolean isDevice = false; if (matches(entry)) { String typeStr = group(1); String hardLinkCount = group(15); String usr = group(16); String grp = group(17); String filesize = group(18); String datestr = group(19) + " " + group(20); String name = group(21); String endtoken = group(22); try { //file.setTimestamp(super.parseTimestamp(datestr)); FTPTimestampParserImplExZH Zh2En = new FTPTimestampParserImplExZH(); file.setTimestamp(Zh2En.parseTimestamp(datestr)); } catch (ParseException e) { //logger.error(e, e); //return null; // this is a parsing failure too. //logger.info(entry+":修改日期重置为当前时间"); file.setTimestamp(Calendar.getInstance()); } // bcdlfmpSs- switch (typeStr.charAt(0)) { case 'd': type = FTPFile.DIRECTORY_TYPE; break; case 'l': type = FTPFile.SYMBOLIC_LINK_TYPE; break; case 'b': case 'c': isDevice = true; // break; - fall through case 'f': case '-': type = FTPFile.FILE_TYPE; break; default: type = FTPFile.UNKNOWN_TYPE; } file.setType(type); int g = 4; for (int access = 0; access < 3; access++, g += 4) { // Use != '-' to avoid having to check for suid and sticky bits file.setPermission(access, FTPFile.READ_PERMISSION, (!group(g).equals("-"))); file.setPermission(access, FTPFile.WRITE_PERMISSION, (!group(g + 1).equals("-"))); String execPerm = group(g + 2); if (!execPerm.equals("-") && !Character.isUpperCase(execPerm.charAt(0))) { file.setPermission(access, FTPFile.EXECUTE_PERMISSION, true); } else { file.setPermission(access, FTPFile.EXECUTE_PERMISSION, false); } } if (!isDevice) { try { file.setHardLinkCount(Integer.parseInt(hardLinkCount)); } catch (NumberFormatException e) { // intentionally do nothing } } file.setUser(usr); file.setGroup(grp); try { file.setSize(Long.parseLong(filesize)); } catch (NumberFormatException e) { // intentionally do nothing } if (null == endtoken) { file.setName(name); } else { // oddball cases like symbolic links, file names // with spaces in them. name += endtoken; if (type == FTPFile.SYMBOLIC_LINK_TYPE) { int end = name.indexOf(" -> "); // Give up if no link indicator is present if (end == -1) { file.setName(name); } else { file.setName(name.substring(0, end)); file.setLink(name.substring(end + 4)); } } else { file.setName(name); } } return file; } else { logger.info("matches(entry) failure:"+entry); } return null; } /** * Defines a default configuration to be used when this class is * instantiated without a {@link FTPClientConfig FTPClientConfig} * parameter being specified. * @return the default configuration for this parser. */ protected FTPClientConfig getDefaultConfiguration() { return new FTPClientConfig( FTPClientConfig.SYST_UNIX, DEFAULT_DATE_FORMAT, DEFAULT_RECENT_DATE_FORMAT, null, null, null); } }

然后在你连接ftp的地方加上

 ftp.configure(new FTPClientConfig("com.xpp.util.filezillapatch.UnixFTPEntryParser"));

这样就能返回正常的数据了

你可能感兴趣的:(工作)