模式介绍
PORT(主动模式)
PORT中文称为主动模式,工作的原理: FTP客户端连接到FTP服务器的21端口,发送用户名和密码登录,登录成功后要list列表或者读取数据时,客户端随机开放一个端口(1024以上),发送 PORT命令到FTP服务器,告诉服务器客户端采用主动模式并开放端口;FTP服务器收到PORT主动模式命令和端口号后,通过服务器的20端口和客户端开放的端口连接,发送数据。
PASV(被动模式)
PASV是Passive的缩写,中文成为被动模式,工作原理:FTP客户端连接到FTP服务器的21端口,发送用户名和密码登录,登录成功后要list列表或者读取数据时,发送PASV命令到FTP服务器, 服务器在本地随机开放一个端口(1024以上),然后把开放的端口告诉客户端, 客户端再连接到服务器开放的端口进行数据传输。
两种模式的比较
从上面的运行原来看到,主动模式和被动模式的不同简单概述为: 主动模式传送数据时是“服务器”连接到“客户端”的端口;被动模式传送数据是“客户端”连接到“服务器”的端口。
主动模式需要客户端必须开放端口给服务器,很多客户端都是在防火墙内,开放端口给FTP服务器访问比较困难。
被动模式只需要服务器端开放端口给客户端连接就行了。
注意:
java中,内网用被动模式 ,外网连接时用主动模式,服务器相应改动
连接FTP代码如下,这里提供一个工具类
package com.ahtsoft.mesher.support.util;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPReply;
import java.io.*;
import java.util.HashMap;
import java.util.Map;
@Slf4j
public class FTPUtil {
/** FTP地址 **/
private String ftpAddress;
/** FTP端口 **/
private int ftpPort = 0;
/** FTP用户名 **/
private String ftpUsername;
/** FTP密码 **/
private String ftpPassword;
/** FTP基础目录 **/
private String basePath = "/";
/** 初始化登录ftp 默认false 登录成功返回true **/
private Boolean b = false;
/** 是否是内网 **/
private Boolean privateNetwork = false;
public Boolean getB() {
return b;
}
/**
* 2018-6-13 12:39:55
* 新添,初始化登录ftp,连接失败 返回b 为:false ,成功 为 :true
* @param ftpUsername
* @param ftpPassword
* @param basePath
*/
public FTPUtil(String ftpAddress, int ftpPort, String ftpUsername, String ftpPassword, String basePath,Boolean privateNetwork) {
this.ftpAddress = ftpAddress;
this.ftpPort = ftpPort;
this.ftpUsername = ftpUsername;
this.ftpPassword = ftpPassword;
this.basePath = basePath;
this.privateNetwork = privateNetwork;
b = login(ftpAddress, ftpPort, this.ftpUsername, this.ftpPassword);
}
/** 本地字符编码 **/
private static String localCharset = "GBK";
/** FTP协议里面,规定文件名编码为iso-8859-1 **/
private static String serverCharset = "ISO-8859-1";
/** UTF-8字符编码 **/
private static final String CHARSET_UTF8 = "UTF-8";
/** OPTS UTF8字符串常量 **/
private static final String OPTS_UTF8 = "OPTS UTF8";
/** 设置缓冲区大小 **/
private static final int BUFFER_SIZE = 1024 * 1024 * 10;
/** FTPClient对象 **/
private static FTPClient ftpClient = null;
/**
* 下载指定文件到本地
*
* @param ftpPath FTP服务器文件相对路径,例如:test/123
* @param fileName 要下载的文件名,例如:test.txt
* @param savePath 保存文件到本地的路径,例如:D:/test
* @return 成功返回true,否则返回false
*/
public boolean downloadFile(String ftpPath, String fileName, String savePath) {
// 登录
boolean flag = false;
if (ftpClient != null) {
try {
String path = changeEncoding(basePath + ftpPath);
// 判断是否存在该目录
if (!ftpClient.changeWorkingDirectory(path)) {
log.error(basePath + ftpPath + "该目录不存在");
return flag;
}
if(privateNetwork){
ftpClient.enterLocalActiveMode(); // 设置主动模式,内网使用
}else {
ftpClient.enterLocalPassiveMode(); // 设置被动模式,开通一个端口来传输数据,外网使用
}
String[] fs = ftpClient.listNames();
// 判断该目录下是否有文件
if (fs == null || fs.length == 0) {
log.error(basePath + ftpPath + "该目录下没有文件");
return flag;
}
for (String ff : fs) {
String ftpName = new String(ff.getBytes(CHARSET_UTF8), CHARSET_UTF8);
if (ftpName.equals(fileName)) {
File file = new File(savePath + '/' + ftpName);
try {
OutputStream os = new FileOutputStream(file);
flag = ftpClient.retrieveFile(ff, os);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
break;
}
}
} catch (IOException e) {
log.error("下载文件失败", e);
} finally {
Boolean close = closeConnect();
log.info("连接是否关闭:" + close);
}
}
return flag;
}
/**
* 下载该目录下所有文件到本地
*
* @param ftpPath FTP服务器上的相对路径,例如:test/123
* @param savePath 保存文件到本地的路径,例如:D:/test
* @return 成功返回true,否则返回false
*/
public boolean downloadFiles(String ftpPath, String savePath) {
// 登录
boolean flag = false;
if (ftpClient != null) {
try {
String path = changeEncoding(basePath + ftpPath);
// 判断是否存在该目录
if (!ftpClient.changeWorkingDirectory(path)) {
log.error(basePath + ftpPath + "该目录不存在");
return flag;
}
if(privateNetwork){
ftpClient.enterLocalActiveMode(); // 设置主动模式,内网使用
}else {
ftpClient.enterLocalPassiveMode(); // 设置被动模式,开通一个端口来传输数据,外网使用
}
String[] fs = ftpClient.listNames();
// 判断该目录下是否有文件
if (fs == null || fs.length == 0) {
log.error(basePath + ftpPath + "该目录下没有文件");
return flag;
}
for (String ff : fs) {
String ftpName = new String(ff.getBytes(CHARSET_UTF8), CHARSET_UTF8);
File file = new File(savePath + '/' + ftpName);
try {
OutputStream os = new FileOutputStream(file);
ftpClient.retrieveFile(ff, os);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
flag = true;
} catch (IOException e) {
log.error("下载文件失败", e);
} finally {
Boolean close = closeConnect();
log.info("连接是否关闭:" + close);
}
}
return flag;
}
/**
* 获取该目录下所有文件,以字节数组返回
*
* @param ftpPath FTP服务器上文件所在相对路径,例如:test/123
* @return Map<String, Object> 其中key为文件名,value为字节数组对象
*/
public Map<String, byte[]> getFileBytes(String ftpPath) {
// 登录
Map<String, byte[]> map = new HashMap<String, byte[]>();
if (ftpClient != null) {
try {
String path = changeEncoding(basePath + ftpPath);
// 判断是否存在该目录
if (!ftpClient.changeWorkingDirectory(path)) {
log.error(basePath + ftpPath + "该目录不存在");
return map;
}
if(privateNetwork){
ftpClient.enterLocalActiveMode(); // 设置主动模式,内网使用
}else {
ftpClient.enterLocalPassiveMode(); // 设置被动模式,开通一个端口来传输数据,外网使用
}
String[] fs = ftpClient.listNames();
// 判断该目录下是否有文件
if (fs == null || fs.length == 0) {
log.error(basePath + ftpPath + "该目录下没有文件");
return map;
}
for (String ff : fs) {
try {
InputStream is = ftpClient.retrieveFileStream(ff);
String ftpName = new String(ff.getBytes(CHARSET_UTF8), CHARSET_UTF8);
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
byte[] buffer = new byte[BUFFER_SIZE];
int readLength;
while ((readLength = is.read(buffer, 0, BUFFER_SIZE)) > 0) {
byteStream.write(buffer, 0, readLength);
}
map.put(ftpName, byteStream.toByteArray());
ftpClient.completePendingCommand(); // 处理多个文件
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
} catch (IOException e) {
log.error("获取文件失败", e);
} finally {
Boolean close = closeConnect();
log.info("连接是否关闭:" + close);
}
}
return map;
}
/**
* 根据名称获取文件,以字节数组返回
*
* @param ftpPath FTP服务器文件相对路径,例如:test/123
* @param fileName 文件名,例如:test.xls
* @return byte[] 字节数组对象
*/
public byte[] getFileBytesByName(String ftpPath, String fileName) {
// 登录
ByteArrayOutputStream byteStream = new ByteArrayOutputStream();
if (ftpClient != null) {
try {
String path = changeEncoding(basePath + ftpPath);
// 判断是否存在该目录
if (!ftpClient.changeWorkingDirectory(path)) {
log.error(basePath + ftpPath + "该目录不存在");
return byteStream.toByteArray();
}
if(privateNetwork){
ftpClient.enterLocalActiveMode(); // 设置主动模式,内网使用
}else {
ftpClient.enterLocalPassiveMode(); // 设置被动模式,开通一个端口来传输数据,外网使用
}
String[] fs = ftpClient.listNames();
// 判断该目录下是否有文件
if (fs == null || fs.length == 0) {
log.error(basePath + ftpPath + "该目录下没有文件");
return byteStream.toByteArray();
}
for (String ff : fs) {
String ftpName = new String(ff.getBytes(CHARSET_UTF8), CHARSET_UTF8);
int index = ftpName.indexOf(fileName);
if (index != -1) {
try {
InputStream is = ftpClient.retrieveFileStream(ff);
byte[] buffer = new byte[BUFFER_SIZE];
int len;
while ((len = is.read(buffer, 0, BUFFER_SIZE)) != -1) {
byteStream.write(buffer, 0, len);
}
} catch (Exception e) {
log.error(e.getMessage(), e);
}
break;
}
}
} catch (IOException e) {
log.error("获取文件失败", e);
} finally {
Boolean close = closeConnect();
log.info("连接是否关闭:" + close);
}
}
return byteStream.toByteArray();
}
/**
* 获取该目录下所有文件,以输入流返回
*
* @param ftpPath FTP服务器上文件相对路径,例如:test/123
* @return Map<String, InputStream> 其中key为文件名,value为输入流对象
*/
public Map<String, InputStream> getFileInputStream(String ftpPath) {
// 登录
Map<String, InputStream> map = new HashMap<String, InputStream>();
if (ftpClient != null) {
try {
String path = changeEncoding(basePath + ftpPath);
// 判断是否存在该目录
if (!ftpClient.changeWorkingDirectory(path)) {
log.error(basePath + ftpPath + "该目录不存在");
return map;
}
if(privateNetwork){
ftpClient.enterLocalActiveMode(); // 设置主动模式,内网使用
}else {
ftpClient.enterLocalPassiveMode(); // 设置被动模式,开通一个端口来传输数据,外网使用
}
String[] fs = ftpClient.listNames();
// 判断该目录下是否有文件
if (fs == null || fs.length == 0) {
log.error(basePath + ftpPath + "该目录下没有文件");
return map;
}
for (String ff : fs) {
String ftpName = new String(ff.getBytes(CHARSET_UTF8), CHARSET_UTF8);
InputStream is = ftpClient.retrieveFileStream(ff);
map.put(ftpName, is);
ftpClient.completePendingCommand(); // 处理多个文件
}
} catch (IOException e) {
log.error("获取文件失败", e);
} finally {
Boolean close = closeConnect();
log.info("连接是否关闭:" + close);
}
}
return map;
}
/**
* 根据名称获取文件,以输入流返回
*
* @param ftpPath FTP服务器上文件相对路径,例如:test/123
* @param fileName 文件名,例如:test.txt
* @return InputStream 输入流对象
*/
public InputStream getInputStreamByName(String ftpPath, String fileName) throws IOException {
// 登录
InputStream input = null;
if (ftpClient != null) {
try {
String path = changeEncoding(basePath + ftpPath);
// 判断是否存在该目录
if (!ftpClient.changeWorkingDirectory(path)) {
log.error(basePath + ftpPath + "该目录不存在");
return null;
}
if(privateNetwork){
ftpClient.enterLocalActiveMode(); // 设置主动模式,内网使用
}else {
ftpClient.enterLocalPassiveMode(); // 设置被动模式,开通一个端口来传输数据,外网使用
}
//设置缓冲区
ftpClient.setBufferSize(64*1024*1024);
input = ftpClient.retrieveFileStream(fileName);
} catch (IOException e) {
log.error("获取文件失败", e);
throw e;
} finally {
Boolean connect = closeConnect();
log.info("连接关闭状态:" + connect);
}
}
return input;
}
/**
* 根据文件夹,文件 名称,判断是否存在
*
* @param ftpPath FTP服务器上文件相对路径,例如:test/123
* @param fileName 文件名,例如:test.txt
* @return map
*/
public Map checkoutFtpPathAndFileName(String ftpPath, String fileName) {
// 登录
Map<String, Boolean> map = new HashMap<String, Boolean>();
map.put("filePath", false);
map.put("fileName", false);
if (ftpClient != null) {
try {
String path = changeEncoding(basePath + ftpPath);
// 判断是否存在该目录
if (!ftpClient.changeWorkingDirectory(path)) {
log.info(basePath + ftpPath + "该目录不存在");
map.put("filePath", false);
} else {
map.put("filePath", true);
}
if(privateNetwork){
ftpClient.enterLocalActiveMode(); // 设置主动模式,内网使用
}else {
ftpClient.enterLocalPassiveMode(); // 设置被动模式,开通一个端口来传输数据,外网使用
}
String[] fs = ftpClient.listNames();
// 判断该目录下是否有文件
if (fs == null || fs.length == 0) {
log.info(basePath + ftpPath + "该目录下没有文件");
map.put("fileName", false);
}
for (String ff : fs) {
String ftpName = new String(ff.getBytes(CHARSET_UTF8), CHARSET_UTF8);
if (ftpName.equals(fileName)) {
map.put("fileName", true);
}
}
} catch (IOException e) {
log.error("获取文件失败", e);
}
}
return map;
}
/**
* 连接FTP服务器
*
* @param address 地址,如:127.0.0.1
* @param port 端口,如:21
* @param username 用户名,如:root
* @param password 密码,如:root
*/
private Boolean login(String address, int port, String username, String password) {
ftpClient = new FTPClient();
try {
ftpClient.connect(address, port);
ftpClient.login(username, password);
ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
//不进行地址校验
ftpClient.setRemoteVerificationEnabled(false);
int reply = ftpClient.getReplyCode();
if (!FTPReply.isPositiveCompletion(reply)) {
closeConnect();
log.error("FTP服务器连接失败:" + "地址:" + address + " 端口:" + port + " 用户名:" + username + " 密码:" + password);
} else {
b = true;
}
} catch (Exception e) {
log.error("FTP登录失败", e);
}
return b;
}
/**
* 关闭FTP连接
*
*/
public Boolean closeConnect() {
Boolean b = false;
if (ftpClient != null && ftpClient.isConnected()) {
try {
// ftpClient.logout();
ftpClient.disconnect();
b = true;
} catch (IOException e) {
log.error("关闭FTP连接失败", e);
}
}
return b;
}
/**
* FTP服务器路径编码转换
*
* @param ftpPath FTP服务器路径
* @return String
*/
private static String changeEncoding(String ftpPath) {
String directory = null;
try {
if (FTPReply.isPositiveCompletion(ftpClient.sendCommand(OPTS_UTF8, "ON"))) {
localCharset = CHARSET_UTF8;
}
directory = new String(ftpPath.getBytes(CHARSET_UTF8), CHARSET_UTF8);
} catch (Exception e) {
log.error("路径编码转换失败", e);
}
return directory;
}
}
配置如下:
ftp:
# FTP地址
ftpAddress: ip
# FTP端口
ftpPort: 端口
# FTP帐号
ftpUsername: 账号
# FTP密码
ftpPassword: 密码
#是否内网
private_network: false