2010-06-30 3 views
24

업데이트 : 상관없이 무엇을 내가가 설정 한 기본 모드 (ASCII)를 사용하지 FTP 서버의 원인, 내가 로그인 전에 FTPClient.setFileType()를 호출했다아파치 commons-net FTPClient로 원시 바이너리를 전송 하시겠습니까?

해결. 반면에 클라이언트는 파일 유형이 제대로 설정되어있는 것처럼 작동합니다. BINARY 모드는 원하는대로 정확하게 작동하며 모든 경우에 바이트 단위로 파일을 전송합니다. 내가해야 할 일은 wireshark에서 약간의 트래픽을 감지 한 다음 netcat을 사용하여 FTP 명령을 모방하여 무슨 일이 일어나고 있는지를 파악하는 것이 었습니다. 내가 왜 이틀 전 그렇게 생각하지 않았어!? 감사합니다, 모두 도와주세요!

xml 파일 (utf-16로 인코딩 됨)은 apache의 commons-net-2.0 Java 라이브러리의 FTPClient를 사용하여 FTP 사이트에서 다운로드됩니다. ASCII_FILE_TYPEBINARY_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 복사본을 만들었고, 체크섬을 사용하여 CheckedInputStreamSocketInputStreamBufferedInputStream을 모두 감쌌다. 또한, 나는 FileOutputStreamFTPClient에게 보내어 체크섬을 사용하여 CheckOutputStream에 출력을 저장했습니다. MyFTPClient의 코드는 here으로 게시되었고 위의 테스트 코드를 수정하여 FTPClient의이 버전을 사용했습니다 (수정 된 코드에 요점 URL을 게시하려했지만 하나 이상의 URL을 게시하려면 10 개의 평판 포인트가 필요합니다!). test.xmltest.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 전송을하기 위해 쉘에게 전화를 걸어서 진행할 것이지만, 나는 지옥이 무엇이 일어나고 있는지 이해할 때까지 이것을 추구 할 생각이다. 나는이 연구 결과를 나의 연구 결과로 갱신 할 것이고, 나는 누군가가 가지고 있을지 모르는 어떤 공헌에도 감사 할 것이다. 바라기를 이것은 어떤 점에서 누군가에게 유용 할 것입니다!

+2

와우, 이상한 모양을 PassiveMode하는 모드를 설정하기 전에. 나는'BufferedInputStream'과'SocketInputStream' (적어도 Java 부분)의 소스 코드를 검사 했었습니다. 그런 바이트를 바꿀 수있는 어떤 것도 보지 못했습니다. 나는'FTPClient'의 복사본을 만들고 입력 스트림 계층을'CheckedInputStream (BufferedInputStream (CheckedInputStream (SocketInputStream()))')으로 변경하고 체크섬을 사용하여 바이트가 변경되는 위치를 식별 할 수 있는지 확인하는 것이 좋습니다. 그 질문에 유용한 정보가 될 것입니다. (심지어 더 나은, 귀하의 테스트 코드를 온라인으로하고 그것에 연결) –

+1

또한, 잘 쓰여진 질문 +1 ;-) –

+0

나는 이것을 시도합니다; 고맙습니다. 나는 CheckedInputStream에 대해 들어 본 적이 없었다. 매우 시원한! – cgs1019

답변

4

응용 프로그램 코드에서 ASCII 및 BINARY 모드를 반전시킨 것처럼 들리는 것 같습니다. ASCII는 변경되지 않고 통과하며, 행간 문자 변환을 수행하는 BINARY는 과 정확하게 반대가됩니다. FTP가 작동하는 방법은입니다.

그래도 문제가 해결되지 않으면 질문을 편집하여 코드의 관련 부분을 추가하십시오.

편집

다른 가능한 (그러나 IMO 가능성) 설명의 몇 :

  • FTP 서버가 잘못/나뉩니다. (비 Java 명령 행 FTP 유틸리티를 사용하여 ASCII/BINARY 모드로 파일을 성공적으로 다운로드 할 수 있습니까?)
  • 프록시가 잘못되었거나 잘못 구성되어 FTP 서버와 통신 중입니다.
  • 당신은 어쨌든 아파치 FTP 클라이언트 JAR 파일의 dodgy (해킹 된) 사본을 확보 할 수있었습니다. (매우 가능성, 참, 참 ...)
+0

그런 식으로 보일 수도 있지만 적어도 5 번 코드를 실행하고 최대한 많은 변수를 제거했습니다. 내가 확인한 코드를 포함하도록 내 게시물을 편집하여 문제를 재현합니다. 불행히도 파일을 다운로드 할 ftp 사이트를 제공 할 수는 없으므로 잘만하면 로컬 호스트에서 테스트하고 있습니다. 답장을 보내 주셔서 감사 드리며, 공유 할 생각이 있으시면 고맙겠습니다. – cgs1019

+0

코드가 모두 정확하다면 언급 한 첫 번째 사례를 가장 가능성있는 설명으로 생각했습니다. 우분투에서 proftp를 설치하는 것은 꽤 기본입니다. 방금 표준 ftp 명령 줄 클라이언트로 다운로드를 시도했는데 xml 파일은 클라이언트가 FTP 클라이언트와 함께 XML을 올바르게 전송 한 ascii 모드를 사용하고 있기 때문에 정상적으로 작동합니다. 또한 FTP 클라이언트가 cmd 라인 클라이언트 (가능성)와 다른 설정으로 연결하지 않는 한 mp3 파일을 올바르게 (동일한 md5sum) 전송하므로 서버처럼 보이지 않습니다. – cgs1019

+1

또한, 당신의 도움을 위해 당신을 upvote 줄 알았지 만 아직 15의 rep 포인트가 없습니다! :) – cgs1019

25

FTP 서버

ftp.setFileType(FTP.BINARY_FILE_TYPE); 

에 로그인 한 후 라인 아래 그것을 해결하지 않습니다

//ftp.setFileTransferMode(org.apache.commons.net.ftp.FTP.BINARY_FILE_TYPE); 
+0

감사합니다, 이것은 나를 위해 그것을했다. 이상한 텍스트 모드는 기본값입니다. – Davor

2

나는 것을 발견 아파치 retrieveFile (...)이 특정 크기를 초과하는 파일 크기에서 작동하지 않는 경우가 있습니다. 이를 극복하기 위해 retrieveFileStream()을 대신 사용했습니다. 내가 올바른에 FileType 설정 다운로드

그래서 코드가

.... 
    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(); 
    ....