Java 使用apache commons net FTPClient传输原始二进制文件?

Java 使用apache commons net FTPClient传输原始二进制文件?,java,ftp,binary-data,apache-commons-net,Java,Ftp,Binary Data,Apache Commons Net,更新:已解决 我在登录之前调用了FTPClient.setFileType(),导致FTP服务器使用默认模式(ASCII),无论我将其设置为什么。另一方面,客户端的行为就好像文件类型已正确设置一样二进制模式现在完全按照需要工作,在所有情况下逐字节传输文件。我所要做的就是在wireshark中进行一点流量嗅探,然后使用netcat模拟FTP命令,看看发生了什么。为什么我两天前没想到!?谢谢大家的帮助 我有一个xml文件,utf-16编码,我正在使用apache的commons-net-2.0 ja

更新:已解决

我在登录之前调用了
FTPClient.setFileType()
,导致FTP服务器使用默认模式(
ASCII
),无论我将其设置为什么。另一方面,客户端的行为就好像文件类型已正确设置一样<代码>二进制模式现在完全按照需要工作,在所有情况下逐字节传输文件。我所要做的就是在wireshark中进行一点流量嗅探,然后使用netcat模拟FTP命令,看看发生了什么。为什么我两天前没想到!?谢谢大家的帮助

我有一个xml文件,utf-16编码,我正在使用apache的commons-net-2.0 java库的FTPClient从FTP站点下载。它支持两种传输模式:
ASCII\u FILE\u TYPE
BINARY\u FILE\u TYPE
,区别在于
ASCII
将用适当的本地行分隔符替换行分隔符(
'\r\n'
或仅
'\n'
--十六进制,
0x0d0a
或仅
0x0a
)。我的问题是:我有一个utf-16编码的测试文件,它包含以下内容:



废话

这是十六进制:
0000000:003c 003f 0078 006d 006c 0020 0076 0065…

0000050:003c 0064 0061 0074 0061 003e 000a 0009…..

0000060:003c 0062 006c 0061 0068 003e 0062 006c..b.l

0000070:0061 0068 003c 002f 0062 006c 0061 0068.a.h.…

当我对这个文件使用
ASCII
模式时,它会正确地逐字节传输;结果具有相同的md5sum。伟大的当我使用
BINARY
传输模式时,该模式除了将
InputStream
中的字节洗牌到
OutputStream
中之外什么都不做,结果是换行符(
0x0a
)转换为回车+换行符对(
0x0d0a
)。以下是二进制传输后的十六进制:

0000000:003c 003f 0078 006d 006c 0020 0076 0065…

0000050:0a00 3c00 6400 6100 7400 6100 3e00 0d0a…..

0000060:0009 003c 0062 006c 0061 0068 003e 0062….b

0000070:006c 0061 0068 003c 002f 0062 006c 0061.l.a.h.。

它不仅转换换行符(不应该),而且不尊重utf-16编码(我不希望它知道应该这样做,它只是一个愚蠢的FTP管道)。如果不进行进一步处理以重新对齐字节,则无法读取结果。我只会使用
ASCII
模式,但我的应用程序也会在同一管道中移动真正的二进制数据(mp3文件和jpeg图像)。在这些二进制文件上使用
BINARY
传输模式也会导致将随机
0x0d
注入到其内容中,这无法安全删除,因为二进制数据通常包含合法的
0x0d0a
序列。如果我对这些文件使用
ASCII
模式,那么“聪明”ftp客户端会将这些
0x0d0a
s转换为
0x0a
,无论我做什么,都会使文件不一致

我想我的问题是:有没有人知道有什么好的java FTP库可以将该死的字节从这里移到这里,或者我必须破解apache commons-net-2.0并维护我自己的FTP客户端代码,只用于这个简单的应用程序?还有其他人处理过这种奇怪的行为吗?如有任何建议,将不胜感激

我查看了commons net的源代码,它看起来并不对使用
BINARY
模式时的奇怪行为负责。但是它在
二进制模式下读取的
InputStream
只是一个
java.io.bufferedintustream
封装在套接字
InputStream
周围。这些较低级别的java流是否进行过任何奇怪的字节操作?如果他们这样做,我会感到震惊,但我不知道这里还会发生什么

编辑1:

下面是一段最简单的代码,它模仿了我下载文件的过程。要编译,只需执行以下操作

javac -classpath /path/to/commons-net-2.0.jar Main.java
要运行,您需要文件下载到的目录/tmp/ascii和/tmp/binary,以及一个ftp站点,该站点中设置了文件。代码还需要配置适当的ftp主机、用户名和密码。我将该文件放在测试ftp站点的test/文件夹下,并将其命名为test.xml文件。测试文件应该至少有一行以上,并且是utf-16编码的(这可能不是必需的,但将有助于重新创建我的确切情况)。打开一个新文件后,我使用了vim的
:set fileencoding=utf-16
命令,并输入了上面引用的xml文本。最后,要跑,就做吧

