FTP: File Transfer Protocol文件传输协议,两台计算机传送文件的协议,客户端可以通过FTP命令从服务器下载,上传文件,修改目录。可以通过命令vsftpd -version查看是否安装了vsftpd。
FTP支持以下两种工作模式:
- 主动模式:客户端向FTP服务器发送端口信息,由服务器主动连接该端口。
- 被动模式:FTP服务器开启并发送端口信息给客户端,由客户端连接该端口,服务器被动接受连接。
说明 大多数FTP客户端都在局域网中,没有独立的公网IP地址,且有防火墙阻拦,主动模式下FTP服务器成功连接到客户端比较困难。因此,如无特殊需求,建议您将FTP服务器配置为被动模式。
FTP支持以下三种认证模式:
- 匿名用户模式:任何人无需密码验证就可以直接登录到FTP服务器。这种模式最不安全,一般只用来保存不重要的公开文件,不推荐在生产环境中使用。
- 本地用户模式:通过Linux系统本地账号进行验证的模式,相较于匿名用户模式更安全。
- 虚拟用户模式:FTP服务器的专有用户。虚拟用户只能访问Linux系统为其提供的FTP服务,而不能访问Linux系统的其它资源,进一步增强了FTP服务器的安全性。
一、ftp服务端的安装
# 获得最近的软件包的列表
sudo apt-get update
# 安装FTP服务端
sudo apt-get install vsftpd
# 检测是否安装成功
vsftpd -version
看到如下信息,则表明vsftpd安装成功:
root@wangmin-virtual-machine:~# vsftpd -version
vsftpd: version 3.0.3
二、ftp服务端的配置
FTP服务的默认配置文件是:/etc/vsftpd.conf,在配置之前我们最好先把原文件备份一下,命令如下:
cp /etc/vsftpd.conf /etc/vsftpd_bak.conf
执行vim /etc/vsftd.conf命令,编辑配置文件如下:
# 是否开启监听ipv4和ipv6数据
listen=YES
# 是否开启监听ipv6数据
listen_ipv6=NO
# Allow anonymous FTP? (Disabled by default).
# 是否允许匿名用户登陆,无需密码
anonymous_enable=NO
# Uncomment this to allow local users to log in.
# 是否允许本地用户登录
local_enable=YES
# Uncomment this to enable any form of FTP write command.
# 是否允许登陆者上传文件
write_enable=YES
# 设置本地用户默认要减免的权限
#local_umask=022
# 是否允许匿名用户上传文件
anon_upload_enable=NO
# 是否允许匿名用户新建文件夹
anon_mkdir_write_enable=NO
# 目录消息,能够给远程登陆的用户发送目录
dirmessage_enable=YES
# 服务器所展示的目录将随着本地时间而改变
use_localtime=YES
# Activate logging of uploads/downloads.
# 开启上传下载的日志记录
xferlog_enable=YES
# Make sure PORT transfer connections originate from port 20 (ftp-data).
# 确认连接传输的端口号为20
connect_from_port_20=YES
#默认注释了,开启匿名用户上传用户映射,和下面一行一起使用 48行
#chown_uploads=YES
#默认注释了,将匿名用户上传的文件的用户映射为whoever用户 49行
#chown_username=whoever
# 日志文件存放位置
xferlog_file=/var/log/vsftpd.log
# 日志文件采用标准格式
xferlog_std_format=YES
# You may fully customise the login banner string:
# 在使用shell时登陆那么会发送欢迎语
ftpd_banner=Welcome to blah FTP service.
# 对本地用户是否实施限制
#chroot_local_user=YES
# 对本地用户是否实施限制
chroot_local_user=YES
# 开启限制白名单
chroot_list_enable=YES
# (default follows)允许chroot_list文件中配置的用户登录此ftp服务器
# 白名单路径,若无这个文件需要自己创建
chroot_list_file=/etc/vsftpd.chroot_list
# 设置PAM认证服务的配置文件名称
# pam_service_name=vsftpd
# Uncomment this to indicate that vsftpd use a utf8 filesystem.
# 编码统一为utf8编码,可以识别中文,防止乱码
utf8_filesystem=YES
# 配置ftp服务器的上传下载文件所在的根目录
local_root=/home/ftp
#开启被动模式,若不指定,默认就是被动模式
#pasv_enable=YES
allow_writeable_chroot=YES
#本教程中为Linux实例公网IP
pasv_address=1xx.1XX.1XX.1XX
#设置被动模式下,建立数据传输可使用的端口范围的最小值
#pasv_min_port=50000
#设置被动模式下,建立数据传输可使用的端口范围的最大值
#pasv_max_port=51000
#说明 建议您把端口范围设置在一段比较高的范围内,例如50000~50010,有助于提高访问FTP服务器的安全性
注意 修改和添加配置文件内的信息时,请注意格式问题。例如,添加多余的空格会造成无法重启服务的结果。
用户登录控制参数说明如下表所示。
参数 | 说明 |
---|---|
anonymous_enable=YES | 接受匿名用户 |
no_anon_password=YES | 匿名用户login时不询问口令 |
anon_root=(none) | 匿名用户主目录 |
local_enable=YES | 接受本地用户 |
local_root=(none) | 本地用户主目录 |
用户权限控制参数说明如下表所示。
参数 | 说明 |
---|---|
write_enable=YES | 可以上传文件(全局控制) |
local_umask=022 | 本地用户上传的文件权限 |
file_open_mode=0666 | 上传文件的权限配合umask使用 |
anon_upload_enable=NO | 匿名用户可以上传文件 |
anon_mkdir_write_enable=NO | 匿名用户可以建目录 |
anon_other_write_enable=NO | 匿名用户修改删除 |
chown_username=lightwiter | 匿名上传文件所属用户名 |
三、创建FTP用户及用户组
# 创建一个供FTP服务使用的文件目录
mkdir /home/ftp
# 赋予FTP用户及用组读写权限
chmod 775 -R /home/ftp
# 为FTP服务创建一个Linux用户。本示例中,该用户名为ftptest
sudo adduser ftptest
# 修改ftptest用户的密码
sudo passwd ftptest
# 创建ftpusers用户组
sudo groupadd ftpusers
# 将这个新用户加入到ftpusers用户组中
usermod -G ftpusers ftptest
# 更改/home/ftp目录的拥有者为ftptest
chown -R ftptest:ftpusers /home/ftp
将该用户加入vsftpd.chroot_list白名单中:
mkdir /etc/vsftpd.chroot_list
vim /etc/vsftpd.chroot_list
添加如下内容:
# FTP用户白名单
ftptest
四、FTP服务器的启停命令
service vsftpd start
service vsftpd stop
service vsftpd restart
# 查看FTP服务器监听的端口
netstat -antup | grep ftp
# 运行以下命令设置FTP服务开机自启动。
systemctl enable vsftpd.service
# 运行以下命令启动FTP服务。
systemctl start vsftpd.service
# 查看FTP服务器的运行状态
systemctl status vsftpd.service
# 看到active (running),表明FTP服务器运行正常
root@wangmin-virtual-machine:/etc# systemctl status vsftpd.service
● vsftpd.service - vsftpd FTP server
Loaded: loaded (/lib/systemd/system/vsftpd.service; enabled; vendor preset: enabled)
Active: active (running) since 六 2020-10-31 08:15:29 CST; 45min ago
Main PID: 920 (vsftpd)
CGroup: /system.slice/vsftpd.service
└─920 /usr/sbin/vsftpd /etc/vsftpd.conf
10月 31 08:15:29 wangmin-virtual-machine systemd[1]: Starting vsftpd FTP server...
10月 31 08:15:29 wangmin-virtual-machine systemd[1]: Started vsftpd FTP server.
10月 31 09:00:55 wangmin-virtual-machine systemd[1]: Started vsftpd FTP server.
五、客户端测试
FTP客户端、Windows命令行工具或浏览器均可用来测试FTP服务器。本教程以Google Chrome浏览器为例,介绍FTP服务器的访问步骤。
说明 使用浏览器访问FTP服务器出错时,建议您清除浏览器缓存后再尝试。
- 打开客户端的Google Chrome浏览器。
- 在地址栏中输入
ftp://<FTP服务器公网IP地址>:21
。本教程中为Linux实例的公网IP地址。
- 在弹出的对话框中,输入用户名ftptest和对应的密码,即可对FTP文件进行相应权限的操作。
全部测试方法如下:
方法一:打开浏览器,在地址栏输入:ftp://ip
方法二: 在ubuntu中使用shell输入:ftp ip_address
方法三: 在windows中在文件管理器地址栏输入:ftp://ip_addresss,该方式可以上传下载文件
方法四: 在windows中使用cmd输入:ftp://ip_addresss //显示连接成功
六、springboot实现FTP文件上传与下载
1、pom.xml引入依赖
<dependency>
<groupId>commons-net</groupId>
<artifactId>commons-net</artifactId>
<version>3.6</version>
</dependency>
2、application.properties添加配置
# ftp相关配置
ftp.host=192.168.188.*
# ftp端口号
ftp.port=21
# ftp请求的用户名
ftp.username=ftptest
# ftp请求的密码
ftp.password=1XXX
# ftp请求读取写入的文件路径
ftp.filepath=/home/ftp
3、FtpConfig
import java.io.IOException;
import java.net.SocketException;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPReply;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
@Configuration
@Slf4j
@Getter
@Setter
public class FtpConfig {
/**
* ftp站点
*/
@Value("${ftp.host}")
private String ftpHost;
/**
* ftp端口号
*/
@Value("${ftp.port}")
private int ftpPort;
/**
* ftp访问用户名
*/
@Value("${ftp.username}")
private String ftpUsername;
/**
* ftp访问密码
*/
@Value("${ftp.password}")
private String ftpPassword;
/**
* ftp访问文件路径
*/
@Value("${ftp.filepath}")
private String ftpFilepath;
@Bean
public FTPClient ftpClient(FtpConfig ftpConfig) {
FTPClient ftpClient = new FTPClient();
ftpClient.setConnectTimeout(1000 * 30); // 设置连接超时时间,单位:毫秒
ftpClient.setControlEncoding("UTF-8"); // 设置ftp字符集
ftpClient.enterLocalPassiveMode(); // 设置被动模式,文件传输端口设置
try {
ftpClient.connect(ftpConfig.getFtpHost(),ftpConfig.getFtpPort());
ftpClient.setFileType(FTP.BINARY_FILE_TYPE);// 设置文件传输模式为二进制,可以保证传输的内容不会被改变
ftpClient.login(ftpConfig.getFtpUsername(), ftpConfig.getFtpPassword());
int replyCode = ftpClient.getReplyCode();
// 所有以2开头的代码都是连接成功的响应
if (!FTPReply.isPositiveCompletion(replyCode)) {
ftpClient.disconnect();
log.error("connect to ftp server fail!,replyCode is {}",replyCode);
return null;
} else {
log.info("connect to ftp server success,replyCode is {}",replyCode);
return ftpClient;
}
} catch (SocketException e) {
log.error("connect to ftp server fail,{}", e);
return null;
} catch (IOException e) {
log.error("connect to ftp server fail,{}", e);
return null;
}
}
}
4、FtpService提供上传、下载方法和删除文件的方法
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Objects;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.tomcat.util.http.fileupload.IOUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import lombok.extern.slf4j.Slf4j;
@Service
@Slf4j
public class FtpService {
@Autowired
private FTPClient ftpClient;
/**
* 下载文件
* @param remoteFileName 远程文件名称
* @param remoteDir 远程文件目录
* @param descDir 下载文件保存路径
* @return boolean
*/
public boolean downloadFile(String remoteFileName, String remoteDir, String descDir) {
if (Objects.isNull(ftpClient)) {
log.error("ftpClient is null!");
return false;
}
if (StringUtils.isEmpty(remoteFileName) || StringUtils.isEmpty(remoteDir)) {
log.error("{} or {} is null!", remoteFileName, remoteDir);
return false;
}
InputStream inputStream = null;
OutputStream outputStream = null;
try {
ftpClient.changeWorkingDirectory(remoteDir);
FTPFile[] ftpFiles = ftpClient.listFiles(remoteDir);
if (ftpFiles.length == 0) {
log.error("{},no files found in this directory", remoteDir);
return false;
}
// 遍历当前目录下的文件,判断要读取的文件是否在当前目录下
for (FTPFile ftpFile : ftpFiles) {
if (ftpFile.isFile()) {
if (ftpFile.getName().equals(remoteFileName)) {
// 获取待读文件输入流
inputStream = ftpClient.retrieveFileStream(remoteFileName);
outputStream = new FileOutputStream(new File(descDir + remoteFileName));
// 文件大小 < 2G,请使用IOUtils.copy(input, output)
// 文件大小 > 2G,请使用IOUtils.copyLarge(input, output)
long available = IOUtils.copyLarge(inputStream, outputStream);
if (available < 0) {
log.error("copy failed!");
return false;
}
outputStream.flush();
}
}else {
log.info("{} is a directory",ftpFile);
}
}
log.info("download file {} success", remoteFileName);
ftpClient.logout();
} catch (IOException e) {
log.error("download file fail,{}", e);
return false;
} finally {
if (ftpClient.isConnected()) {
try {
ftpClient.disconnect();
} catch (IOException e) {
log.error("disconnect fail,{}", e);
return false;
}
}
if (inputStream != null) {
try {
inputStream.close();
} catch (IOException e) {
log.error("inputStream close fail,{}", e);
return false;
}
}
if (outputStream != null) {
try {
outputStream.close();
} catch (IOException e) {
log.error("outputStream close fail,{}", e);
return false;
}
}
}
return true;
}
/**
* 上传文件
*
* @param inputStream 待上传文件的输入流
* @param originName 文件保存时的名字
* @param remoteDir 文件要存放的目录
*/
public boolean uploadFile(InputStream inputStream, String originName, String remoteDir) {
if (Objects.isNull(ftpClient)) {
log.error("ftpClient is null!");
return false;
}
try {
// 进入到文件保存的目录
boolean changeDir = ftpClient.changeWorkingDirectory(remoteDir);
if(changeDir) {
// 保存文件
boolean storeFile = ftpClient.storeFile(originName, inputStream);
ftpClient.logout();
if(storeFile) {
log.info("{} upload success", originName);
return true;
}else {
log.error("store file fail,{}",originName);
return false;
}
}else {
log.error("change working directory fail,{}",remoteDir);
return false;
}
} catch (IOException e) {
log.error("{} upload fail,{}", originName, e);
return false;
} finally {
if (ftpClient.isConnected()) {
try {
ftpClient.disconnect();
} catch (IOException e) {
log.error("disconnect fail ------->>>{}", e);
}
}
}
}
/**
* 删除指定路径下的指定文件;若文件名为空,则删除指定路径下的全部文件
* @param fileName
* @param pathname
* @return boolean
*/
public boolean deleteFile(String fileName, String pathname) {
if (Objects.isNull(ftpClient)) {
log.error("ftpClient is null!");
return false;
}
if (StringUtils.isEmpty(pathname)) {
log.error("pathname is null!");
return false;
}
try {
boolean changeDir = ftpClient.changeWorkingDirectory(pathname);
if (changeDir) {
FTPFile[] ftpFiles = ftpClient.listFiles();
for (FTPFile ftpFile : ftpFiles) {
if (ftpFile.isDirectory()) {
if (ftpClient.removeDirectory(ftpFile.getName())) {
continue;
} else {
log.error("delete {} fail", ftpFile.getName());
return false;
}
} else if (StringUtils.isEmpty(fileName)) {
// 删除全部文件
if (ftpClient.deleteFile(ftpFile.getName())) {
continue;
} else {
log.error("delete {} fail", ftpFile.getName());
return false;
}
} else if (ftpFile.getName().equals(fileName)) {
return ftpClient.deleteFile(fileName);
}
}
return true;
} else {
log.error("change working directory fail,{}", pathname);
return false;
}
} catch (IOException e) {
log.error("delete file fail,{}", e);
return false;
}
}
}
5、测试类
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.InputStream;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.summer.App;
import com.summer.ftp.FtpService;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = App.class)
public class FtpTest {
@Autowired
FtpService ftpService;
@Test
public void uploadFile() {
String filePath = "C:\\Users\\Administrator\\Desktop\\vsftpd.conf";
InputStream inputStream = null;
try {
inputStream = new FileInputStream(new File(filePath));
String originName = "vsftpd.conf";
String remoteDir = "/home/ftpuser/";
ftpService.uploadFile(inputStream, originName, remoteDir);
log.info(">>>>>>>>>文件上传成功!");
} catch (FileNotFoundException e) {
e.printStackTrace();
}
}
@Test
public void download() {
ftpService.downloadFile("backlog.txt", "/home/ftpuser/", "C://");
}
@Test
public void deleteFile() {
String fileName = null;//"vsftpd.conf";
String pathname = "/home/ftpuser/";
boolean deleteFile = ftpService.deleteFile(fileName, pathname );
if(deleteFile) {
log.info(">>>>>>>>删除文件成功!");
}else {
log.error(">>>>>>>>删除文件失败!");
}
}
}