- 一、背景
- 二、maven依赖
- 三、FTP工具类
- 3.1、主动模式(PORT)
- 3.2、被动模式(PASV)
- 四、验证
- 4.1、dos下操作FTP
- 4.2、FTP文件上传
- 4.3、FTP文件下载
我在之前的文章(Java实现文件上传和下载)里讲过非FTP文件的上传和下载,今天我们来讲一下FTP文件上传和下载,本文测试过程中Spring Boot 版本为2.5.2,commons-net 版本为3.8.0,JDK环境为 1.8。本文是在window环境下完成的,因为本机环境的复杂性,是把本机的防火墙关闭了的(不然dos登录后操作不了,或者上传下载超时)。
二、maven依赖pom.xml
三、FTP工具类4.0.0 org.springframework.boot spring-boot-starter-parent 2.5.2 com.alian ftp 0.0.1-SNAPSHOT ftp java实现FTP文件上传下载 1.8 org.springframework.boot spring-boot-starter org.apache.commons commons-lang3 3.12.0 commons-net commons-net 3.8.0 org.projectlombok lombok 1.16.14 junit junit 4.12 compile org.springframework.boot spring-boot-maven-plugin
本工具类中包含四个方法,实例化方法,上传文件,下载文件,及关闭连接方法。
ApacheFtpClient.java
package com.alian.ftp.utils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.Pair;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import java.io.*;
@Slf4j
public class ApacheFtpClient {
private FTPClient ftpClient;
public ApacheFtpClient(String hostName, int port, String userName, String password) throws IOException {
ftpClient = new FTPClient();
//设置传输命令的超时
ftpClient.setDefaultTimeout(20000);//毫秒
//设置两个服务连接超时时间
ftpClient.setConnectTimeout(10000);//毫秒
//被动模式下设置数据传输的超时时间
ftpClient.setDataTimeout(15000);//毫秒
//被动模式(需要设置在连接之前)
ftpClient.enterLocalPassiveMode();
//连接FTP
ftpClient.connect(hostName, port);
//更加账户密码登录服务
ftpClient.login(userName, password);
}
public Pair uploadFile(String remoteUploadDirectory, String localUploadFilePathName, String encoding) {
FileInputStream fis = null;
try {
// 如果不能进入dir下,说明此目录不存在!
if (!ftpClient.changeWorkingDirectory(remoteUploadDirectory)) {
log.info("没有目录:{}", remoteUploadDirectory);
if (!ftpClient.makeDirectory(remoteUploadDirectory)) {
log.info("创建文件目录【{}】 失败!", remoteUploadDirectory);
return Pair.of(false, "创建文件目录【" + remoteUploadDirectory + "】 失败");
}
}
//进入文件目录
ftpClient.changeWorkingDirectory(remoteUploadDirectory);
//创建文件流
fis = new FileInputStream(new File(localUploadFilePathName));
//设置上传目录
ftpClient.setBufferSize(1024);
//设置编码(默认是ISO-8859-1)
ftpClient.setControlEncoding(encoding);
//设置文件类型(二进制)
ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
//FTP服务器上最终的名字
String uploadFileName = localUploadFilePathName.substring(localUploadFilePathName.lastIndexOf(File.separator) + 1);
//文件上传
boolean b = ftpClient.storeFile(uploadFileName, fis);
int replyCode = ftpClient.getReplyCode();
log.info("上传文件响应码:{}", replyCode);
return Pair.of(b, b ? "上传成功" : "上传失败");
} catch (Exception e) {
log.error("FTP上传文件异常!:", e);
return Pair.of(false, "上传文件异常");
} finally {
try {
if (fis != null) {
fis.close();
}
} catch (IOException e) {
log.error("关闭流发生异常!", e);
}
}
}
public Pair downloadFile(String remoteDownloadDirectory, String localDirectory, String downloadFileName) {
OutputStream out = null;
try {
if (StringUtils.isBlank(downloadFileName)) {
return Pair.of(false, "要下载的文件不能为空");
}
//工作目录切换到下载文件的目录下
if (!ftpClient.changeWorkingDirectory(remoteDownloadDirectory)) {
log.info("目录不存在:{}", remoteDownloadDirectory);
return Pair.of(false, "目录不存在");
}
//获取目录下所有文件
FTPFile[] files = ftpClient.listFiles();
if (files.length < 1) {
return Pair.of(false, "目录为空");
}
boolean fileExist = false;
boolean downloadFlag = false;
//遍历文件列表
for (FTPFile ftpFile : files) {
String localFile = localDirectory + File.separator + downloadFileName;
//是否存在要下载的文件
if (downloadFileName.equals(ftpFile.getName())) {
fileExist = true;
out = new FileOutputStream(localFile);
//下载
downloadFlag = ftpClient.retrieveFile(downloadFileName, out);
int replyCode = ftpClient.getReplyCode();
log.info("下载文件响应码:{}", replyCode);
break;
}
}
if (!fileExist) {
return Pair.of(false, "FTP服务器上文件不存在");
}
return Pair.of(downloadFlag, downloadFlag ? "下载成功" : "下载失败");
} catch (Exception e) {
log.error("FTP下载文件异常!:", e);
return Pair.of(false, "下载文件异常");
} finally {
try {
if (out != null) {
out.flush();
out.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void close() {
try {
if (ftpClient != null && ftpClient.isConnected()) {
ftpClient.disconnect();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
注意构造方法中的调用,FTP协议有两种工作方式:
//被动模式(需要设置在连接之前)
ftpClient.enterLocalPassiveMode();
3.1、主动模式(PORT)
主动模式的FTP是指服务器主动连接客户端的数据端口。
- FTP客户端随机开启一个大于1024的端口N向服务器的21号端口发起连接,同时开放N+1号端口进行监听,然后发送PORT命令到FTP服务器,告知是主动模式
- FTP服务器给客户端的命令端口返回一个ACK的应答码
- FTP客户端发出数据传输的指令
- FTP服务器发起一个从它自己的数据端口(一般是20端口)到客户端先前指定的数据端口(N+1)的连接
- FTP客户端返回一个ACK后,进行数据传输
被动模式的FTP是指服务器被动地等待客户端连接自己的数据端口。
- FTP客户端随机开启一个大于1024的端口N向服务器的21号端口发起连接,同时会开启N+1号端口。然后向服务器发送PASV命令,告知是被动模式
- FTP服务器收到命令后,会开放一个大于1024的端口P进行监听,然后用PORT命令通知客户端,服务端的数据端口是P
- FTP客户端发出数据传输的指令,会通过N+1号数据端口连接服务器的端口P
- FTP服务器给客户端的数据端口返回一个ACK的应答码,然后在两个端口之间进行数据传输
如果你是window环境下操作FTP,建议关闭防火墙操作。
输入命令:
#在cmd命令提示符下输入(ftp加FTP服务器地址) ftp 192.168.0.151 #根据提示输入用户名 Alian #根据提示输入登录密码 Alian@1223
本文登录后的文件列表:
@Test
public void upload() throws IOException {
ApacheFtpClient apacheFtpClient = new ApacheFtpClient("192.168.0.151", 21, "Alian", "Alian@1223");
Pair pair = apacheFtpClient.uploadFile("apacheFTP", "C:\myFile\CSDN\result.png", "GBK");
log.info("上传返回结果:{}", pair);
apacheFtpClient.close();
}
运行结果:
17:16:55.830 [main] INFO com.alian.ftp.utils.ApacheFtpClient - 上传文件响应码:226 17:16:55.835 [main] INFO com.alian.ftp.service.TestApacheFtpService - 上传返回结果:(true,上传成功)
登录FTP服务器上查看文件如下:
@Test
public void download() throws IOException {
ApacheFtpClient apacheFtpClient = new ApacheFtpClient("192.168.0.151", 21, "Alian", "Alian@1223");
Pair pair = apacheFtpClient.downloadFile("apacheFTP", "C:\myFile\download", "result.png");
log.info("下载返回结果:{}", pair);
apacheFtpClient.close();
}
运行结果:
17:20:37.126 [main] INFO com.alian.ftp.utils.ApacheFtpClient - 下载文件响应码:226 17:20:37.130 [main] INFO com.alian.ftp.service.TestApacheFtpService - 下载返回结果:(true,下载成功)
进入到我们本地下载的目录,结果如下:



