项目场景ABC三台电脑,AB同一局域网下,A内网、B可访问外网、C为公司电脑,需要将A电脑内网数据传输至公司。不知道有没有伙伴遇到这种场景,思路是A电脑的服务通过B电脑的nginx代理转发至C电脑,BC电脑网络互通问题采用Openvpn虚拟网段。思路是可行后边就开始一步步验证,就开始踩坑了。。。
一、搭建FTP服务器
百度基本都是直接采用Windows自带的iis搭建FTP服务器,也可使用第三方程序搭建FTP例如FileZilla Server更方便操作。
附上链接Windows10 通过 IIS 搭建 FTP 服务器_iis 10 ftp服务器-CSDN博客 建议IP不要固定
直接默认
二、编写业务代码(自行编辑,附上工具类)
package com.sclead.urdis.common.config;
import com.sclead.urdis.common.utils.date.DateUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.filefilter.TrueFileFilter;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPReply;
import org.apache.tools.ant.taskdefs.Sleep;
import java.io.*;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.time.LocalDateTime;
import java.util.Date;
@Slf4j
public class FtpUtil {
/**
* 获取FTPClient对象
*
* @param ftpHost FTP主机服务器
* @param ftpPassword FTP 登录密码
* @param ftpUserName FTP登录用户名
* @param ftpPort FTP端口 默认为21
* @return
*/
public static FTPClient getFTPClient(String ftpHost, String ftpUserName, String ftpPassword, int ftpPort) {
FTPClient ftpClient = null;
try {
//创建一个ftp客户端
ftpClient = new FTPClient();
// 连接FTP服务器
ftpClient.connect(ftpHost, ftpPort);
// 登陆FTP服务器
boolean login = ftpClient.login(ftpUserName, ftpPassword);
log.info("登录FTP服务器:"+login);
if (!FTPReply.isPositiveCompletion(ftpClient.getReplyCode())) {
log.info("未连接到FTP,用户名或密码错误。");
System.out.println("未连接到FTP,用户名或密码错误。");
ftpClient.disconnect();
} else {
log.info("FTP连接成功。");
System.out.println("FTP连接成功。");
}
} catch (SocketException e) {
e.printStackTrace();
log.info("FTP的IP地址可能错误,请正确配置。");
System.out.println("FTP的IP地址可能错误,请正确配置。");
} catch (IOException e) {
e.printStackTrace();
log.info("FTP的端口错误,请正确配置。");
System.out.println("FTP的端口错误,请正确配置。");
}
return ftpClient;
}
/**
* 下载文件
*
* @param ftpHost ftp服务器地址
* @param ftpUserName anonymous匿名用户登录,不需要密码。administrator指定用户登录
* @param ftpPassword 指定用户密码
* @param ftpPort ftp服务员器端口号
* @param ftpPath ftp文件存放物理路径
* @param fileName 文件路径
*/
public static void downloadFile(String ftpHost, String ftpUserName, String ftpPassword, int ftpPort, String ftpPath, String localPath, String fileName) {
FTPClient ftpClient = null;
try {
ftpClient = getFTPClient(ftpHost, ftpUserName, ftpPassword, ftpPort);
ftpClient.setControlEncoding("UTF-8"); // 中文支持
ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
ftpClient.enterLocalPassiveMode();
ftpClient.changeWorkingDirectory(ftpPath);
File localFile = new File(localPath + File.separatorChar + fileName);
OutputStream os = new FileOutputStream(localFile);
ftpClient.retrieveFile(fileName, os);
os.close();
ftpClient.logout();
} catch (FileNotFoundException e) {
log.error("没有找到" + ftpPath + "文件");
e.printStackTrace();
} catch (SocketException e) {
log.error("连接FTP失败.");
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
log.error("文件读取错误。");
e.printStackTrace();
}
}
/**
* 上传文件
*
* @param ftpHost ftp服务器地址
* @param ftpUserName anonymous匿名用户登录,不需要密码。administrator指定用户登录
* @param ftpPassword 指定用户密码
* @param ftpPort ftp服务员器端口号
* @param ftpPath ftp文件存放物理路径
* @param fileName 文件路径
* @param input 文件输入流,即从本地服务器读取文件的IO输入流
*/
public static void uploadFile(String ftpHost, String ftpUserName, String ftpPassword, int ftpPort, String ftpPath, String fileName, InputStream input) {
FTPClient ftp = null;
try {
ftp = getFTPClient(ftpHost, ftpUserName, ftpPassword, ftpPort);
//ftp.setRemoteVerificationEnabled(false); // 关闭远程IP校验
ftp.setUseEPSVwithIPv4(true);
// 检查目标目录是否存在,不存在则逐层创建
String[] folders = ftpPath.split("/"); //循环创建子目录
for (String folder : folders) {
// 跳过空目录
if (folder.isEmpty()) {
continue;
}
folder = new String(folder.getBytes("GBK"), FTP.DEFAULT_CONTROL_ENCODING); //指定FTP中文格式
// 尝试切换到当前目录,如果失败则创建目录
if (!ftp.changeWorkingDirectory(folder)) {
ftp.makeDirectory(folder);
if (!ftp.changeWorkingDirectory(folder)) {
throw new IOException("无法切换到目录:" + folder);
}
}
}
ftp.enterLocalPassiveMode();
ftp.setFileType(FTP.BINARY_FILE_TYPE);
String fullPath = ftpPath + "/" + fileName; // 完整的文件路径
log.info("当前存入FTP路径为:"+fullPath);
fullPath = new String(fullPath.getBytes("GBK"), FTP.DEFAULT_CONTROL_ENCODING); // 确保路径编码正确
boolean b = ftp.storeFile(fullPath, input);
if(b){
System.out.println("上传文件成功");
}
//fileName = new String(fileName.getBytes("GBK"), FTP.DEFAULT_CONTROL_ENCODING);
//ftp.storeFile(fileName, input);
input.close();
ftp.logout();
System.out.println("结束本次上传");
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 创建目录(有则切换目录,没有则创建目录)
* @param dir
* @return
*/
public boolean createDir(String dir,FTPClient ftp){
String d;
try {
//目录编码,解决中文路径问题
d = new String(dir.toString().getBytes("GBK"),"iso-8859-1");
//尝试切入目录
if(ftp.changeWorkingDirectory(d))
return true;
String[] arr = dir.split("/");
StringBuffer sbfDir=new StringBuffer();
//循环生成子目录
for(String s : arr){
sbfDir.append("/");
sbfDir.append(s);
//目录编码,解决中文路径问题
d = new String(sbfDir.toString().getBytes("GBK"),"iso-8859-1");
//尝试切入目录
if(ftp.changeWorkingDirectory(d))
continue;
if(!ftp.makeDirectory(d)){
System.out.println("[失败]ftp创建目录:"+sbfDir.toString());
return false;
}
System.out.println("[成功]创建ftp目录:"+sbfDir.toString());
}
//将目录切换至指定路径
return ftp.changeWorkingDirectory(d);
} catch (Exception e) {
e.printStackTrace();
return false;
}
}
}
因为项目需要需要用到中文,工具类都附带了中文转换,如不需要可将其取消。
//ftp.setRemoteVerificationEnabled(false); // 关闭远程IP校验
ftp.setUseEPSVwithIPv4(true);
这两句代码费了好大劲找资料,在本项目都需要开启,第一个IP校验通过nginx代理后再经过FTP服务器返回的IP地址不一致再进行数据传输就会报错,所以需要开启。如果是第三方服务器可以直接在服务端禁用IP校验,windows的FTP服务暂时未找到此设置选项。 第二个是在复杂的网络环境下确保数据连接正常,项目所有思路都是通的,本地测试是在同一局域网但是只要一放到现场通过nginx代理转发上传文件就报错,差点推翻重来。。。
-
EPSV (Extended Passive Mode):EPSV 是 FTP 的扩展被动模式。在 FTP 中,传输数据需要建立数据连接。标准的被动模式(PASV)使用端口 21 作为控制连接,然后服务器在另一个端口(通常是端口号大于1024)上打开数据连接。EPSV 是扩展的被动模式,它使用了更多的端口范围(如 IPv6 的范围),同时也能够支持 IPv4。
-
方法作用:
setUseEPSVwithIPv4(true)方法告诉 FTP 客户端在使用 IPv4 时,也使用 EPSV 命令来建立数据连接。EPSV 命令可以在某些网络环境下提高 FTP 数据传输的可靠性和兼容性。如果你的 FTP 服务器支持 EPSV 命令,并且你的网络环境有可能受到防火墙或 NAT 网络设备的影响,这个设置可能会帮助你解决数据连接问题。 -
为什么要使用 EPSV:传统的被动模式(PASV)可能在防火墙或 NAT 设备后面导致连接失败,因为它涉及到服务器在动态端口上打开连接,这些端口可能被防火墙阻塞。EPSV 命令通过使用服务器控制端口 21 上的被动数据连接,可以避免这些问题,提高连接的成功率和稳定性。
三、nginx代理配置
stream {
upstream ftp { # FTP控制面转发
server 10.8.0.194:21;
}
upstream ftpt1 { # FTP控制面转发
server 10.8.0.194:10001;
}
upstream ftpt2 { # FTP控制面转发
server 10.8.0.194:10002;
}
upstream ftpt3 { # FTP控制面转发
server 10.8.0.194:10003;
}
server { # 转发FTP控制面请求
listen 21; #监听端口
proxy_pass ftp;
}
server { # 转发FTP控制面请求
listen 10001; #监听端口
proxy_pass ftpt1;
}
server { # 转发FTP控制面请求
listen 10002; #监听端口
proxy_pass ftpt2;
}
server { # 转发FTP控制面请求
listen 10003; #监听端口
proxy_pass ftpt3;
}
}
简约有效,刚开始只配置了21端口连接登录FTP,后边登录连接都没有问题然后上传文件一直报错,后边查资料了解FTP文件传输分为主动模式与被动模式,文件上传与登录连接是两个不同的链路,主动模式是客户端设定端口传输,被动模式是服务端随机分配端口告知客户端进行文件上传。需要详细了解的可以自行百度。 很奇怪的是本地测试不需要指定端口,只要一挂上vpn通过vpn的虚拟ip传输就要报错,后边通过指定被动模式IP范围并将指定的IP配置好nginx解决。

windows中自带的FTP服务在防火墙支持服务里边指定。以上大多是遇到的困难点、也方便自己日后再有需要参考记录,还有openvpn网络互通也有其他方式实现自行斟酌。

2613

被折叠的 条评论
为什么被折叠?



