업데이트 : 상관없이 무엇을 내가가 설정 한 기본 모드 (ASCII
)를 사용하지 FTP 서버의 원인, 내가 로그인 전에 FTPClient.setFileType()
를 호출했다아파치 commons-net FTPClient로 원시 바이너리를 전송 하시겠습니까?
해결. 반면에 클라이언트는 파일 유형이 제대로 설정되어있는 것처럼 작동합니다. BINARY
모드는 원하는대로 정확하게 작동하며 모든 경우에 바이트 단위로 파일을 전송합니다. 내가해야 할 일은 wireshark에서 약간의 트래픽을 감지 한 다음 netcat을 사용하여 FTP 명령을 모방하여 무슨 일이 일어나고 있는지를 파악하는 것이 었습니다. 내가 왜 이틀 전 그렇게 생각하지 않았어!? 감사합니다, 모두 도와주세요!
xml 파일 (utf-16로 인코딩 됨)은 apache의 commons-net-2.0 Java 라이브러리의 FTPClient를 사용하여 FTP 사이트에서 다운로드됩니다. ASCII_FILE_TYPE
과 BINARY_FILE_TYPE
의 두 가지 전송 모드에 대한 지원을 제공합니다. 즉, ASCII
은 행 구분 기호를 적절한 로컬 행 구분 기호 ('\r\n'
또는 단지 '\n'
- 16 진수, 0x0d0a
또는 단지 0x0a
)로 바꿉니다.내 문제는 이것이다 :
0000000: 003c 003f 0078 006d 006c 0020 0076 0065 .<.?.x.m.l. .v.e
:
<?xml version='1.0' encoding='utf-16'?>
<data>
<blah>blah</blah>
다음 </data>
은 진수입니다 : 내가 테스트 파일이 UTF-16으로 인코딩, 그 다음이 포함되어
0000010: 0072 0073 0069 006f 006e 003d 0027 0031 .r.s.i.o.n.=.'.1
0000020: 002e 0030 0027 0020 0065 006e 0063 006f ...0.'. .e.n.c.o
0000030: 0064 0069 006e 0067 003d 0027 0075 0074 .d.i.n.g.=.'.u.t
0000040: 0066 002d 0031 0036 0027 003f 003e 000a .f.-.1.6.'.?.>..
0000050: 003c 0064 0061 0074 0061 003e 000a 0009 .<.d.a.t.a.>....
0000060: 003c 0062 006c 0061 0068 003e 0062 006c .<.b.l.a.h.>.b.l
0000070: 0061 0068 003c 002f 0062 006c 0061 0068 .a.h.<./.b.l.a.h
0000080: 003e 000a 003c 002f 0064 0061 0074 0061 .>...<./.d.a.t.a
0000090: 003e 000a
0,123,781 9백5경5천3백19조4천3백13억6천5백58만3천2백10 01,237, .>..
나는이 파일에 대한 ASCII
모드를 사용이 올바르게 바이트 단위를 전송하는 경우; 결과에는 동일한 md5sum이 있습니다. 큰. BINARY
전송 모드를 사용하면 아무것도 수행하지 않고 InputStream
에서 OutputStream
으로 바이트를 셔플하면 줄 바꿈 (0x0a
)이 캐리지 리턴 + 개행 쌍 (0x0d0a
)으로 변환됩니다.
0000000: 003c 003f 0078 006d 006c 0020 0076 0065 .<.?.x.m.l. .v.e
0000010: 0072 0073 0069 006f 006e 003d 0027 0031 .r.s.i.o.n.=.'.1
0000020: 002e 0030 0027 0020 0065 006e 0063 006f ...0.'. .e.n.c.o
0000030: 0064 0069 006e 0067 003d 0027 0075 0074 .d.i.n.g.=.'.u.t
0000040: 0066 002d 0031 0036 0027 003f 003e 000d .f.-.1.6.'.?.>..
0000050: 0a00 3c00 6400 6100 7400 6100 3e00 0d0a ..<.d.a.t.a.>...
0000060: 0009 003c 0062 006c 0061 0068 003e 0062 ...<.b.l.a.h.>.b
0000070: 006c 0061 0068 003c 002f 0062 006c 0061 .l.a.h.<./.b.l.a
0000080: 0068 003e 000d 0a00 3c00 2f00 6400 6100 .h.>....<./.d.a.
다음은 바이너리 전송 후 진수입니다 53,691,363,210 0000090: 7400 6100 3e00 0d0a
,319,431 365,583,210 t.a.>...
뿐만 아니라 그것은 개행 문자를 변환 않습니다 (이 그것을 안)하지만 나는 그것이 것을 알고 기대하는 것을하지합니다 (UTF-16 인코딩을 존중하지 않는다 , 그것은 단지 바보 FTP 파이프). 바이트를 재정렬하기위한 추가 처리 없이는 결과를 읽을 수 없습니다. ASCII
모드를 사용 하겠지만, 내 응용 프로그램은 바이너리 데이터 (mp3 파일 및 jpeg 이미지)를 동일한 파이프에서 이동할 수도 있습니다. 이 이진 파일에 BINARY
전송 모드를 사용하면 해당 내용에 임의의 0x0d
을 삽입하게됩니다. 이진 데이터에는 종종 합법적 인 0x0d0a
시퀀스가 포함되어 있기 때문에 안전하게 제거 할 수 없습니다. 이 파일들에서 ASCII
모드를 사용하면 ""을 "똑똑한"FTPClient가 0x0a
으로 변환하여 내가하는 일과 상관없이 파일을 일관되게 유지합니다.
내 질문에 (들) 같아요 : 누군가가 여기에 저주 바이트를 이동 자바에 대한 좋은 FTP 라이브러리를 알고, 아니면 내가 아파치 커먼즈 - 해킹해야 할거야 -2.0 및이 간단한 응용 프로그램에 대한 내 자신의 FTP 클라이언트 코드를 유지? 다른 누구도이 기괴한 행동을 다루었습니까? 모든 제안을 부탁드립니다.
BINARY
모드를 사용하면 commons-net 소스 코드가 이상한 동작을 일으키는 것처럼 보입니다. 하지만 BINARY
모드에서 읽는 InputStream
은 소켓에 포장 된 java.io.BufferedInptuStream
입니다. 이러한 낮은 레벨의 자바 스트림이 이상한 바이트 조작을 수행합니까? 내가 그랬다면 나는 충격을받을 것이지만, 그 밖의 다른 일은 여기서 볼 수 없다.
편집 1 :
는 여기에 내가 파일을 다운로드 뭘하는지 모방 그 코드의 최소한의 조각입니다.파일이 파일이 앉아로 설정 FTP 사이트뿐만 아니라,에 다운로드 할 바로 실행하려면
javac -classpath /path/to/commons-net-2.0.jar Main.java
수행 컴파일하려면, 당신은 및/tmp에/진 디렉토리/tmp를/아스키가 필요합니다 . 코드는 적절한 ftp 호스트, 사용자 이름 및 암호로 구성해야합니다. 테스트/폴더 아래에있는 테스트 용 ftp 사이트에 파일을 저장하고 test.xml 파일을 호출했습니다. 테스트 파일은 적어도 둘 이상의 라인을 가져야하고, utf-16으로 인코딩되어야합니다 (이것은 필요하지 않을 수도 있지만 제 정확한 상황을 재현하는 데 도움이됩니다). 새 파일을 연 다음 vim의 :set fileencoding=utf-16
명령을 사용하고 위에 참조 된 xml 텍스트를 입력했습니다. 마지막으로, 단지 어떻게 실행을
java -cp .:/path/to/commons-net-2.0.jar Main
코드 :
(참고 : "편집 2"의 아래에 링크 된 사용자 정의 FTPClient 객체를 사용하도록 수정 코드)
import java.io.*;
import java.util.zip.CheckedInputStream;
import java.util.zip.CheckedOutputStream;
import java.util.zip.CRC32;
import org.apache.commons.net.ftp.*;
public class Main implements java.io.Serializable
{
public static void main(String[] args) throws Exception
{
Main main = new Main();
main.doTest();
}
private void doTest() throws Exception
{
String host = "ftp.host.com";
String user = "user";
String pass = "pass";
String asciiDest = "/tmp/ascii";
String binaryDest = "/tmp/binary";
String remotePath = "test/";
String remoteFilename = "test.xml";
System.out.println("TEST.XML ASCII");
MyFTPClient client = createFTPClient(host, user, pass, org.apache.commons.net.ftp.FTP.ASCII_FILE_TYPE);
File path = new File("/tmp/ascii");
downloadFTPFileToPath(client, "test/", "test.xml", path);
System.out.println("");
System.out.println("TEST.XML BINARY");
client = createFTPClient(host, user, pass, org.apache.commons.net.ftp.FTP.BINARY_FILE_TYPE);
path = new File("/tmp/binary");
downloadFTPFileToPath(client, "test/", "test.xml", path);
System.out.println("");
System.out.println("TEST.MP3 ASCII");
client = createFTPClient(host, user, pass, org.apache.commons.net.ftp.FTP.ASCII_FILE_TYPE);
path = new File("/tmp/ascii");
downloadFTPFileToPath(client, "test/", "test.mp3", path);
System.out.println("");
System.out.println("TEST.MP3 BINARY");
client = createFTPClient(host, user, pass, org.apache.commons.net.ftp.FTP.BINARY_FILE_TYPE);
path = new File("/tmp/binary");
downloadFTPFileToPath(client, "test/", "test.mp3", path);
}
public static File downloadFTPFileToPath(MyFTPClient ftp, String remoteFileLocation, String remoteFileName, File path)
throws Exception
{
// path to remote resource
String remoteFilePath = remoteFileLocation + "/" + remoteFileName;
// create local result file object
File resultFile = new File(path, remoteFileName);
// local file output stream
CheckedOutputStream fout = new CheckedOutputStream(new FileOutputStream(resultFile), new CRC32());
// try to read data from remote server
if (ftp.retrieveFile(remoteFilePath, fout)) {
System.out.println("FileOut: " + fout.getChecksum().getValue());
return resultFile;
} else {
throw new Exception("Failed to download file completely: " + remoteFilePath);
}
}
public static MyFTPClient createFTPClient(String url, String user, String pass, int type)
throws Exception
{
MyFTPClient ftp = new MyFTPClient();
ftp.connect(url);
if (!ftp.setFileType(type)) {
throw new Exception("Failed to set ftpClient object to BINARY_FILE_TYPE");
}
// check for successful connection
int reply = ftp.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply)) {
ftp.disconnect();
throw new Exception("Failed to connect properly to FTP");
}
// attempt login
if (!ftp.login(user, pass)) {
String msg = "Failed to login to FTP";
ftp.disconnect();
throw new Exception(msg);
}
// success! return connected MyFTPClient.
return ftp;
}
}
편집 2 :
나는 CheckedXputStream
조언을 따르며 여기에 내 결과가 있습니다. 나는 MyFTPClient
라고 불리는 아파치의 FTPClient
복사본을 만들었고, 체크섬을 사용하여 CheckedInputStream
에 SocketInputStream
과 BufferedInputStream
을 모두 감쌌다. 또한, 나는 FileOutputStream
을 FTPClient
에게 보내어 체크섬을 사용하여 CheckOutputStream
에 출력을 저장했습니다. MyFTPClient의 코드는 here으로 게시되었고 위의 테스트 코드를 수정하여 FTPClient의이 버전을 사용했습니다 (수정 된 코드에 요점 URL을 게시하려했지만 하나 이상의 URL을 게시하려면 10 개의 평판 포인트가 필요합니다!). test.xml
및 test.mp3
결과는 이렇게했다 : 나는 손실에있어
bf89673ee7ca819961442062eaaf9c3f ascii/test.mp3
7bd0e8514f1b9ce5ebab91b8daa52c4b binary/test.mp3
ee172af5ed0204cf9546d176ae00a509 original/test.mp3
104e14b661f3e5dbde494a54334a6dd0 ascii/test.xml
36f482a709130b01d5cddab20a28a8e8 binary/test.xml
104e14b661f3e5dbde494a54334a6dd0 original/test.xml
:이 수
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
, 기본적으로 제로 의미가 무엇이든지 여기에 있기 때문에 corresponsing 파일의 md5sums를이다. I 맹세이 과정에서 파일 이름/경로를 임의로 바꾸지 않았으며 모든 단계를 세 번 확인했습니다. 그것은 단순한 것이어야하지만, 나는 다음에 어디를보아야하는지 안 좋은 생각이 없습니다. 실용성의 관점에서 나는 FTP 전송을하기 위해 쉘에게 전화를 걸어서 진행할 것이지만, 나는 지옥이 무엇이 일어나고 있는지 이해할 때까지 이것을 추구 할 생각이다. 나는이 연구 결과를 나의 연구 결과로 갱신 할 것이고, 나는 누군가가 가지고 있을지 모르는 어떤 공헌에도 감사 할 것이다. 바라기를 이것은 어떤 점에서 누군가에게 유용 할 것입니다!
와우, 이상한 모양을 PassiveMode하는 모드를 설정하기 전에. 나는'BufferedInputStream'과'SocketInputStream' (적어도 Java 부분)의 소스 코드를 검사 했었습니다. 그런 바이트를 바꿀 수있는 어떤 것도 보지 못했습니다. 나는'FTPClient'의 복사본을 만들고 입력 스트림 계층을'CheckedInputStream (BufferedInputStream (CheckedInputStream (SocketInputStream()))')으로 변경하고 체크섬을 사용하여 바이트가 변경되는 위치를 식별 할 수 있는지 확인하는 것이 좋습니다. 그 질문에 유용한 정보가 될 것입니다. (심지어 더 나은, 귀하의 테스트 코드를 온라인으로하고 그것에 연결) –
또한, 잘 쓰여진 질문 +1 ;-) –
나는 이것을 시도합니다; 고맙습니다. 나는 CheckedInputStream에 대해 들어 본 적이 없었다. 매우 시원한! – cgs1019