FTP文件上传与下载
FTP文件上传与下载
实现FTP文件上传与下载可以通过以下两种种方式实现:
1、通过JDK自带的API实现;
2、通过Apache提供的API实现。
JDK自带的API实现
package com.cloudpower.util; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import sun.net.TelnetInputStream; import sun.net.TelnetOutputStream; import sun.net.ftp.FtpClient; /** * Java自带的API对FTP的操作 */ public class Ftp { /** * 本地文件名 */ private String localfilename; /** * 远程文件名 */ private String remotefilename; /** * FTP客户端 */ private FtpClient ftpClient; /** * 服务器连接 * @param ip 服务器IP * @param port 服务器端口 * @param user 用户名 * @param password 密码 * @param path 服务器路径 */ public void connectServer(String ip, int port, String user, String password, String path) { try { /* ******连接服务器的两种方法*******/ //第一种方法 // ftpClient = new FtpClient(); // ftpClient.openServer(ip, port); //第二种方法 ftpClient = new FtpClient(ip); ftpClient.login(user, password); // 设置成2进制传输 ftpClient.binary(); System.out.println("login success!"); if (path.length() != 0){ //把远程系统上的目录切换到参数path所指定的目录 ftpClient.cd(path); } ftpClient.binary(); } catch (IOException ex) { ex.printStackTrace(); throw new RuntimeException(ex); } } /** * 关闭连接 */ public void closeConnect() { try { ftpClient.closeServer(); System.out.println("disconnect success"); } catch (IOException ex) { System.out.println("not disconnect"); ex.printStackTrace(); throw new RuntimeException(ex); } } /** * 上传文件 * @param localFile 本地文件 * @param remoteFile 远程文件 */ public void upload(String localFile, String remoteFile) { this.localfilename = localFile; this.remotefilename = remoteFile; TelnetOutputStream os = null; FileInputStream is = null; try { //将远程文件加入输出流中 os = ftpClient.put(this.remotefilename); //获取本地文件的输入流 File file_in = new File(this.localfilename); is = new FileInputStream(file_in); //创建一个缓冲区 byte[] bytes = new byte[1024]; int c; while ((c = is.read(bytes)) != -1) { os.write(bytes, 0, c); } System.out.println("upload success"); } catch (IOException ex) { System.out.println("not upload"); ex.printStackTrace(); throw new RuntimeException(ex); } finally{ try { if(is != null){ is.close(); } } catch (IOException e) { e.printStackTrace(); } finally { try { if(os != null){ os.close(); } } catch (IOException e) { e.printStackTrace(); } } } } /** * 下载文件 * @param remoteFile 远程文件路径(服务器端) * @param localFile 本地文件路径(客户端) */ public void download(String remoteFile, String localFile) { TelnetInputStream is = null; FileOutputStream os = null; try { //获取远程机器上的文件filename,借助TelnetInputStream把该文件传送到本地。 is = ftpClient.get(remoteFile); File file_in = new File(localFile); os = new FileOutputStream(file_in); byte[] bytes = new byte[1024]; int c; while ((c = is.read(bytes)) != -1) { os.write(bytes, 0, c); } System.out.println("download success"); } catch (IOException ex) { System.out.println("not download"); ex.printStackTrace(); throw new RuntimeException(ex); } finally{ try { if(is != null){ is.close(); } } catch (IOException e) { e.printStackTrace(); } finally { try { if(os != null){ os.close(); } } catch (IOException e) { e.printStackTrace(); } } } } public static void main(String agrs[]) { String filepath[] = { "/temp/aa.txt", "/temp/regist.log"}; String localfilepath[] = { "C:\\tmp\\1.txt","C:\\tmp\\2.log"}; Ftp fu = new Ftp(); /* * 使用默认的端口号、用户名、密码以及根目录连接FTP服务器 */ fu.connectServer("127.0.0.1", 22, "anonymous", "IEUser@", "/temp"); //下载 for (int i = 0; i < filepath.length; i++) { fu.download(filepath[i], localfilepath[i]); } String localfile = "E:\\亚古兽.txt"; String remotefile = "/temp/暴龙兽.txt"; //上传 fu.upload(localfile, remotefile); fu.closeConnect(); } }
这种方式没啥可说的,比较简单,也不存在中文乱码的问题。貌似有个缺陷,不能操作大文件,有兴趣的朋友可以试试。
Apache提供的API实现
public class FtpApche { private static FTPClient ftpClient = new FTPClient(); private static String encoding = System.getProperty("file.encoding"); /** * Description: 向FTP服务器上传文件 * * @Version1.0 * @param url FTP服务器hostname * @param port FTP服务器端口 * @param username FTP登录账号 * @param password FTP登录密码 * @param path FTP服务器保存目录,如果是根目录则为“/” * @param filename 上传到FTP服务器上的文件名 * @param input 本地文件输入流 * @return 成功返回true,否则返回false */ public static boolean uploadFile(String url, int port, String username, String password, String path, String filename, InputStream input) { boolean result = false; try { int reply; // 如果采用默认端口,可以使用ftp.connect(url)的方式直接连接FTP服务器 ftpClient.connect(url); // ftp.connect(url, port);// 连接FTP服务器 // 登录 ftpClient.login(username, password); ftpClient.setControlEncoding(encoding); // 检验是否连接成功 reply = ftpClient.getReplyCode(); if (!FTPReply.isPositiveCompletion(reply)) { System.out.println("连接失败"); ftpClient.disconnect(); return result; } // 转移工作目录至指定目录下 boolean change = ftpClient.changeWorkingDirectory(path); ftpClient.setFileType(FTP.BINARY_FILE_TYPE); if (change) { result = ftpClient.storeFile(new String(filename.getBytes(encoding),"iso-8859-1"), input); if (result) { System.out.println("上传成功!"); } } input.close(); ftpClient.logout(); } catch (IOException e) { e.printStackTrace(); } finally { if (ftpClient.isConnected()) { try { ftpClient.disconnect(); } catch (IOException ioe) { } } } return result; } /** * 将本地文件上传到FTP服务器上 */ public void testUpLoadFromDisk() { try { FileInputStream in = new FileInputStream(new File("E:/亚古兽.txt")); boolean flag = uploadFile("127.0.0.1", 21, "zlb","123", "/", "暴龙兽.txt", in); System.out.println(flag); } catch (FileNotFoundException e) { e.printStackTrace(); } } /** * Description: 从FTP服务器下载文件 * * @Version1.0 * @param url FTP服务器hostname * @param port FTP服务器端口 * @param username FTP登录账号 * @param password FTP登录密码 * @param remotePath FTP服务器上的相对路径 * @param fileName 要下载的文件名 * @param localPath 下载后保存到本地的路径 * @return */ public static boolean downFile(String url, int port, String username, String password, String remotePath, String fileName, String localPath) { boolean result = false; try { int reply; ftpClient.setControlEncoding(encoding); /* * 为了上传和下载中文文件,有些地方建议使用以下两句代替 * new String(remotePath.getBytes(encoding),"iso-8859-1")转码。 * 经过测试,通不过。 */ // FTPClientConfig conf = new FTPClientConfig(FTPClientConfig.SYST_NT); // conf.setServerLanguageCode("zh"); ftpClient.connect(url, port); // 如果采用默认端口,可以使用ftp.connect(url)的方式直接连接FTP服务器 ftpClient.login(username, password);// 登录 // 设置文件传输类型为二进制 ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE); // 获取ftp登录应答代码 reply = ftpClient.getReplyCode(); // 验证是否登陆成功 if (!FTPReply.isPositiveCompletion(reply)) { ftpClient.disconnect(); System.err.println("FTP server refused connection."); return result; } // 转移到FTP服务器目录至指定的目录下 ftpClient.changeWorkingDirectory(new String(remotePath.getBytes(encoding),"iso-8859-1")); // 获取文件列表 FTPFile[] fs = ftpClient.listFiles(); for (FTPFile ff : fs) { if (ff.getName().equals(fileName)) { File localFile = new File(localPath + "/" + ff.getName()); OutputStream is = new FileOutputStream(localFile); ftpClient.retrieveFile(ff.getName(), is); is.close(); } } ftpClient.logout(); result = true; } catch (IOException e) { e.printStackTrace(); } finally { if (ftpClient.isConnected()) { try { ftpClient.disconnect(); } catch (IOException ioe) { } } } return result; } /** * 将FTP服务器上文件下载到本地 */ public void testDownFile() { try { boolean flag = downFile("127.0.0.1", 21, "tabu", "123", "/", "暴龙兽.txt", "D:/"); System.out.println(flag); } catch (Exception e) { e.printStackTrace(); } } public static void main(String[] args) { FtpApche fa = new FtpApche(); fa.testDownFile(); } }
这种方式的话需要注意中文乱码问题啦,如果你设置不恰当,有可能上传的文件名会为乱码,有的时候根本就上传不上去,当然,也不会跟你提示,因为原本就没异常。在网上找了好多解答方案,众说纷纭,几乎都是从一个版本拷贝过去的,也没有经过自己的真是检验。为此,也吃了不少苦头。就此,可以继续往下看。
FTPClient下载文件取不到的问题
private static String encoding = System.getProperty("file.encoding");
...
ftpClient.changeWorkingDirectory(new String(remotePath.getBytes(encoding),"iso-8859-1"));
...
ftpClientInFunction.retrieveFile(new String(fInFunction.getName().getBytes("GBK"),"iso-8859-1"), is);
编码问题
在FTP协议里面,规定文件名编码为iso-8859-1,所以目录名或文件名需要转码。
接下来的问题是,我们应该将什么编码转换为此格式。
如果FTP系统的编码格式为“GBK”时,这时是没有问题的;
但是,如果系统的编码格式为“UTF-8”时,那就会仍然出现乱码。
所以,我们需要通过代码先获取系统的编码格式,然后通过此编码格式转换为ISO-8859-1的编码格式。
获取系统编码格式
private static String encoding = System.getProperty("file.encoding");
Apache 自身bug问题
ftpclient listFile方法无法返回正确的数据,一般返回时null ,使用listNames 返回的也是只有文件名,这个是ftpclient 工具包的一个bug。
有人给出了解决的代码,需要加入两个类class FTPTimestampParserImplExZH
和class UnixFTPEntryParser
,这两个类打包在package com.zznode.tnms.ra.c11n.nj.resource.ftp
中。
/*
* ftpclient listFile方法无法返回正确的数据,一般返回时null ,使用listNames 返回的也是只有文件名,这个是ftpclient 工具包的一个bug,
* 需要用下面的代码,并在源码中加入:
* package com.zznode.tnms.ra.c11n.nj.resource.ftp
* class FTPTimestampParserImplExZH
* class UnixFTPEntryParser
*/
FTPClientConfig ftpCfg = new FTPClientConfig("com.zznode.tnms.ra.c11n.nj.resource.ftp.UnixFTPEntryParser");
FTPTimestampParserImplExZH.java
package com.zznode.tnms.ra.c11n.nj.resource.ftp; 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; /** * 此类的原始贡献瀤?ohzwei206? * 解决apache ftp中文语言环境下, * FTPClient.listFiles()为空的bug */ public class FTPTimestampParserImplExZH extends FTPTimestampParserImpl { private SimpleDateFormat defaultDateFormat = new SimpleDateFormat("mm d HH:mm"); //原来是hh:mm 12小时制,现在改为HH:mm 24小时制 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(java.lang.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 do not 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; } }
UnixFTPEntryParser.java
package com.zznode.tnms.ra.c11n.nj.resource.ftp; /* * 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: * <code>"yyyy-MM-dd HH:mm</code>. * 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. * <p> * For now end users may specify this format only via * <code>UnixFTPEntryParser(FTPClientConfig)</code>. * 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憿暿26 //而出错的hp机器下的显示䶿8暿0日(没有空格分开ﺿ //故无法匹配瀦¥错 //将下面字符串改为ﺿ + "((?:\\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 * <code>REGEX</code> 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 * <code>REGEX</code> 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 <code> FTPFile </code> * instance. If the file listing line doesn't describe a file, * <code> null </code> is returned, otherwise a <code> FTPFile </code> * instance representing the files in the directory is returned. * <p> * @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); } }