java अपाचे कमांड-नेट FTPClient के साथ कच्चे बाइनरी हस्तांतरित करें?




binary-data apache-commons-net (3)

FTP सर्वर में प्रवेश करने के बाद

ftp.setFileType(FTP.BINARY_FILE_TYPE);

नीचे दी गई पंक्ति इसे हल नहीं करती है:

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

अद्यतन: हल

मैं लॉग इन करने से पहले FTPClient.setFileType() को बुला रहा था, जिससे FTP सर्वर को डिफ़ॉल्ट मोड ( ASCII ) का उपयोग करने के लिए कोई फर्क नहीं पड़ता जिससे कि मैंने इसे सेट किया हो दूसरी तरफ क्लाइंट, जैसे कि फ़ाइल प्रकार ठीक से सेट किया गया था, व्यवहार कर रहा था। BINARY मोड अब वांछित रूप से ठीक काम कर रहा है, सभी मामलों में बाइट-टू-बाइट फ़ाइल को ट्रांसपोर्ट करना। मुझे बस इतना करना था कि वाइपरहार्क में एक छोटी सी यातायात सूँघना हो और फिर एफ़टीपी कमांड का इस्तेमाल करके नेटकैट का इस्तेमाल करके देखे कि क्या चल रहा था। मुझे दो दिन पहले क्यों नहीं सोचा था !? आपकी मदद के लिए सभी को शुक्रिया!

मेरे पास एक्सएमएल फ़ाइल है, यूटएफ -16 एन्कोडेड है, जो मैं एपीपीई साइट से डाउनलोड कर रहा हूं एपाचे का कॉमन्स-नेट -200 जावा लाइब्रेरी का एफटीपीसीएलएन्ट। यह दो स्थानांतरण मोडों के लिए समर्थन प्रदान करता है: ASCII_FILE_TYPE और BINARY_FILE_TYPE , अंतर यह है कि ASCII लाइन सेपरेटर को उचित स्थानीय लाइन सेपरेटर ( '\r\n' या सिर्फ '\n' - हेक्स, 0x0d0a या सिर्फ 0x0a ) के साथ बदल देगा। । मेरी समस्या ये है: मेरे पास एक परीक्षण फ़ाइल है, यूटएफ -16 एन्कोडेड, जिसमें निम्न है:

<?xml version='1.0' encoding='utf-16'?>
<data>
<blah>blah</blah>
</data>

यहां हेक्स है:
0000000: 003c 003f 0078 006d 006c 0020 0076 0065 .<.?.xml .ve
0000010: 0072 0073 0069 006f 006e 003d 0027 0031 .rsion=.'.1
0000020: 002e 0030 0027 0020 0065 006e 0063 006f ...0.'. .enco
0000030: 0064 0069 006e 0067 003d 0027 0075 0074 .ding=.'.ut
0000040: 0066 002d 0031 0036 0027 003f 003e 000a .f.-.1.6.'.?.>..
0000050: 003c 0064 0061 0074 0061 003e 000a 0009 .<.data>....
0000060: 003c 0062 006c 0061 0068 003e 0062 006c .<.blah>.bl
0000070: 0061 0068 003c 002f 0062 006c 0061 0068 .ah<./.blah
0000080: 003e 000a 003c 002f 0064 0061 0074 0061 .>...<./.data
0000090: 003e 000a .>..

जब मैं इस फाइल के लिए ASCII मोड का उपयोग करता हूं तो यह सही रूप से स्थानांतरित होता है, बाइट-टू-बाइट; परिणाम में एक ही md5sum है महान। जब मैं BINARY ट्रांसफर मोड का उपयोग करता हूं, जो किसी InputStream 0x0a InputStream में कुछ भी करने के लिए बाइट नहीं करता है, नतीजा यह है कि नई लाइनें ( 0x0a ) कैरिज रिटर्न + न्यूलाइन जोड़े ( 0x0d0a ) में परिवर्तित हो जाती हैं। बाइनरी हस्तांतरण के बाद हेक्स यहां है:

0000000: 003c 003f 0078 006d 006c 0020 0076 0065 .<.?.xml .ve
0000010: 0072 0073 0069 006f 006e 003d 0027 0031 .rsion=.'.1
0000020: 002e 0030 0027 0020 0065 006e 0063 006f ...0.'. .enco
0000030: 0064 0069 006e 0067 003d 0027 0075 0074 .ding=.'.ut
0000040: 0066 002d 0031 0036 0027 003f 003e 000d .f.-.1.6.'.?.>..
0000050: 0a00 3c00 6400 6100 7400 6100 3e00 0d0a ..<.data>...
0000060: 0009 003c 0062 006c 0061 0068 003e 0062 ...<.blah>.b
0000070: 006c 0061 0068 003c 002f 0062 006c 0061 .lah<./.bla
0000080: 0068 003e 000d 0a00 3c00 2f00 6400 6100 .h.>....<./.da
0000090: 7400 6100 3e00 0d0a ta>...

न केवल यह न्यूलाइन वर्णों को परिवर्तित करता है (जो इसे नहीं करना चाहिए), लेकिन यह यूटीएफ -16 एन्कोडिंग का सम्मान नहीं करता है (यह उम्मीद नहीं करेगा कि यह जानना चाहिए कि यह होना चाहिए, यह सिर्फ एक गूंगा FTP पाइप है)। परिणाम बाइट को फिर से बदलने के लिए आगे की प्रक्रिया के बिना अपठनीय है। मैं सिर्फ ASCII मोड का उपयोग करता हूं, लेकिन मेरा आवेदन एक ही पाइप में वास्तविक बाइनरी डेटा (एमपी 3 फाइलें और जेपीजी छवियों) को भी बढ़ रहा होगा। इन 0x0d फाइलों पर BINARY स्थानांतरण मोड का उपयोग करने से उन्हें अपनी सामग्री में रैंडम 0x0d एस इंजेक्ट करने का भी कारण बनता है, जो सुरक्षित रूप से हटा नहीं सकते क्योंकि बाइनरी डेटा में अक्सर वैध 0x0d0a अनुक्रम होता है अगर मैं इन फ़ाइलों पर ASCII मोड का उपयोग करता हूं, तो "चतुर" FTP क्लाइंट इन 0x0d0a s को 0x0a में 0x0a कर 0x0a जिससे फ़ाइल असंगत हो जाती है, इससे कोई फर्क नहीं पड़ता कि मैं क्या करता हूं।

