上周给某单位做了一次开发培训, 学员们希望学习用FTP上传下载的Java实现. 上网找了下, 最后找到了两个, 一个是 Apache的Jakarta Commons Net来实现, 其设置选项还是挺多的, 网上的资料也很多, Google一下一大把, 另一个则是http://www.enterprisedt.com/ 开发的一个开源和商业版本的FTP类库, 商业版本支持批量目录的上传和下载. 用的过程中发现了中文问题, 不过最后还是胜利解决了.
先说一下搭建测试FTP服务器, 一般Windows下用的多的有Server-U(收费)等, 开源的有FileZilla FTP Server(经测试貌似无法上传超过100MB的文件, 不知道哪里有设置, 最后否定了), 目前使用的是一款免费绿色小巧的FTP服务器来做测试: TYPSoft FTP Server. 下载后直接解压缩即可运行, 不过如果要显示中文界面的话, 请修改config.ini:
LangFile=chineses
之后建立用户进行测试就可以了. 界面如下所示:
一般的客户端连接中文Windows下的FTP服务器, 默认编码是GB2312, 因此不加设置的话很容易无法上传和下载中文附件. 网上有一些代码片段讨论Jakarta Commons Net, 但是看起来正确的解决此问题的代码不多. 其实FtpClient类已经提供了设置的方法, 调用:
ftpClient.setControlEncoding("gb2312");
即可, 这样在打开Socket的时候都会才用正确的reader和writer了. 相关的源码片段如下:
/**
* Sets the character encoding used by the FTP control connection.
* Some FTP servers require that commands be issued in a non-ASCII
* encoding like UTF-8 so that filenames with multi-byte character
* representations (e.g, Big 8) can be specified.
*
* @param encoding The new character encoding for the control connection.
*/
public void setControlEncoding(String encoding) {
_controlEncoding = encoding;
}
/**
* @return The character encoding used to communicate over the
* control connection.
*/
public String getControlEncoding() {
return _controlEncoding;
}
下面要说的是edtftpj, 去其官方网站下载得到ZIP, 解压缩后即可运行其自带的例子, 不过默认清空下不支持汉字. 例子及压缩包内容如下图所示:
可见支持的功能还是挺全面的, 要看的例子就是upload_download_and_delete_a_file, 现在新建一个Java项目, 把lib\edtftpj.jar加入项目即可,然后将例子复制进来编译运行, 如下所示:
import com.enterprisedt.net.ftp.FileTransferClient;
import com.enterprisedt.util.debug.Level;
import com.enterprisedt.util.debug.Logger;
import java.io.File;
public class UploadDownloadFiles {
public static void main(String[] args) {
// we want remote host, user name and password
if (args.length < 3) {
System.out
.println("Usage: run remote-host username password");
System.exit(1);
}
// extract command-line arguments
String host = args[0];
String username = args[1];
String password = args[2];
String filename = "UploadDownloadFiles.java";
// set up logger so that we get some output
Logger log = Logger.getLogger(UploadDownloadFiles.class);
Logger.setLevel(Level.INFO);
FileTransferClient ftp = null;
try {
// create client
log.info("Creating FTP client");
ftp = new FileTransferClient();
// set remote host
ftp.setRemoteHost(host);
ftp.setUserName(username);
ftp.setPassword(password);
// connect to the server
log.info("Connecting to server " + host);
ftp.connect();
log.info("Connected and logged in to server " + host);
log.info("Uploading file");
ftp.uploadFile(filename, filename);
log.info("File uploaded");
log.info("Downloading file");
ftp.downloadFile(filename + ".copy", filename);
log.info("File downloaded");
log.info("Deleting remote file");
ftp.deleteFile(filename);
log.info("Deleted remote file");
File file = new File(filename + ".copy");
file.delete();
log.info("Deleted local file copy");
// Shut down client
log.info("Quitting client");
ftp.disconnect();
log.info("Example complete");
} catch (Exception e) {
e.printStackTrace();
}
}
}
具体操作包括上传,下载,不过当文件名为中文时候, 上传和下载都会出现问题, 报错提示服务器找不到文件. 最终解决方法是继承FileTransferClient,然后获取连接时的配置信息然后修改交互时的字符集:
import com.enterprisedt.net.ftp.FileTransferClient;
/**
* 可以设置连接时的字符集的FTP客户端.
* @author BeanSoft
* 2008-11
*/
public class SetEncodingFileTransferClient extends FileTransferClient {
/**
* 设置连接时的字符集, 默认值是US-ASCII.
* @param controlEncoding 字符集名, 如GB2312等
*/
public synchronized void setControlEncoding(String controlEncoding) {
super.masterContext.setControlEncoding(controlEncoding);
}
}
相应的测试代码是:
import java.io.File;
import com.enterprisedt.util.debug.Level;
import com.enterprisedt.util.debug.Logger;
public class UploadDownloadFiles {
public static void main(String[] args) {
// extract command-line arguments
String host = "localhost";
String username = "test";
String password = "test";
String filename = "图片输出.gif";
// set up logger so that we get some output
Logger log = Logger.getLogger(UploadDownloadFiles.class);
Logger.setLevel(Level.INFO);
SetEncodingFileTransferClient ftp = null;
try {
// create client
log.info("Creating FTP client");
ftp = new SetEncodingFileTransferClient();
// set remote host
ftp.setRemoteHost(host);
ftp.setUserName(username);
ftp.setPassword(password);
ftp.setControlEncoding("GB2312");
// connect to the server
log.info("Connecting to server " + host);
ftp.connect();
log.info("Connected and logged in to server " + host);
log.info("Uploading file");
ftp.uploadFile(filename, filename);
log.info("File uploaded");
log.info("Downloading file");
ftp.downloadFile(filename + ".copy", filename);
log.info("File downloaded");
log.info("Deleting remote file");
//ftp.deleteFile(filename);
log.info("Deleted remote file");
File file = new File(filename + ".copy");
// file.delete();
log.info("Deleted local file copy");
// Shut down client
log.info("Quitting client");
ftp.disconnect();
log.info("Example complete");
} catch (Exception e) {
e.printStackTrace();
}
}
}
运行后服务器可看到正确的文件名, 而本机则可以下载到正确的文件副本.
输出日志为:
INFO [ftp.UploadDownloadFiles] 26 十一月 2008 21:02:48.203 : Creating FTP client
INFO [ftp.UploadDownloadFiles] 26 十一月 2008 21:02:48.359 : Connecting to server localhost
INFO [ftp.UploadDownloadFiles] 26 十一月 2008 21:02:48.546 : Connected and logged in to server localhost
INFO [ftp.UploadDownloadFiles] 26 十一月 2008 21:02:48.546 : Uploading file
INFO [ftp.UploadDownloadFiles] 26 十一月 2008 21:02:48.703 : File uploaded
INFO [ftp.UploadDownloadFiles] 26 十一月 2008 21:02:48.703 : Downloading file
INFO [ftp.UploadDownloadFiles] 26 十一月 2008 21:02:48.734 : File downloaded
INFO [ftp.UploadDownloadFiles] 26 十一月 2008 21:02:48.734 : Deleting remote file
INFO [ftp.UploadDownloadFiles] 26 十一月 2008 21:02:48.734 : Deleted remote file
INFO [ftp.UploadDownloadFiles] 26 十一月 2008 21:02:48.734 : Deleted local file copy
INFO [ftp.UploadDownloadFiles] 26 十一月 2008 21:02:48.734 : Quitting client
INFO [ftp.UploadDownloadFiles] 26 十一月 2008 21:02:48.734 : Example complete
运行后一切正常, 非常好. 而此软件包的付费版本可支持目录批量上传和下载(Apache Commons Net 貌似不支持), 当然了, 许可证就要自己想办法Crack了:
import com.enterprisedt.net.ftp.FTPClient;
import com.enterprisedt.net.ftp.pro.ProFTPClient;
import com.enterprisedt.util.debug.Level;
import com.enterprisedt.util.debug.Logger;
import java.io.File;
public class TransferMultipleFilesDirectories {
public static void main(String[] args) {
// we want remote host, user name and password
if (args.length < 5) {
System.out
.println("Usage: run remote-host username password localdir remotedir");
System.exit(1);
}
// extract command-line arguments
String host = args[0];
String username = args[1];
String password = args[2];
String localDir = args[3];
String remoteDir = args[4];
// set up logger so that we get some output
Logger log = Logger.getLogger(TransferMultipleFilesDirectories.class);
Logger.setLevel(Level.DEBUG);
ProFTPClient ftp = null;
try {
// create client
log.info("Creating FTP client");
ftp = new ProFTPClient();
// set remote host
log.info("Setting remote host");
ftp.setRemoteHost(host);
// connect to the server
log.info("Connecting to server " + host);
ftp.connect();
log.info("Connected to server " + host);
// log in
log.info("Logging in with username=" + username + " and password="
+ password);
ftp.login(username, password);
log.info("Logged in");
log.info("Uploading directory");
ftp.mput(localDir, remoteDir, "*.html", true);
log.info("Directory uploaded");
log.info("Downloading directory");
ftp.mget(localDir + ".copy", remoteDir, "*.html", true);
log.info("Directory downloaded");
log.info("Deleting remote directory");
ftp.rmdir(remoteDir, true);
log.info("Remote directory deleted");
// Shut down client
log.info("Quitting client");
ftp.quit();
log.info("Example complete");
} catch (Exception e) {
e.printStackTrace();
}
}
}
至此, 我们的任务已经完成, 可以加上定时器之类的软件或者类库实现定时同步/备份文件等功能. 想获取本项目源代码? 请点击 http://cid-519b3f7aa2172030.skydrive.live.com/self.aspx/java/opensource/javaftp.zip 138KB 下载(单线程下载, 请不要用下载软件如迅雷).