zoukankan      html  css  js  c++  java
  • FTP文件上传 支持断点续传 并 打印下载进度(二) —— 单线程实现

    这个就看代码,哈哈哈哈哈  需要用到的jar包是:

        <dependency>
                <groupId>commons-net</groupId>
                <artifactId>commons-net</artifactId>
                <version>3.3</version>
            </dependency>

    一:定义我们可能会返回的状态值。两个枚举类 一个异常类

    public enum  UploadStatus {
        Create_Directory_Fail,      //远程服务器相应目录创建失败
        Create_Directory_Success,   //远程服务器闯将目录成功
        Upload_New_File_Success,    //上传新文件成功
        Upload_New_File_Failed,     //上传新文件失败
        File_Exits,                 //文件已经存在
        Remote_Bigger_Local,        //远程文件大于本地文件
        Upload_From_Break_Success,  //断点续传成功
        Upload_From_Break_Failed,   //断点续传失败
        Delete_Remote_Faild;        //删除远程文件失败
    }
    public enum  DownloadStatus {
        Remote_File_Noexist, //远程文件不存在
        Local_Bigger_Remote, //本地文件大于远程文件
        Download_From_Break_Success, //断点下载文件成功
        Download_From_Break_Failed, //断点下载文件失败
        Download_New_Success, //全新下载文件成功
        Download_New_Failed; //全新下载文件失败
    }

    //用于记录创建时候的异常

    public class CreateException extends Exception{
        private static Logger log = LoggerFactory.getLogger(CreateException.class);
        private static final long serialVersionUID = 1L;
    
        private Integer errCode;
        private String errMessage;
    
        public CreateException(Throwable cause, Integer errCode, String errMessage) {
            super(cause);
            this.errCode = errCode;
            this.errMessage = errMessage;
        }
    
        public CreateException(Integer errCode, String errMessage) {
            this.errCode = errCode;
            this.errMessage = errMessage;
        }
        public CreateException(Integer errCode, UploadStatus uploadStatus) {
            this.errCode = errCode;
            this.errMessage = uploadStatus.toString();
        }
    
    
        public Integer getErrCode() {
            return errCode;
        }
    
        public String getErrMessage() {
            return errMessage;
        }
    }

    二:创建连接ftp服务器的类

    package com.utils.study.ftpCenter;
    
    import com.coocaa.core.generation.service.CreateBeanService;
    import org.apache.commons.net.PrintCommandListener;
    import org.apache.commons.net.ftp.FTP;
    import org.apache.commons.net.ftp.FTPClient;
    import org.apache.commons.net.ftp.FTPReply;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    
    import java.io.IOException;
    import java.io.PrintWriter;
    
    /**
     * Created by yugaofeng on 2017/9/5.
     */
    public class ContinueFTP {
        private static Logger logger = LoggerFactory.getLogger(CreateBeanService.class);
    
    
        //定义一个客户端
        private static FTPClient ftpClient ;
    
        //单例模式
        public  FTPClient getFtpClient(){
            if(ftpClient == null){
                ftpClient = new FTPClient();
            }
            return ftpClient;
        }
    
        public ContinueFTP(){
            getFtpClient().addProtocolCommandListener(new PrintCommandListener(
                    new PrintWriter(System.out)));
        }
    
    
        /**
         * 连接到FTP服务器
         * @param hostname  主机名
         * @param port 端口
         * @param username 用户名
         * @param password 密码
         * @return 是否连接成功
         * @throws IOException
         */
        public boolean connect(String hostname, int port, String username, String password) throws IOException {
            getFtpClient().connect(hostname, port);
            if (FTPReply.isPositiveCompletion(getFtpClient().getReplyCode())) {
                if (getFtpClient().login(username, password)) {
                    getFtpClient().enterLocalPassiveMode();
                    getFtpClient().setFileType(FTP.BINARY_FILE_TYPE);
                    return true;
                }
            }
            disconnect();
            return false;
        }
    
        /**
         * 断开与服务器的连接
         * @throws IOException
         */
        public void disconnect() throws IOException {
            if (getFtpClient().isConnected()) {
                getFtpClient().disconnect();
                System.out.println("ftp is disconnect!");
            }
        }
    }

     这个时候 先测试一下 你能不能连接到服务器

        public static void main(String[] args) throws IOException {
            ContinueFTP ftp = new ContinueFTP();
    
            System.out.println("<<<<<<<<<<<<<<<<<1"+ftp.getFtpClient().isConnected());
            ftp.connect("172.20.139.217", 21, "ftp01", "ftp111");
            System.out.println("<<<<<<<<<<<<<<<<<3"+ftp.getFtpClient().isConnected());
            ftp.getFtpClient().disconnect();
        }

    表示连接成功

    三:实现文件上传 和断点续传

    由于设置了观察者 在观察当前上传的进度变化 ,本次代码中 没有添加观察者模式的代码,所以这个地方 可能需要先注释掉观察者

    package com.utils.study.ftpCenter;
    
    import com.utils.study.CreateException;
    import com.utils.study.enums.UploadStatus;
    import com.utils.study.observerModel.FileObserverAble;
    import com.utils.study.observerModel.FilePercentObserver;
    import org.apache.commons.net.ftp.FTPClient;
    import org.apache.commons.net.ftp.FTPFile;
    
    import java.io.*;
    
    /**
     * Created by yugaofeng on 2017/9/5.
     */
    public class FileOperateByFtp {
    
        private FTPClient ftpClient;
    
        FileObserverAble fileObserverAble;
    
        public FileOperateByFtp(FTPClient ftpClient) {
            //添加观察者对象
            fileObserverAble = new FileObserverAble();
            FilePercentObserver filePercentObserver = new FilePercentObserver(fileObserverAble);
            this.ftpClient = ftpClient;
        }
    
        /**
         * 上传文件到FTP服务器,支持断点续传 并返回上传文件进度
         * @param local 本地文件名称,绝对路径
         * @param remote 远程文件路径,使用/home/directory1/subdirectory/file.ext
         *               按照Linux上的路径指定方式,支持多级目录嵌套,支持递归创建不存在的目录结构
         * @return 上传结果
         * @throws IOException
         */
        public UploadStatus upload(String local, String remote) throws Exception{
            try {
                if (!ftpClient.isConnected()) {
                    throw new CreateException(-1, "远程服务器相应目录创建失败");
                }
                UploadStatus result;
                // 对远程目录的处理  并返回文件的名称
                String remoteFileName = createDirectory(remote, ftpClient);
                // 检查远程是否存在文件
                FTPFile[] files = ftpClient.listFiles(remoteFileName);
                File localFile = new File(local);
                if(localFile.length() <=0){
                    throw new CreateException(-1,"本地文件不存在");
                }
                if (files.length == 1) {
                    //判断文件是否存在
                    long remoteSize = files[0].getSize();
                    long localSize = localFile.length();
                    if(remoteSize==localSize){
                        return UploadStatus.File_Exits;
                    }else if(remoteSize > localSize){
                        return UploadStatus.Remote_Bigger_Local;
                    }
                    result = this.writeByUnit(remoteFileName,localFile,ftpClient,remoteSize,localFile.length());
                } else {
                    result = this.writeByUnit(remoteFileName,localFile,ftpClient,0,localFile.length());
                }
                return result;
            }catch (CreateException e){
                throw e;
            }finally {
                //上传完成之后 切回到根目录
                ftpClient.changeWorkingDirectory("/");
            }
        }
    
        /**
         * 判断目录
         * @param remoteFilePath 远程服务器上面的 文件目录
         * @param ftpClient ftp客户端
         * @return
         * @throws Exception
         */
        private String createDirectory(String remoteFilePath,FTPClient ftpClient) throws Exception {
            if(ftpClient == null){
                throw new CreateException(-1,"FTP客户端为空,请先连接到客户端");
            }
            String fileName = remoteFilePath;
            if(remoteFilePath.contains("/")){
                fileName = remoteFilePath.substring(remoteFilePath.lastIndexOf("/") + 1);
                String directory = remoteFilePath.substring(0, remoteFilePath.lastIndexOf("/") + 1);
                if(directory.startsWith("/")){
                    directory = directory.substring(1);
                }
                while (true){
                    if(!directory.contains("/")){
                        break;
                    }
                    String subDirectory = directory.substring(0, directory.indexOf("/"));
                    directory = directory.substring(directory.indexOf("/")+1);
                    if (!ftpClient.changeWorkingDirectory(subDirectory)) {
                        if (ftpClient.makeDirectory(subDirectory)) {
                            ftpClient.changeWorkingDirectory(subDirectory);
                        } else {
                            throw new CreateException(-1,"创建目录失败");
                        }
                    }
                }
            }
            return fileName;
        }
    
    
        /**
         * 上传文件到服务器,新上传和断点续传
         * @param remoteFile 远程文件名,在上传之前已经将服务器工作目录做了改变
         * @param localFile 本地文件File句柄,绝对路径
         * @param ftpClient FTPClient引用 beginSize是指文件长传开始指针位置  endSize是结束的位置 为多线程上传下载提供接口 不过该方法还需要修改
         * @return 
         * @throws IOException
         */
    
        private  UploadStatus writeByUnit(String remoteFile,File localFile,FTPClient ftpClient,long beginSize,long endSize) throws Exception {
            long localSize = localFile.length();
            if(endSize > localSize){
                endSize = localSize;
            }
            if(beginSize < 0){
                beginSize = 0;
            }
            //等待写入的文件大小
            long writeSize = endSize - beginSize;
            if(writeSize <= 0){
                throw new CreateException(1,"文件指针参数出错");
            }
            //获取百分单位是 1-100
            RandomAccessFile raf = new RandomAccessFile(localFile,"r");
            OutputStream out = ftpClient.appendFileStream(new String(remoteFile.getBytes("GBK"),"iso-8859-1"));
            //把文件指针移动到 开始位置
            ftpClient.setRestartOffset(beginSize);
            raf.seek(beginSize);
            //定义最小移动单位是 1024字节 也就是1kb
            byte[] bytes = new byte[1024];
            int c;
            double finishSize = 0;
            double finishPercent = 0;
            //存在一个bug 当分布移动的时候  可能会出现下载重复的问题 后期需要修改
            while ((c = raf.read(bytes)) != -1) {
                out.write(bytes, 0, c);
                finishSize += c;
                if(finishSize > writeSize){
                    finishPercent = 1;
                    //System.out.println(">>>>>完成进度:" + finishPercent);
                    fileObserverAble.setKeyValue(localFile.getName(),finishPercent,"upload");
                    break;
                }
                if ((finishSize / writeSize) - finishPercent > 0.01) {
                    finishPercent = finishSize / writeSize;
                    //System.out.println(">>>>>完成进度:" + finishPercent);
                    fileObserverAble.setKeyValue(localFile.getName(),finishPercent,"upload");
                }
            }
            out.flush();
            raf.close();
            out.close();
            boolean result =ftpClient.completePendingCommand();
            return  result?UploadStatus.Upload_From_Break_Success:UploadStatus.Upload_From_Break_Failed;
        }
    
    
        /**
         * 从FTP服务器上下载文件
         * @param remote 远程文件路径
         * @param local 本地文件路径
         * @return 是否成功
         * @throws IOException
         */
        public boolean download(String remote,String local) throws Exception{
            FTPFile[] files = ftpClient.listFiles(remote);
            if(files == null || files.length < 0){
                throw new CreateException(-1,"远程文件不存在");
            }
            if(files.length != 1){
                throw new CreateException(-1,"远程文件不唯一");
            }
            File localFile = new File(local);
            if(localFile.exists()){
                long localBeginSize = localFile.length();
                if(localBeginSize == files[0].getSize()){
                    throw new CreateException(-1,"文件已经存在");
                }else if(localBeginSize > files[0].getSize()){
                    throw new CreateException(-1,"下载文件出错");
                }
                return downloadByUnit(remote,local,localBeginSize,files[0].getSize());
            }else {
                return downloadByUnit(remote,local,0,files[0].getSize());
            }
        }
        private Boolean downloadByUnit(String remote,String local,long beginSize,long endSize) throws Exception {
            File localFile = new File(local);
            long waitSize = endSize - beginSize;
            //进行断点续传,并记录状态
            FileOutputStream out = new FileOutputStream(localFile,true);
            //把文件指针移动到 开始位置
            ftpClient.setRestartOffset(beginSize);
            InputStream in = ftpClient.retrieveFileStream(new String(remote.getBytes("GBK"),"iso-8859-1"));
            byte[] bytes = new byte[1024];
            int c;
            double finishSize =0;
            double finishPercent = 0;
            while((c = in.read(bytes))!= -1){
                out.write(bytes,0,c);
                finishSize += c;
                if(finishSize > waitSize){
                    //System.out.println(">>>>>完成进度:" + 1);
                    fileObserverAble.setKeyValue(localFile.getName(),1,"download");
    
                }
                if ((finishSize / waitSize) - finishPercent > 0.01) {
                    finishPercent = finishSize / waitSize;
                    //System.out.println(">>>>>完成进度:" + finishPercent);
                    fileObserverAble.setKeyValue(localFile.getName(),finishPercent,"download");
                }
            }
            in.close();
            out.close();
            return ftpClient.completePendingCommand();
        }
    
    
    }

    测试上传 并在控制台打印出 上传百分百

    public static void main(String[] args) {
            ContinueFTP ftp = new ContinueFTP();
            try {
                ftp.connect("172.20.139.217", 21, "ftp01", "ftp111");
                FileOperateByFtp fileOperateByFtp = new FileOperateByFtp(ftp.getFtpClient());
                fileOperateByFtp.upload("F:\upload7.temp","/upload2/f3/upload7.temp");
                fileOperateByFtp.upload("F:\upload6.temp","/upload2/f3/upload6.temp");
               /* fileOperateByFtp.download("/upload2/f3/upload7.temp","F:\upload6.temp");
                fileOperateByFtp.download("//upload2/f3/upload6.temp","F:\upload7.temp");*/
                if(ftp.getFtpClient() != null){
                    ftp.getFtpClient().disconnect();
                }
            } catch (Exception e) {
                if(e instanceof CreateException){
                    System.out.println(((CreateException) e).getErrMessage());
                }
            }
    
        }

     上传结果:

     下载就不做演示

    四:总结

     ftp文件长传其实很简单,,实现断点续传也不能
    ftp里面提供了一个  ftpClient.setRestartOffset(beginSize); 方法 实现了文件指针移动的开始位置  为后面的 分布式断点 多点上传 提供了 基础 .

    另外关于文件显示进度比例,在这里实现也不能,但要是与前端进度条进行实时数据交互式不现实的。。。后来通过查阅资料发现有些还很有道理的。

    比如我们服务器一般也会限制文件上传的大小,所以一般显示进度条是在前端做的,通过比较浏览器发送出去的数据量 和带上传的文件大小 进行比较来显示 进度条,但这种方法还没有测试成功,后面会进行验证。

  • 相关阅读:
    转:win2000/2003 Discuz生存环境搭建及基础优化
    http://db.grussell.org 测试答案 1
    转:linux系统的主机做代理服务器
    C# code 0002
    Welcome to .NET BY C#
    几篇关于Visual Studio Team Foundation Server (TFS) 安装的文章
    VS2003不知道怎么不能编译C文件
    Kaspersky 更新修复
    入行性能测试两个月
    WMI 注册表 StdRegProv
  • 原文地址:https://www.cnblogs.com/luffyu/p/7491939.html
Copyright © 2011-2022 走看看