目录
FTP文件服务器
/      

FTP文件服务器

FTP安装

系统:Linux Centos 7

yum install -y vsftpd

添加 test 用户,并禁止ssh登录

useradd -d /home/vsftpd/test -g ftp -s /sbin/nologin test

更改密码

passwd test

修改 /etc/vsftpd/vsftpd.conf

chroot_local_user=YES
#chroot_list_enable=YES
# (default follows)
#chroot_list_file=/etc/vsftpd/chroot_list
#
# You may activate the "-R" option to the builtin ls. This is disabled by
# default to avoid remote users being able to cause excessive I/O on large
# sites. However, some broken FTP clients such as "ncftp" and "mirror" assume
# the presence of the "-R" option, so there is a strong case for enabling it.
#ls_recurse_enable=YES
#
# When "listen" directive is enabled, vsftpd runs in standalone mode and
# listens on IPv4 sockets. This directive cannot be used in conjunction
# with the listen_ipv6 directive.
listen=NO
#
# This directive enables listening on IPv6 sockets. By default, listening
# on the IPv6 "any" address (::) will accept connections from both IPv6
# and IPv4 clients. It is not necessary to listen on *both* IPv4 and IPv6
# sockets. If you want that (perhaps because you want to listen on specific
# addresses) then you must run two copies of vsftpd with two configuration
# files.
# Make sure, that one of the listen options is commented !!
listen_ipv6=YES

pam_service_name=vsftpd
userlist_enable=YES
tcp_wrappers=YES

chroot_list_enable=YES
chroot_list_file=/etc/vsftpd/chroot_list

创建文件 /etc/vsftpd/chroot_list, 内容为test

修改 /etc/pam.d/vsftpd 文件

#%PAM-1.0
session    optional     pam_keyinit.so    force revoke
auth       required     pam_listfile.so item=user sense=deny file=/etc/vsftpd/ftpusers onerr=succeed
auth       required     pam_nologin.so
auth       include      password-auth
account    include      password-auth
session    required     pam_loginuid.so
session    include      password-auth

auth required pam_shells.so修改为->auth required pam_nologin.so 或者将 auth required pam_shells.so注释

然后重启

文章地址:https://blog.csdn.net/wqh0830/article/details/87795709

FTP服务器的两种工作模式

不管哪种模式,都必须通过21这个端口建立起FTP的管道连接,通过这个通道发送命令

发起连接的 是客户端,所以就是 客户端的21端口可以是关闭状态的,但是服务端的21端口一定要开着。

通过21端口可以创建文件夹,但不能创建文件

遇到的问题是 可以创建文件,但是不能写入文件

视频笔记

FTP:文件传输协议,20 、21端口

两个TCP连接,一个控制层面的连接,一个数据层面的连接

  • 被动模式(第二信道的TCP连接时由客户端主动发起的)

    客户端主动找服务器建立第一信道,目的端口21

    服务器给客户端发一个数据包(PASV命令),IP地址,x,y ,意思就是告诉客户端,这是的IP地址,我开放的端口为 x * 256 + y

    客户端要主动发起第二信道的TCP连接,通过上一步得到的IP地址,和 计算出来的端口号

  • 主动模式

    • 第一个TCP连接 【第一信道】

      客户端主动发起,和FTP服务器建立通信,目的端口 21

      发送PORT命令, 客户端告诉服务器,我在 IP地址, x, y, 端口为 x * 256 + y

      建立第二信道,服务端主动找客户端

主动模式【PORT】

1、通过tcp的21端口建立通道

2、客户端通过此通道发起PORT命令,$\color{red} {并产生一个随机非特殊的端口号N ( 1023 < N < 65536) 给到FTP服务器}$

3、此时客户端监听 N + 1 端口 (N + 1 >= 1025, 不一定是N端口 + 1),同时通过21的通道发送命令通知FTP服务器,客户端通过此端口接收数据传输。

4、FTP服务器接收到上一步的响应后,通过自己的数据源端口20,去连接远程的客户端的 N + 1 端口(此时是FTP服务端主动发起的一个端口连接)

5、如果此时客户端的防火墙开着,并且只开放了21端口,则会造成连接成功, 而无法上传文件,会导致上一步的出现数据端口连接失败

6、如果端口开放正常, FTP客户端会接收到服务端响应并返回信息,则建立起了数据连接通道。
总结:主动模式,是FTP服务器主动连接客户端的数据端口,服务端可以只开放端口21,但是客户端还是保证端口要开放指定范围,或者客户端关闭防火墙
服务端需要开启 20、21 端口

被动模式【PASV】

1、通过tcp的21端口建立起通道

