ubuntu16.04 OS搭建FTP服务器

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服务器出错时,建议您清除浏览器缓存后再尝试。

  1. 打开客户端的Google Chrome浏览器。
  2. 在地址栏中输入ftp://<FTP服务器公网IP地址>:21

    本教程中为Linux实例的公网IP地址。

  3. 在弹出的对话框中,输入用户名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(">>>>>>>>删除文件失败!");
        }
 }
}

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值