这几天一个同事要在项目里实现用ftp下载文件. 遇到了很多问题. 于是我推荐他用Jakarta-Commons项目中的net组件在实现. 其实之前我也没有实际用过, 稍稍看了一下文档,知道里面有个ftp包能完成相关的操作. 于是我的同事就兴致勃勃的拿去用了. 可用了以后才发现有很多问题, 搞得焦头烂额. 经过我们的努力, 终于把问题都解决了, 下面我把遇到的问题和解决方案写下来, 以备其他想要用common-net包的朋友参考.
首先把代码贴出来:
1 public class ClientTest {
2 public static void main(String[] args) {
3 String url = "172.17.1.38";
4 String user = "test";
5 String pwd = "test";
6
7 FTPClient ftp = new FTPClient();
8 ftp.setControlEncoding("GBK");
9 FTPClientConfig conf = new FTPClientConfig(FTPClientConfig.SYST_NT);
10 conf.setServerLanguageCode("zh");
11 ftp.configure(conf);
12
13 try {
14 ftp.connect(url);
15 if (ftp.login(user, pwd)) {
16 int reply = ftp.getReplyCode();
17 if (!FTPReply.isPositiveCompletion(reply)) {
18 ftp.disconnect();
19 System.out.println("disconnect");
20 } else {
21 ftp.enterLocalPassiveMode();
22 ftp.setFileType(FTP.BINARY_FILE_TYPE);
23
24 File dir = new File("down");
25 if (!dir.exists()) {
26 dir.mkdirs();
27 }
28
29 String[] names = ftp.listNames();
30 for (String name : names) {
31 File file = new File(dir.getPath() + File.separator + name);
32 if (!file.exists()) {
33 file.createNewFile();
34 }
35 long pos = file.length();
36 RandomAccessFile raf = new RandomAccessFile(file, "rw");
37 raf.seek(pos);
38 ftp.setRestartOffset(pos);
39
40 InputStream is = ftp.retrieveFileStream(name);
41 if (is == null) {
42 System.out.println("no such file:" + name);
43 } else {
44 System.out.println("start getting file:" + name);
45
46 int b;
47 while ((b = is.read()) != -1) {
48 raf.write(b);
49 }
50 if (ftp.getReply() == FTPReply.CODE_226) {
51 System.out.println("done!");
52 }
53 is.close();
54 }
55 raf.close();
56 }
57 }
58 ftp.logout();
59 }
60 } catch (IOException e) {
61 e.printStackTrace();
62 }
63 }
64 }
一, 文件名中文乱码问题.
开始知道能用FTPClient的listNames方法得到当前目录下所有文件的列表. 但是发现中文文件名是乱码. 默认情况下FTPClient使用UTF-8字符集作为和服务器通讯的编码集. 而我们的ftp服务器是在中文windowsXP上装的ServU. 所有使用GBK做为通讯编码集. 经过查找api文档, 我看到了setControlEncoding方法, 试了一下,果然好使. 于是这个问题就解决了:
第8行: ftp.setControlEncoding("GBK")
至于conf.setServerLanguageCode("zh")对这个有什么影响,我还没有验证. 但是只有这句是不行的.
二, 传输binary文件, 由于FTPClient默认使用ASCII作为传输模式, 所有不能传输二进制文件. 通过
ftp.setFileType(FTP.BINARY_FILE_TYPE)个可以解决这个问题, 但是要在login以后执行. 因为这个方法要向服务器发送"TYPE I"命令.
开始的时候用的是setFileTransferMode, 不过不好使. 它会执行 MODE I命令, 服务器不接受.
三, 用被动模式传输: enterLocalPassiveMode()这个到不用在login之后执行, 因为它只改变FTPClient实例的内部属性.
四, 断点续传. 心想应该有支持吧, 于是查API结果找到了setRestartOffset()方法, 试了一下,果真好使. 用RandomAccessFile配合使用, 实现起来还是蛮简单的.
五, 只能传一个文件!!
不知道大家有没有遇到这个问题, 传输第一个文件好使, 后面的的retrieveFileStream(name)都是返回null. 这个实在是令人头痛的问题, 难不成要传一个文件重新建立一次连接? 那样也太土了吧. 但是文档里也没有写, 来点狠的,debug它的源码, 看看它究竟做了什么事情. 首先看一下ftp服务器的日志, 发现日志没问题, 过来的命令和reply都是正确的, 但是发现第一个文件以后没有执行RETR命令. 于是跟踪PASV命令的reply代码,发现不是227,而服务器上的日志明明返回的是227. 难道是FTPClient解析Reply出问题了. 进一步跟踪发现了问题, 原来在一个文件传输过程中会产生两个Reply:
150 Opening BINARY mode data connection for a.sql (19890 Bytes).
226 Transfer complete.
而FTPClient自动消费掉一个,于是解析Reply就发生了错位, 下一个命令的会解析266那条. 接下来的命令都不是解析自己的Reply而是前一次命令的. 所有在PASV命令的Reply码就不对了, FTPClient也就不会执行接下来本应该执行RETR命令.
他不消费,我们来消费吧. 于是在文件传输完成以后, 主动调用一次getReply()把接下来的226消费掉. 这样做是可以解决这个暂时的问题, 但不知道在其他的ftp操作上会不会也有类似的情况. FTPClient这点可做的不大好.
对于上面这个问题, 我本来想修改一下FTPClient这个类来彻底解决问题. 结果发现自己也想不出好办法. 最后还是放弃了.
今天才发现,原来FTPClient有个
completePendingCommand()方法就是用来干这件事情的!
完成的程序,上传,下载,删除
http://www.blogjava.net/Files/mstar/ClientTest.zip