मुझे लगता है कि मेरा प्रश्न (है) है: क्या किसी को जावा के लिए किसी भी अच्छी एफ़टीपी पुस्तकालयों का पता है जो कि वहां से बाँटने वाले बाइट्स को वहां से स्थानांतरित करता है, या क्या मैं अपाचे कमांड-नेट-2.0 को हैक करने जा रहा हूं बस इस सरल अनुप्रयोग के लिए अपना स्वयं का FTP क्लाइंट कोड? क्या इस विचित्र व्यवहार से किसी और के साथ काम किया है? किसी भी सुझाव की सराहना की जाएगी।

मैंने कॉमन्स-नेट सोर्स कोड की जांच की और ऐसा नहीं दिखता कि यह BINARY मोड के लिए अजीब व्यवहार के लिए जिम्मेदार है। लेकिन InputStream जो कि BINARY मोड में से पढ़ रहा है वह सिर्फ एक java.io.BufferedInptuStream एक सॉकेट InputStream आसपास लपेटता है। क्या इन निचले स्तर के जावा धाराएं कभी किसी भी अजीब बाइट-हेरफेर करते हैं? अगर मुझे लगता है कि मुझे डर लगता है, लेकिन मुझे नहीं पता है कि यहाँ क्या हो रहा है।

1 संपादित करें:

यह कोड का एक न्यूनतम टुकड़ा है जो कि फ़ाइल को डाउनलोड करने के लिए मैं क्या कर रहा हूं। संकलन करने के लिए, बस करो

javac -classpath /path/to/commons-net-2.0.jar Main.java

चलाने के लिए, आपको डाउनलोड करने के लिए फाइल के लिए / tmp / ascii और / tmp / binary निर्देशिका की आवश्यकता होगी, साथ ही उसमें बैठे फ़ाइल के साथ एक FTP साइट सेट अप की जाएगी कोड को उचित FTP मेजबान, उपयोगकर्ता नाम और पासवर्ड के साथ कॉन्फ़िगर करने की आवश्यकता होगी। मैंने फाइल को परीक्षण / फ़ोल्डर के तहत अपने परीक्षण FTP साइट पर डाल दिया और फ़ाइल को test.xml कहा। परीक्षण फ़ाइल में कम से कम एक पंक्ति होनी चाहिए, और यूटीएफ -16 एन्कोडेड होनी चाहिए (यह आवश्यक नहीं हो सकता है, लेकिन मेरी सटीक स्थिति को पुनः बनाने में मदद करेगा)। मैंने vim का उपयोग किया है :set fileencoding=utf-16 एक नई फ़ाइल खोलने के बाद और ऊपर संदर्भित xml पाठ में दर्ज किया गया। अंत में, चलाने के लिए, बस करो

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

कोड:

(नोट: "एडिट 2" के नीचे लिंक किए गए कस्टम एफ़टीपी क्लायंट ऑब्जेक्ट का उपयोग करने के लिए संशोधित इस कोड)

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 सलाह का पालन किया और यहां मेरे परिणाम हैं CheckedXputStream मैंने FTPClient की FTPClient की एक कॉपी FTPClient बुलाया, और मैंने दोनों SocketInputStream और BufferedInputStream को CRC32 चेकसमों का उपयोग कर SocketInputStream लपेट लिया। इसके अलावा, मैंने FTPClient को लपेट लिया है कि मैं CRC32 चेकसम के साथ FTPClient में आउटपुट को स्टोर करने के लिए FTPClient को देता हूं। MyFTPClient के लिए कोड यहां पोस्ट किया गया है और मैंने उपरोक्त परीक्षण कोड को एफटीपीसीएलएन्ट का उपयोग करने के लिए संशोधित किया है (संशोधित कोड में एक सार यूआरएल पोस्ट करने की कोशिश की है, लेकिन मुझे एक से अधिक यूआरएल पोस्ट करने के लिए 10 प्रतिष्ठा अंक की आवश्यकता है!), 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

यह मूलभूत रूप से शून्य समझ में आता है, क्योंकि यहां फाइलिंग के एमडी 5 एस्यूम हैं:

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 सर्वर से बात कर रहे हैं जो टूटा हुआ है या गलत तरीके से कॉन्फ़िगर किया गया है।
  • आप किसी तरह अपाचे एफ़टीपी ग्राहक JAR फ़ाइल की एक बेतरतीब (हैक की गई) प्रतिलिपि को पकड़ने में कामयाब रहे हैं। (हाँ, हाँ, बहुत संभावना नहीं है ...)

मुझे पता चला कि अपाचे पुनर्प्राप्त फ़ाइल (...) कभी-कभी फ़ाइल सीमाओं के साथ एक निश्चित सीमा से अधिक काम नहीं करती थी इस पर काबू पाने के लिए कि मैं इसके बजाय फ़्लीस्ट्रीम () को पुनः प्राप्त करेगा। डाउनलोड करने से पहले मैंने सही फ़ाइल प्रकार सेट किया है और मोड को निष्क्रिय मॉडेम पर सेट किया है

तो कोड ऐसा दिखेगा

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