JAVA实现FTP文件上传下载及FTP服务器搭建
前言
最近项目中需要到FTP服务器中下载文件,于是花了点时间研究了一下,网上查了很多资料,都没有很详细的FTP服务器的搭建教程,踩了很多坑,终于搞定了,因此在此记录分享,如有错误,欢迎大家指正,谢谢!
一、FTP是什么?
FTP是一个专门进行文件管理的操作服务,一般来讲可以在任意的操作系统之中进行配置,但是如果考虑到简便性,一般来讲可以直接在Linux系统下进行安装。
FTP (File Transfer Protocol、文件传输协议)是TCP/IP协议中的一部分,属于应用层协议。使用FTP最主要的功能是对文件进行管理,所以在FTP内部对于文件支持有两种传输模式:文本模式(ASCII、默认)和二进制模式(Binary),通常文本文件使用ASCIl模式,而对于图片、视频、声音、压缩等文件则会使用二进制的方式进行传输。
FTP的操作一般是分为服务端以及客户端两个组成部分,客户端需要通过特定的FTP服务进行服务器的连接(大部分的FTP服务器上都是需要进行用户登录认证的)。
二、FTP两种模式
如果要进行FTP文件的管理,则客户端一定要与FTP服务器进行连接,在FTP中每一次通讯实际上都需要有两个连接存在,一个连接专门用于传输FTP命令、另外一个连接负责数据传送,所以在FTP中一般会支持两种不同的工作模式:一种是Standard模式(也被称为“PORT”模式),另外一种是Passive (也被称为“PASV”模式),这两种模式的概念如下:
- PORT主动模式
当客户端与服务端连接后,客户端会打开一个新的本地端口,随后将此端口告诉给FTP服务端,这样FTP服务端就会主动的连接到FTP客户端公布的端口,随后进行数据传送。
- PASV被动模式
FTP在定义的时候就公布了一个操作端口(一般为21端口),这样当客户端连接之后会明确的知道该操作端口并且进行数据传送。
在实际的FTP运行机制之中,如果要想通过FTP服务进行操作,则一般会使用被动模式,在所有的系统中几乎都会存在有防火墙的概念,如果要考虑到客户端的方便使用的话,被动的模式会更加的合理。
三、搭建FTP服务器
本文的FTP服务器是在CentOS7系统上搭建,FTP功能验证是在Windows10专业版。
安装FTP服务
[root@localhost ~]# yum install vsftpd
添加FTP访问用户
[root@localhost ~]# useradd myjftp
#设置密码,密码位数不足八位,会提示你无效的密码: 密码少于 8 个字符,不用管,继续设置即可。
[root@localhost ~]# passwd myjftp
创建用户能访问的目录且给于操作权限
[root@localhost ~]# mkdir -p /data/ftp_data
[root@localhost ~]# mkdir -p /data/ftp_data/anno
#为目录分配操作权限,注意,匿名用户一定不能有写权限
[root@localhost ~]# chmod o+w /data/ftp_data/
修改FTP服务配置文件
[root@localhost ~]# cd /etc/vsftpd/
[root@localhost vsftpd]# ll
总用量 20
-rw-------. 1 root root 125 6月 10 2021 ftpusers
-rw-------. 1 root root 361 6月 10 2021 user_list
-rw-------. 1 root root 5116 6月 10 2021 vsftpd.conf
-rwxr--r--. 1 root root 338 6月 10 2021 vsftpd_conf_migrate.sh
#备份配置文件,且删除文件中的注释内容
[root@localhost vsftpd]# mv vsftpd.conf vsftpd.conf.bak
[root@localhost vsftpd]# grep -v "#" vsftpd.conf.bak >vsftpd.conf
#修改配置文件
[root@localhost vsftpd]# vim vsftpd.conf
#配置内容如下,复制即可:
anonymous_enable=YES
local_enable=YES
write_enable=YES
local_umask=022
anon_root=/data/ftp_data/anon
anon_upload_enable=YES
anon_mkdir_write_enable=YES
local_root=/data/ftp_data
chroot_local_user=YES
#限制本地用户只能在根目录
allow_writeable_chroot=YES
dirmessage_enable=YES
xferlog_enable=YES
connect_from_port_20=YES
xferlog_std_format=YES
listen=NO
listen_ipv6=YES
pam_service_name=vsftpd
userlist_enable=YES
tcp_wrappers=YES
配置文件各种配置解释如下,有需要可查看。
anonymous_enable 设置为NO,表示不允许匿名访问。
local_enable 设置为YES,表示允许本地用户登录。
chroot_local_user 设置为YES,这样我们就能限制用户在自己的目录中。
local_root 设置为/home/cppuser,这将用户的根目录设置为/home/cppuser。
write_enable 设置为YES,允许用户上传文件。
dirmessage_enable 设置为YES,这样用户在访问目录时会看到消息。
user_config_dir 设置为/etc/vsftpd/user_conf,这样我们就能为每个用户创建单独的配置文件。
local_root 设置为/home/cppuser/%u,这将用户的根目录设置为他们的用户名目录。
chroot_local_user 设置为YES,这样我们就能限制用户在自己的目录中。
secure_chroot_dir 设置为/etc/vsftpd/securefiles,这将用于存储用户的公钥。
chroot_list_enable 设置为YES,且设置下面的chroot_list_file。
chroot_list_file 设置/etc/vsftpd/chroot_list,这将列出所有被限制在自己目录的用户。
local_root 设置为/home/%u/%u,,这将用户的根目录设置为他们的用户名目录下的用户名目录。
chroot_local_user 设置为YES,这样我们就能限制用户在自己的目录中。
chroot_list_enable 设置为YES,且设置下面的chroot_list_file
chroot_list_file 设置为/etc/vsftpd/chroot_list,这将列出所有被限制在自己目录的用户。
userlist_enable 设置为YES,且设置下面的userlist_file
userlist_file 设置为/etc/vsftpd/userlist,这将列出所有被禁止登录的用户。
passwd_file 设置为/etc/vsftpd/passwd,这将存储用户的密码。
auth_system 设置为YES,且设置下列auth_local
auth_local 设置为NO,这将使用系统的密码文件进行认证。
allow_anonlogin 设置为NO,这将阻止匿名用户登录。
anonymous_root 设置为/home/cppuser,这将匿名用户的根目录设置为/home/cppuser
ftp_username 设置为ftp,这将用于 FTP 认证的 Unix 用户名。
local_root 设置为/home/cppuser/%u,这将用户的根目录设置为他们的用户名目录。
启动FTP服务
[root@localhost vsftpd]# systemctl start vsftpd
防火墙永久性地添加 FTP 服务
[root@localhost vsftpd]# sudo firewall-cmd --permanent --add-service=ftp
success
#重新加载防火墙配置,使更改生效
[root@localhost vsftpd]# firewall-cmd --reload
success
#验证 FTP 服务是否已添加到防火墙中
[root@localhost vsftpd]# firewall-cmd --list-all
public (active)
target: default
icmp-block-inversion: no
interfaces: ens33
sources:
services: dhcpv6-client ftp ssh 【表示成功添加】
ports:
protocols:
masquerade: no
forward-ports:
source-ports:
icmp-blocks:
rich rules:
设置SELinux的状态,SELinux的状态有Enforcing(强制执行)、Permissive(宽容)或者Disabled(禁用)三种状态,需要将它的状态设置为非Enforcing(强制执行)状态就行。
#查看SELinux的状态
[root@localhost vsftpd]# getenforce
Enforcing
#临时设置SELinux的状态为Permissive(宽容)
[root@localhost vsftpd]# setenforce 0
#永久设置状态方法
vi /etc/selinux/config
#将下列配置修改成你需要的状态即可
SELINUX=permissive
到此,FTP服务器就简单搭建完毕了,下面开始测试结果。
在/data/ftp_data/ 目录下新建文件hello.txt
[root@localhost ftp_data]# touch hello.txt
[root@localhost ftp_data]# ls
anno hello.txt
打开本地Windows10系统的磁盘,在文件路径处直接输入ftp://你的IP地址 然后回车,输入账密。
哈哈,到此,FTP服务器就算搭建成功了!
三、JAVA实现FTP文件上传下载工具类
引入依赖
<dependency>
<groupId>commons-net</groupId>
<artifactId>commons-net</artifactId>
<version>3.9.0</version>
</dependency>
FTP工具类
package com.example.springbootmytest.ftp;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.charset.StandardCharsets;
/**
* FTP服务器文件上传下载工具类
*/
@Slf4j
public class FTPUtil {
private static final int CONNECT_TIME_OUT = 5000;
private static final String ENCODING ="UTF-8";
/**
* FTP服务器连接工具方法
* @param host FTP服务器IP
* @param port FTP服务器端口
* @param userName FTP服务器用户名称
* @param password FTP服务器用户密码
*/
public static FTPClient connectFTP(String host,Integer port,String userName,String password) throws IOException {
FTPClient client = new FTPClient();
//连接服务器
client.connect(host, port);
//登录服务器
client.login(userName, password);
//设置连接超时时间
client.setConnectTimeout(CONNECT_TIME_OUT);
//设置编码格式
client.setControlEncoding(ENCODING);
// 设置文件传输编码类型, 字节传输:BINARY_FILE_TYPE, 文本传输:ASCII_FILE_TYPE, 建议使用BINARY_FILE_TYPE进行文件传输
client.setFileType(FTP.BINARY_FILE_TYPE);
// 设置为被动模式: enterLocalPassiveMode()
client.enterLocalPassiveMode();
return client;
}
/**
* 上传本地文件到FTP文件目录
* FTP服务器连接工具方法
* @param host FTP服务器IP
* @param port FTP服务器端口
* @param userName FTP服务器用户名称
* @param password FTP服务器用户密码
* @param localAbsoluteFile 本地文件的绝对路径
* @param ftpPath FTP服务器的绝对路径目录
* @throws IOException
*/
public static void UploadFTPFile(String host,Integer port,String userName,String password,String localAbsoluteFile,String ftpPath) throws IOException {
//连接服务器
FTPClient client = null;
FileInputStream fileInputStream = null;
try {
client = connectFTP(host,port,userName,password);
//进入目录
boolean existStatus = client.changeWorkingDirectory(ftpPath);
if (!existStatus) {
client.makeDirectory(ftpPath);
}
File file = new File(localAbsoluteFile);
fileInputStream = new FileInputStream(file);
//设置编码格式,防止出现乱码
String tempPath = ftpPath + File.separator + file.getName();
String ftpFileName = new String(tempPath.getBytes(StandardCharsets.UTF_8), "ISO-8859-1");
boolean pushStatus = client.storeFile(ftpFileName, fileInputStream);
if (pushStatus) {
log.info("文件上传成功!");
} else {
log.info("文件上传失败!");
}
} catch (Exception e) {
log.error("文件上传失败,失败原因:{}",e);
} finally {
if (fileInputStream != null) {
fileInputStream.close();
}
if (client != null) {
client.logout();
client.disconnect();
}
}
}
/**
* 从FTP服务上下载文件到本地目录
* @param host FTP服务器IP
* @param port FTP服务器端口
* @param userName FTP服务器用户名称
* @param password FTP服务器用户密码
* @param ftpAbsoluteFile ftp服务器的文件绝对路径
* @param localPath 本地目录
* @throws IOException
*/
public static void downloadFTPFile(String host,Integer port,String userName,String password,String ftpAbsoluteFile,String localPath)throws IOException{
FTPClient client=null;
FileOutputStream fileOutputStream=null;
try{
client = connectFTP(host,port,userName,password);
File file = new File(localPath);
if(!file.exists()){
file.mkdirs();
}
//获取远程服务器上的文件名称和目录
String[] split = ftpAbsoluteFile.split("/");
String localFileName=split[split.length-1];
String ftpAbsolutePath=ftpAbsoluteFile.substring(0,ftpAbsoluteFile.length()-localFileName.length());
//进入目录
client.changeWorkingDirectory(ftpAbsolutePath);
fileOutputStream = new FileOutputStream(file.getAbsolutePath()+File.separator+localFileName);
boolean pullStatus = client.retrieveFile(localFileName, fileOutputStream);
if(pullStatus){
log.info("文件下载成功!");
}else{
log.info("文件下载失败!");
}
}catch (Exception e){
log.info("文件下载失败,原因原因:{}" ,e);
e.printStackTrace();
}finally {
if (fileOutputStream != null) {
fileOutputStream.close();
}
if (client != null) {
client.logout();
client.disconnect();
}
}
}
}