java -cp .:/path/to/commons-net-2.0.jar Main
代码:

(注意:此代码已修改为使用自定义FTPClient对象,链接在下面的“编辑2”下)

编辑2:

好的,我遵循了
CheckedXputStream
建议,下面是我的结果。我复制了一个名为
MyFTPClient
的apache的
FTPClient
,并使用
CRC32
校验和将
SocketInputStream
BufferedInputStream
包装在一个
CheckedInputStream
中。此外,我将提供给
FTPClient
FileOutputStream
包装起来,用
CRC32
校验和将输出存储在
CheckOutputStream
中。MyFTPClient的代码已经发布,我已经修改了上面的测试代码以使用这个版本的FTPClient(尝试将一个要点URL发布到修改后的代码,但我需要10个信誉点来发布多个URL!),
test.xml
test.mp3
,结果如下:

14:00:08,644 DEBUG [main,TestMain] TEST.XML ASCII
14:00:08,919 DEBUG [main,MyFTPClient] Socket CRC32: 2739864033
14:00:08,919 DEBUG [main,MyFTPClient] Buffer CRC32: 2739864033
14:00:08,954 DEBUG [main,FTPUtils] FileOut CRC32: 866869773

14:00:08,955 DEBUG [main,TestMain] TEST.XML BINARY
14:00:09,270 DEBUG [main,MyFTPClient] Socket CRC32: 2739864033
14:00:09,270 DEBUG [main,MyFTPClient] Buffer CRC32: 2739864033
14:00:09,310 DEBUG [main,FTPUtils] FileOut CRC32: 2739864033

14:00:09,310 DEBUG [main,TestMain] TEST.MP3 ASCII
14:00:10,635 DEBUG [main,MyFTPClient] Socket CRC32: 60615183
14:00:10,635 DEBUG [main,MyFTPClient] Buffer CRC32: 60615183
14:00:10,636 DEBUG [main,FTPUtils] FileOut CRC32: 2352009735

14:00:10,636 DEBUG [main,TestMain] TEST.MP3 BINARY
14:00:11,482 DEBUG [main,MyFTPClient] Socket CRC32: 60615183
14:00:11,482 DEBUG [main,MyFTPClient] Buffer CRC32: 60615183
14:00:11,483 DEBUG [main,FTPUtils] FileOut CRC32: 60615183
这基本上毫无意义,因为下面是对应文件的MD5和:

bf89673ee7ca819961442062eaaf9c3f  ascii/test.mp3
7bd0e8514f1b9ce5ebab91b8daa52c4b  binary/test.mp3
ee172af5ed0204cf9546d176ae00a509  original/test.mp3

104e14b661f3e5dbde494a54334a6dd0  ascii/test.xml
36f482a709130b01d5cddab20a28a8e8  binary/test.xml
104e14b661f3e5dbde494a54334a6dd0  original/test.xml
我不知所措。我发誓我
bf89673ee7ca819961442062eaaf9c3f  ascii/test.mp3
7bd0e8514f1b9ce5ebab91b8daa52c4b  binary/test.mp3
ee172af5ed0204cf9546d176ae00a509  original/test.mp3

104e14b661f3e5dbde494a54334a6dd0  ascii/test.xml
36f482a709130b01d5cddab20a28a8e8  binary/test.xml
104e14b661f3e5dbde494a54334a6dd0  original/test.xml
ftp.setFileType(FTP.BINARY_FILE_TYPE);
//ftp.setFileTransferMode(org.apache.commons.net.ftp.FTP.BINARY_FILE_TYPE);
    ....
    ftpClientConnection.setFileType(FTP.BINARY_FILE_TYPE);
    ftpClientConnection.enterLocalPassiveMode();
    ftpClientConnection.setAutodetectUTF8(true);

    //Create an InputStream to the File Data and use FileOutputStream to write it
    InputStream inputStream = ftpClientConnection.retrieveFileStream(ftpFile.getName());
    FileOutputStream fileOutputStream = new FileOutputStream(directoryName + "/" + ftpFile.getName());
    //Using org.apache.commons.io.IOUtils
    IOUtils.copy(inputStream, fileOutputStream);
    fileOutputStream.flush();
    IOUtils.closeQuietly(fileOutputStream);
    IOUtils.closeQuietly(inputStream);
    boolean commandOK = ftpClientConnection.completePendingCommand();
    ....