jsch实现与服务器完成文件相关操作

以前为了实现文件上传服务器的功能,于是在晚上搜了下,发现可以通过jsch来实现,同时发现jsch还是与服务器间通过一些命令完成其他操作,觉得不可思议,但是当时也没有过多的了解。

而这次需要完成从从服务器下拉文件,开始想到用ftp完成,但是发现借助客户端不是太好实现,或者确实不太了解这方面的知识,想到以前用过jsch,既然能够完成文件的上传,那么是否同样能够完成文件的下载呢?

当然在使用前还是会先查阅一番,如果确实可以实现,当然就会深入去了解,看了一些博客,在https://www.cnblogs.com/weiyi1314/p/9517245.html中写道可以通过 put实现文件上传;get实现文件下载。

建立连接:

public void connect(){
        try {
            JSch jsch = new JSch();
            jsch.getSession(config.getUsername(), config.getHost(), config.getPort());
            sshSession = jsch.getSession(config.getUsername(), config.getHost(), config.getPort());
            sshSession.setConfig("PreferredAuthentications","password");//关闭gssapi认证
            sshSession.setConfig("StrictHostKeyChecking","no");// 设置第一次登陆的时候提示
            sshSession.setPassword(config.getPassword());

            Properties sshConfig = new Properties();
            sshConfig.put("StrictHostKeyChecking", "no");
            sshSession.setConfig(sshConfig);
            sshSession.connect();
//            log.info("Session connected!");
            channel = sshSession.openChannel("sftp");
            channel.connect();
//            log.info("Channel connected!");
            this.sftp = (ChannelSftp) channel;
            this.sftp.setFilenameEncoding("UTF-8");
        } catch (Exception e) {
            e.printStackTrace();
        } 
    }

文件下载:

public void fileDownload(String path,String dist,boolean force){
    try {
        String fileNmae = StringUtils.getFilename(path);

        InputStream is = sftp.get(path);
        FileUtils.copyInputStreamToFile(is, FileUtils.getFile(dist,fileNmae,force));
        is.close();
    } catch (SftpException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    }
}

测试:

public static void main(String[] args) throws SftpException {
        SftpConnection connection = new SftpConnection(new SftpConfig());
        connection.connect();
        ChannelSftp sftp = connection.getSftp();

        // /data/test/1.tar.gz
//        connection.fileDownload("/data/test/1.txt","D:\\test",false);
        connection.close();
    }

确实可以实现,单个文件的下载也就没什么问题了,当然往往实际业务不会这么简单,如果是从目录中遍历下载文件会如何?下载文件性能怎么样?是否还有一些其他的方法可以完成更多的功能?下面来从几个方面了解下。

  • 目录遍历

查看了相关的方法,发现又一个ls方法,同时有一个重载方法,使用都是一样,只不过一个没有返回值,一个需要我们自己处理返回值,这个方法可以将制定目录的内容返回,那么遍历无法就是取出目录中的目录递归遍历了:

public List<String> listFiles(String path){
    List<String> list = new ArrayList<>();
    ChannelSftp.LsEntrySelector selector = new ChannelSftp.LsEntrySelector() {
        @Override
        public int select(ChannelSftp.LsEntry entry) {
            String filename = entry.getFilename();
            if(entry.getAttrs().isReg()){
                list.add("f:"+filename);
            }else if(entry.getAttrs().isDir()){
                if(!".".equals(filename)&&!"..".equals(filename)){
                    list.add("d:"+filename);
                    list.addAll(listFiles(path+"/"+filename));
                }
            }
            return ChannelSftp.LsEntrySelector.CONTINUE;
        }
    };
    try {
        sftp.ls(path,selector);
    } catch (SftpException e) {
        e.printStackTrace();
    }
    return list;
}

想想是没问题,但是真要这么处理还真的不行,首先要明白一点,遍历的目录中,会默认的加上"."、".."这两个,所有在操作的时候把这个排除了。

但是还是出现了异常:

先说明下,我的目录结构:

-- test
  -- 1.txt
  -- a
    -- 2.txt

第一层目录的文件没了?

经过断点发现以下奇怪的地方:

在方法中可以看到,在遇到文件直接保存,遇到目录则递归处理,而这个值出现的则是在递归跳出后目录中的对象,而且根本就不存在这个文件夹。

看了下ls的方法:

发现我们在对某个目录进行操作是,是修改了ChannelSftp的成员变量,而在下次处理其他目录的时候,这些属性确实改变后的值,不知道这个是不是导致出现这种问题的原因?

安装这种思路,我们让每次对目录的处理完成后再处理新的目录,方法改写如下:

public List<String> listFiles(String path,boolean justFile,boolean isFullPath){
    List<String> list = new ArrayList<>();
    List<String> cachePaths = new ArrayList<>();
    Vector vector = null;
    try {
        vector = sftp.ls(path);
    } catch (SftpException e) {
        e.printStackTrace();
    }
    if(vector!=null){
        Iterator it = vector.iterator();
        while (it.hasNext()){
            ChannelSftp.LsEntry entry = (ChannelSftp.LsEntry) it.next();
            String filename = entry.getFilename(),fullPath = path+"/"+filename;
            if(entry.getAttrs().isDir()){
                if(!".".equals(filename) && !"..".equals(filename)){
                    if(!justFile){
                        list.add(isFullPath?fullPath: filename);
                    }
                    cachePaths.add(fullPath);
                }
            }else if(entry.getAttrs().isReg()){
                list.add(isFullPath?fullPath: filename);
            }

        }
    }
    if(cachePaths.size()>0){
        for(String cachePath : cachePaths){
            list.addAll(listFiles(cachePath,justFile,isFullPath));
        }
    }
    return list;
}

经过测试也确实没有出现问题了。

  • 遍历下载

在目录遍历完成后,遍历下载相对比较简单了,使用上面的方法,先将文件去不取出,然后遍历下载:

public void directoryDownload(String path,String dist,boolean force){
    List<String> files = listFiles(path, true, true);
    if(files.size()>0){
        for(String file : files){
            String offsetPath = file.substring(path.length()+1,file.length()-StringUtils.getFilename(file).length());
            fileDownload(file,dist+File.separator+offsetPath,force);
        }
    }
}
  • 性能分析

其实真正做测试时,返现系统启动会非常慢,在网上查了下,

断点可以发现主要在方法

Session.connect()

时比较慢,当然我们可以做处理,比如系统启动时就先连接,如果负载过大,我们还可以引入连接池的概念。

上面在文件下载时,说到了遍历下载,如果文件较多或较大时,如何提高下载效率?同时如果下载过程出现问题,如何保证可靠性?

  • 其他操作

按需补充

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

扫码关注云+社区

领取腾讯云代金券