1

我目前在我的代码中使用 com.jcraft.jsch 库,以便我可以将一个文件(或多个文件)从我的本地机器上传到某个远程机器。对于 5KB、100KB、200KB 的文件大小,我没有任何顾虑。但是,当我倾向于上传文件大小为 500MB、1GB、2GB 及以上的文件时,我有一个很大的担忧,因为互联网连接总是有可能在任一端(本地计算机或远程)失败我做了一点我自己的研究发现图书馆有一个叫RESUME的字段,它指的是“文件传输模式”,但我没有找到关于它正确使用的解释。

所以我的问题是:如果连接失败,有没有办法在修复后,文件传输从中断点继续?

4

1 回答 1

0

我只是在我的应用程序中“解决”了这个问题,并认为我会分享我学到的东西。

我研究了 RESUME 是如何工作的,发现它取决于您使用的方法。对我来说,我同时使用了 PipedInputStream 和 PipedOutputStream,因为我可能正在向本地文件传输/从本地文件传输,甚至从远程服务器传输到/从远程服务器传输。

我发现对我来说,我将我的 PipedOutputStream 提供给 get 方法,但没有提供模式(默认为 OVERWRITE),然后将我的 PipedInputStream 提供给 put 方法,参数为 RESUME。put 方法使我的 InputStream 的字节数等于我发送到的文件的当前大小。

这需要一段时间,因为我已经在推进 PipedOutputStream X 个字节数,然后 PipedInputStream 正在推进另一个 X 个字节,我得到了很大的差距。我通过查看ChannelSftp 源代码发现了这一点

当然,如果您不做与我完全相同的事情,情况会有所不同,但如果您的来源或目的地是本地的,您可能不需要担心这一点。如果你不知道你是怎么做的,我会尝试查看源代码。

我正在使用 Grails,所以这可能不适合你,但这就是我所做的

/*    
 * This was initially copied from
 * <a href="http://www.intelligrape.com/blog/2013/04/04/using-ftp-with-grails/">
 * http://www.intelligrape.com/blog/2013/04/04/using-ftp-with-grails/</a> for
 * the basic structure. JavaDoc and additional method were added as needed.
 *
 * @author Puneet Behl
 * @author jonathan.tinsman
 */
class FtpService {

    /**
     * Gets the file from the server and loads it into the provided output stream
     *
     * @param outputStream
     *      - the output stream to have the file loaded to
     * @param fileName
     *      - the desired file
     * @param ftpCredential
     *      -the server credentials
     */
    def load(OutputStream outputStream, String fileName, FtpCredential ftpCredential) {
        connect(ftpCredential) { ChannelSftp sftp ->
            sftp.get fileName, outputStream, new FtpMonitor()
        }
    }

    /**
     * Writes the file on the server
     *
     * @param inputStream
     *      - the input stream for writing
     * @param fileName
     *      - the file name
     * @param mode
     *      - the mode for the transfer (defaults to {@link ChannelSftp#OVERWRITE}
     * @param ftpCredential
     *      - the server credentials
     */
    def save(InputStream inputStream, String fileName, Integer mode = ChannelSftp.OVERWRITE, FtpCredential ftpCredential) {
        connect(ftpCredential) { ChannelSftp sftp ->
            sftp.put inputStream, fileName, mode
        }
    }

    /**
     * Transfers the file from the input server to the output server.
     * <p>
     * The usage of {@link PipedInputStream} and {@link PipedOutputStream} is
     * from <a href="http://ostermiller.org/convert_java_outputstream_inputstream.html">OsterMiller.org</a>
     *
     * @param fileName
     *      - the file name
     * @param inputFtpCredential
     *      - the input server
     * @param outputFtpCredential
     *      - the output server
     * @param mode
     *      - the mode for the transfer (defaults to {@link ChannelSftp#OVERWRITE}
     */
    def transfer(String fileName, FtpCredential inputFtpCredential, FtpCredential outputFtpCredential, Integer mode = ChannelSftp.OVERWRITE) {

        // To change the size of the buffer, add an int with the desired pipe
        // size. The default is 1024
        PipedInputStream input = new PipedInputStream();
        PipedOutputStream output = new PipedOutputStream(input);

        // Starting in different threads so they do not deadlock each other
        new Thread(
                new Runnable(){
                    public void run(){
                        new FtpService().load output, fileName, inputFtpCredential
                    }
                }
                ).start();
        /* 
         * only passing the mode to the "save" as the save will progress the 
         * input stream on it's own. 
         * 
         * If we pass the mode to the "load" method, then there will be a gap
         * in the data as the "load" will progress the stream xx bytes and the
         * "save" will progress it another xx bytes (the size of the existing
         * file).
         */
        save input, fileName, mode, outputFtpCredential
    }

    /**
     * Connect to the server and call the provided ChannelSftp Closure.
     *
     * @param ftpCredential
     *      - the server to connect to
     * @param closure
     *      - the closure to call
     * @param disconnectOnFinish
     *      - to disconnect the Session when the Closure is done (defaults to true)
     */
    private def connect(FtpCredential ftpCredential, Closure closure, boolean disconnectOnFinish = true) {
        Session session = null
        ChannelSftp sftp = null
        try {
            JSch jSch = new JSch()
            session = jSch.getSession ftpCredential?.username, ftpCredential?.server, ftpCredential?.port
            session.setConfig "StrictHostKeyChecking", "no"
            if (ftpCredential?.password) {
                session.password = ftpCredential?.password
            } else {
                File keyFile = new File("${grailsApplication.config.pathToKeyFile}")
                jSch.addIdentity(keyFile?.absolutePath)
            }
            session.connect()
            Channel sFtpChannel = session.openChannel "sftp"
            sFtpChannel.connect()
            sftp = sFtpChannel as ChannelSftp
            sftp.cd ftpCredential?.remoteBaseDir
            closure.call sftp
        } catch (Exception ex) {
            ex.printStackTrace()
        } finally {
            if (disconnectOnFinish) {
                sftp?.exit()
                session?.disconnect()
            }
        }
    }
}
于 2013-06-18T20:37:01.530 回答