以前为了实现文件上传服务器的功能,于是在晚上搜了下,发现可以通过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()
时比较慢,当然我们可以做处理,比如系统启动时就先连接,如果负载过大,我们还可以引入连接池的概念。
上面在文件下载时,说到了遍历下载,如果文件较多或较大时,如何提高下载效率?同时如果下载过程出现问题,如何保证可靠性?
按需补充