2、与主动方式的FTP不同,客户端不会提交PORT命令并允许服务器回连它的数据端口,而是提交PASV命令,会产生两个随机非特殊的端口 N ( 1023 < N < 65536 ) 和 N + 1 给到FTP服务器。其中N端口跟主动模式一样,$\color {red} {会把N端口给到服务端的远程21端口,相当于FTP服务端被动接受数据端口号而不是之前PORT模式的主动发起连接}$

3、FTP服务端会打开 N + 1 端口号

4、客户端发起 对FTP服务器的 N + 1 端口建立数据连接。

总结:所以要保证FTP服务器开放对应范围端口,或者 FTP服务端 把防火墙关闭
服务端需要开启通信端口、一定范围内的数据端口

Spring Boot 集成 FTP

依赖

<!--        集成ftp-->
        <dependency>
            <groupId>commons-net</groupId>
            <artifactId>commons-net</artifactId>
            <version>3.3</version>
        </dependency>

        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-io</artifactId>
            <version>1.3.2</version>
        </dependency>

工具类

package com.example.springbootftpdemo.utils;

import org.apache.commons.io.FileUtils;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPReply;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.io.*;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * <h2></h2>
 * @author gitsilence
 * date 2021/6/18
 */
@Component
public class FtpUtils {

    private final Logger logger = LoggerFactory.getLogger(FtpUtils.class);

    @Value("${custom.config.file-server.ip}")
    private String FTP_ADDRESS;

    @Value("${custom.config.file-server.port}")
    private Integer FTP_PORT;

    @Value("${custom.config.file-ftp-user}")
    private String FTP_USERNAME;

    @Value("${custom.config.file-ftp-password}")
    private String FTP_PASSWORD;

    @Value("${custom.config.file-savepath}")
    private String FTP_BASEPATH;

    @Value("${custom.config.enterLocalPassiveMode}")
    private String isEnablePassiveMode;

    //根据当前文件生成 文件夹
    private static String getTimePath() {
        Date now = new Date();

        DateFormat format = new SimpleDateFormat("yyyy/MM/dd/");
        return format.format(now);
    }

    public String upload (String filePath) {
        File file = new File(filePath);
        String originName = file.getName();
        logger.info("originName : {}", originName);
        StringBuilder url = new StringBuilder();
        FTPClient ftp = new FTPClient();
        ftp.setControlEncoding("utf-8");

        try {
            int reply;
            ftp.enterLocalPassiveMode();
            ftp.setConnectTimeout(120000);
            ftp.setDataTimeout(120000);
            // 连接FTP服务器
            ftp.connect(FTP_ADDRESS, FTP_PORT);
            // 登录
            ftp.login(FTP_USERNAME, FTP_PASSWORD);
            reply = ftp.getReplyCode();
            // FTPReply.isPositiveCompletion()
            logger.info("reply: {}", reply);
            if (!FTPReply.isPositiveCompletion(reply)) {
                logger.warn("connect failed .... ftp server");
            }
            String timePath = getTimePath();
            String saveDir = FTP_BASEPATH + timePath;
            url.append(saveDir);
            ftp.setFileType(FTPClient.BINARY_FILE_TYPE);
            createDir(ftp, saveDir);
            // originName = System.currentTimeMillis() + originName.substring(originName.lastIndexOf('.'));
            url.append(originName);
            FileInputStream inputStream = FileUtils.openInputStream(file);
            logger.info("parth is : {}", url.toString());

            if ("true".equals(isEnablePassiveMode)) {
                // open passive mode
                ftp.enterLocalPassiveMode();
            }

            boolean b = ftp.storeFile(originName, inputStream);
            logger.info("upload result: {}", (b ? "upload success" : "upload failed "));
            // logout
            ftp.logout();

        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (ftp.isConnected()) {
                try {
                    ftp.disconnect();
                } catch (IOException ioe) {
                    ioe.printStackTrace();
                }
            }
        }
        return url.toString();
    }

    // 创建文件夹,并切换到该文件夹
    // 比如: hello/test
    //最终会切换到test 文件夹返回
    private void createDir(FTPClient client, String path) throws IOException {
        String[] dirs = path.split("/");
        for (String dir : dirs) {
            if (StringUtils.isEmpty(dir)) {
                continue;
            }
            // if not exist, mkdirs
            if (!client.changeWorkingDirectory(dir)) {
                client.makeDirectory(dir);
            }
            client.changeWorkingDirectory(dir);
        }
    }
}

完整代码地址:https://github.com/MrNiebit/springboot-ftp-demo

连接超时问题

对于主动模式,可以尝试关闭 客户端的防火墙

对于被动模式,可以尝试关闭 FTP服务端的防火墙

参考文章

https://www.cnblogs.com/reve-wang/articles/7429460.html


标题:FTP文件服务器
作者:gitsilence
地址:https://blog.lacknb.cn/articles/2021/06/18/1624026653742.html