当前位置: 代码网 > 服务器>服务器>Linux > Linux配置FTP服务器实现文件上传下载功能

Linux配置FTP服务器实现文件上传下载功能

2026年04月23日 Linux 我要评论
引言ftp(file transfer protocol)作为最古老、最成熟的文件传输协议之一,至今仍在企业级应用中扮演重要角色。无论是部署静态资源、备份数据库,还是在不同系统间同步文件,ftp 服务

引言

ftp(file transfer protocol)作为最古老、最成熟的文件传输协议之一,至今仍在企业级应用中扮演重要角色。无论是部署静态资源、备份数据库,还是在不同系统间同步文件,ftp 服务都是不可或缺的基础组件。本文将带你从零开始,在 linux 系统上搭建完整的 ftp 服务器,并通过 java 编写客户端程序实现自动化上传与下载功能。

为什么选择 ftp?

虽然现代云存储和 http api 已经非常流行,但 ftp 依然有其不可替代的优势:

  • 简单稳定:协议成熟,兼容性极强。
  • 权限控制灵活:可针对用户、目录设置读写权限。
  • 断点续传支持:大文件传输更可靠。
  • 广泛支持:几乎所有操作系统和编程语言都内置或提供库支持。
  • 低资源占用:轻量级服务,适合嵌入式或老旧设备。

小贴士:如果你需要加密传输,建议使用 sftp 或 ftps,它们是在 ftp 基础上增加 ssl/tls 加密的安全版本。

环境准备

我们将在一台标准的 ubuntu 22.04 lts 服务器上进行操作。如果你使用的是 centos、debian 或其他发行版,命令略有不同,我会在文中注明。

最小系统要求:

  • 操作系统:ubuntu 22.04 / centos 8+
  • 内存:≥ 512mb
  • 磁盘空间:≥ 2gb(视文件大小而定)
  • 网络:开放 20, 21, 及被动模式端口范围(如 49152–65534)

安装 vsftpd —— 非常安全的 ftp 服务器

vsftpd(very secure ftp daemon)是 linux 上最受欢迎的 ftp 服务器软件之一,以其高性能和安全性著称。

sudo apt update
sudo apt install vsftpd -y

如果是 centos/rhel:

sudo yum install vsftpd -y
# 或者在较新版本中:
sudo dnf install vsftpd -y

安装完成后,启动并设置开机自启:

sudo systemctl start vsftpd
sudo systemctl enable vsftpd
sudo systemctl status vsftpd

你应该看到类似如下输出:

● vsftpd.service - vsftpd ftp server
     loaded: loaded (/lib/systemd/system/vsftpd.service; enabled; vendor preset: enabled)
     active: active (running) since mon 2024-06-17 10:00:00 utc; 5s ago

配置 vsftpd —— 安全第一!

默认配置位于 /etc/vsftpd.conf,我们需要对其进行修改以满足生产环境需求。

首先备份原始配置:

sudo cp /etc/vsftpd.conf /etc/vsftpd.conf.bak

然后编辑配置文件:

sudo nano /etc/vsftpd.conf

推荐配置内容如下:

# 禁用匿名登录
anonymous_enable=no
# 启用本地用户登录
local_enable=yes
# 允许本地用户写入
write_enable=yes
# 限制用户只能访问自己的主目录
chroot_local_user=yes
# 允许 chroot 目录可写(重要!否则无法上传)
allow_writeable_chroot=yes
# 使用被动模式(推荐用于公网访问)
pasv_enable=yes
pasv_min_port=49152
pasv_max_port=65534
# 启用日志记录
xferlog_enable=yes
xferlog_file=/var/log/vsftpd.log
xferlog_std_format=yes
# 设置连接超时时间(秒)
idle_session_timeout=600
data_connection_timeout=120
# 限制最大客户端数
max_clients=50
max_per_ip=5
# 拒绝某些用户登录(可选)
# userlist_enable=yes
# userlist_file=/etc/vsftpd.userlist
# userlist_deny=yes
# 启用 utf-8 编码
utf8_filesystem=yes

注意:allow_writeable_chroot=yes 是关键配置。如果没有它,即使你开启了 write_enable,用户也无法在被 chroot 的目录中上传文件。

保存后重启服务:

sudo systemctl restart vsftpd

创建专用 ftp 用户

为了安全起见,不建议直接使用 root 或已有系统用户进行 ftp 操作。我们创建一个专用用户:

sudo adduser ftpuser

系统会提示你设置密码和填写一些信息(可以一路回车跳过)。接着,为该用户创建专属上传目录:

sudo mkdir -p /home/ftpuser/uploads
sudo chown ftpuser:ftpuser /home/ftpuser/uploads
sudo chmod 755 /home/ftpuser

权限说明:

  • 755 表示所有者可读写执行,组和其他人只读执行。
  • 如果希望其他用户也能上传,可设为 775,但需谨慎。

防火墙配置

ubuntu 默认使用 ufw,centos 使用 firewalld。确保开放 ftp 端口:

ubuntu:

sudo ufw allow 20/tcp
sudo ufw allow 21/tcp
sudo ufw allow 49152:65534/tcp
sudo ufw reload

centos:

sudo firewall-cmd --permanent --add-port=20-21/tcp
sudo firewall-cmd --permanent --add-port=49152-65534/tcp
sudo firewall-cmd --reload

测试 ftp 连接

我们可以使用命令行工具 ftp 或图形化工具 filezilla 进行测试。

使用命令行测试:

ftp localhost

输入用户名 ftpuser 和密码,如果成功登录,你会看到:

connected to localhost.
220 (vsftpd 3.0.3)
name (localhost:yourname): ftpuser
331 please specify the password.
password:
230 login successful.
remote system type is unix.
using binary mode to transfer files.
ftp>

尝试上传一个测试文件:

echo "hello ftp server!" > test.txt
ftp> put test.txt
ftp> ls
ftp> quit

如果一切顺利,文件应已上传至 /home/ftpuser/uploads/

外网访问注意事项

如果你希望通过公网 ip 访问 ftp 服务器,请注意:

  1. 路由器端口转发:将 21 和被动端口范围映射到内网服务器。
  2. 云服务商安全组:如 aws、阿里云、腾讯云等,需在控制台放行相应端口。
  3. 动态 dns(可选):如果你没有固定公网 ip,可使用 no-ip 或 dyndns 服务绑定域名。

警告:ftp 协议本身是明文传输,包括用户名和密码。强烈建议仅在内网使用,或升级为 ftps/sftp。

ftp 工作模式图解

ftp 有两种工作模式:主动模式(active)和被动模式(passive)。理解它们对防火墙配置至关重要。

图表解读:

  • 主动模式中,服务器主动连接客户端的数据端口 —— 对客户端防火墙不友好。
  • 被动模式中,客户端连接服务器的数据端口 —— 更适合现代网络环境。
  • 我们前面配置的就是被动模式。

java 实现 ftp 客户端 —— apache commons net

现在进入重头戏 —— 用 java 编写 ftp 客户端程序,实现自动上传、下载、列出目录等功能。

第一步:添加 maven 依赖

在你的 pom.xml 中加入:

<dependency>
    <groupid>commons-net</groupid>
    <artifactid>commons-net</artifactid>
    <version>3.9.0</version>
</dependency>

如果你使用 gradle:

implementation 'commons-net:commons-net:3.9.0'

pache commons net 是一个强大的网络协议库,支持 ftp、smtp、pop3、telnet 等多种协议。官方文档详见:apache commons net

java 代码实战 —— 基础上传下载

下面是一个完整的 java 类,封装了 ftp 连接、上传、下载、断开等操作。

import org.apache.commons.net.ftp.ftp;
import org.apache.commons.net.ftp.ftpclient;
import org.apache.commons.net.ftp.ftpfile;
import org.apache.commons.net.ftp.ftpreply;
import java.io.*;
import java.nio.charset.standardcharsets;
public class ftpuploader {
    private string server;
    private int port;
    private string username;
    private string password;
    private ftpclient ftpclient;
    public ftpuploader(string server, int port, string username, string password) {
        this.server = server;
        this.port = port;
        this.username = username;
        this.password = password;
        this.ftpclient = new ftpclient();
    }
    /**
     * 连接到 ftp 服务器
     */
    public boolean connect() {
        try {
            ftpclient.connect(server, port);
            int replycode = ftpclient.getreplycode();
            if (!ftpreply.ispositivecompletion(replycode)) {
                system.err.println("连接失败,服务器返回码:" + replycode);
                return false;
            }
            boolean loggedin = ftpclient.login(username, password);
            if (!loggedin) {
                system.err.println("登录失败,用户名或密码错误");
                return false;
            }
            // 设置被动模式
            ftpclient.enterlocalpassivemode();
            // 设置二进制传输模式(推荐用于所有文件)
            ftpclient.setfiletype(ftp.binary_file_type);
            // 设置编码(避免中文乱码)
            ftpclient.setcontrolencoding(standardcharsets.utf_8.name());
            system.out.println("✅ 成功连接到 ftp 服务器: " + server);
            return true;
        } catch (ioexception e) {
            system.err.println("连接异常:" + e.getmessage());
            return false;
        }
    }
    /**
     * 上传文件
     */
    public boolean uploadfile(string localfilepath, string remotefilename) {
        file localfile = new file(localfilepath);
        if (!localfile.exists()) {
            system.err.println("本地文件不存在:" + localfilepath);
            return false;
        }
        try (inputstream inputstream = new fileinputstream(localfile)) {
            boolean done = ftpclient.storefile(remotefilename, inputstream);
            if (done) {
                system.out.println("📤 文件上传成功: " + remotefilename);
                return true;
            } else {
                system.err.println("❌ 文件上传失败: " + remotefilename);
                return false;
            }
        } catch (ioexception e) {
            system.err.println("上传过程中发生异常:" + e.getmessage());
            return false;
        }
    }
    /**
     * 下载文件
     */
    public boolean downloadfile(string remotefilename, string localfilepath) {
        try (outputstream outputstream = new fileoutputstream(localfilepath)) {
            boolean done = ftpclient.retrievefile(remotefilename, outputstream);
            if (done) {
                system.out.println("📥 文件下载成功: " + localfilepath);
                return true;
            } else {
                system.err.println("❌ 文件下载失败: " + remotefilename);
                return false;
            }
        } catch (ioexception e) {
            system.err.println("下载过程中发生异常:" + e.getmessage());
            return false;
        }
    }
    /**
     * 列出远程目录内容
     */
    public void listfiles(string remotedir) {
        try {
            ftpclient.changeworkingdirectory(remotedir);
            ftpfile[] files = ftpclient.listfiles();
            system.out.println("📁 当前目录: " + remotedir);
            system.out.println("----------------------------------------");
            for (ftpfile file : files) {
                string fileinfo = file.isdirectory() ? "[dir] " : "[file]";
                fileinfo += " " + file.getname() + " (" + file.getsize() + " bytes)";
                system.out.println(fileinfo);
            }
        } catch (ioexception e) {
            system.err.println("列出文件失败:" + e.getmessage());
        }
    }
    /**
     * 创建远程目录
     */
    public boolean makedirectory(string dirpath) {
        try {
            boolean success = ftpclient.makedirectory(dirpath);
            if (success) {
                system.out.println("✅ 目录创建成功: " + dirpath);
                return true;
            } else {
                system.err.println("❌ 目录创建失败: " + dirpath);
                return false;
            }
        } catch (ioexception e) {
            system.err.println("创建目录异常:" + e.getmessage());
            return false;
        }
    }
    /**
     * 删除远程文件
     */
    public boolean deletefile(string filename) {
        try {
            boolean success = ftpclient.deletefile(filename);
            if (success) {
                system.out.println("🗑️  文件删除成功: " + filename);
                return true;
            } else {
                system.err.println("❌ 文件删除失败: " + filename);
                return false;
            }
        } catch (ioexception e) {
            system.err.println("删除文件异常:" + e.getmessage());
            return false;
        }
    }
    /**
     * 断开连接
     */
    public void disconnect() {
        if (ftpclient.isconnected()) {
            try {
                ftpclient.logout();
                ftpclient.disconnect();
                system.out.println("🔌 已断开 ftp 连接");
            } catch (ioexception e) {
                system.err.println("断开连接时发生异常:" + e.getmessage());
            }
        }
    }
}

测试 java 客户端

编写一个简单的测试类来验证功能:

public class ftptest {
    public static void main(string[] args) {
        // 替换为你自己的服务器信息
        string server = "your-server-ip-or-domain";
        int port = 21;
        string username = "ftpuser";
        string password = "your-password";
        ftpuploader uploader = new ftpuploader(server, port, username, password);
        // 1. 连接服务器
        if (!uploader.connect()) {
            system.exit(1);
        }
        // 2. 创建子目录
        uploader.makedirectory("test-dir");
        // 3. 上传文件
        uploader.uploadfile("/path/to/local/file.txt", "test-dir/uploaded-file.txt");
        // 4. 列出目录内容
        uploader.listfiles("test-dir");
        // 5. 下载文件
        uploader.downloadfile("test-dir/uploaded-file.txt", "/tmp/downloaded-file.txt");
        // 6. 删除文件(可选)
        // uploader.deletefile("test-dir/uploaded-file.txt");
        // 7. 断开连接
        uploader.disconnect();
    }
}

运行后,你将看到类似输出:

✅ 成功连接到 ftp 服务器: 192.168.1.100
✅ 目录创建成功: test-dir
📤 文件上传成功: test-dir/uploaded-file.txt
📁 当前目录: test-dir
----------------------------------------
[file] uploaded-file.txt (18 bytes)
📥 文件下载成功: /tmp/downloaded-file.txt
🔌 已断开 ftp 连接

高级功能 —— 断点续传与进度监控

对于大文件传输,断点续传和进度条是刚需。apache commons net 支持这些功能。

断点续传上传:

public boolean uploadwithresume(string localfilepath, string remotefilename) {
    file localfile = new file(localfilepath);
    if (!localfile.exists()) {
        system.err.println("本地文件不存在:" + localfilepath);
        return false;
    }
    try {
        // 获取远程文件大小(如果存在)
        long remotesize = 0;
        ftpfile[] files = ftpclient.listfiles(remotefilename);
        if (files.length > 0) {
            remotesize = files[0].getsize();
        }
        // 如果远程文件大小 >= 本地文件,则无需上传
        if (remotesize >= localfile.length()) {
            system.out.println("✅ 文件已完整上传,跳过:" + remotefilename);
            return true;
        }
        // 打开输入流,从断点位置开始读取
        randomaccessfile raf = new randomaccessfile(localfile, "r");
        raf.seek(remotesize); // 移动到断点位置
        // 告诉服务器从指定位置开始写入
        ftpclient.setrestartoffset(remotesize);
        inputstream inputstream = new fileinputstream(raf.getfd());
        boolean done = ftpclient.storefile(remotefilename, inputstream);
        raf.close();
        inputstream.close();
        if (done) {
            system.out.println("📤 断点续传完成: " + remotefilename);
            return true;
        } else {
            system.err.println("❌ 断点续传失败: " + remotefilename);
            return false;
        }
    } catch (ioexception e) {
        system.err.println("断点续传异常:" + e.getmessage());
        return false;
    }
}

带进度条的上传(使用观察者模式):

import java.util.function.consumer;
public class progressmonitorinputstream extends inputstream {
    private final inputstream inputstream;
    private final long totalbytes;
    private long bytesread = 0;
    private final consumer<long> progresscallback;
    public progressmonitorinputstream(inputstream inputstream, long totalbytes, consumer<long> progresscallback) {
        this.inputstream = inputstream;
        this.totalbytes = totalbytes;
        this.progresscallback = progresscallback;
    }
    @override
    public int read() throws ioexception {
        int b = inputstream.read();
        if (b != -1) {
            bytesread++;
            progresscallback.accept(bytesread);
        }
        return b;
    }
    @override
    public int read(byte[] b, int off, int len) throws ioexception {
        int result = inputstream.read(b, off, len);
        if (result != -1) {
            bytesread += result;
            progresscallback.accept(bytesread);
        }
        return result;
    }
    @override
    public void close() throws ioexception {
        inputstream.close();
    }
}

然后在上传方法中使用:

public boolean uploadwithprogress(string localfilepath, string remotefilename) {
    file localfile = new file(localfilepath);
    if (!localfile.exists()) {
        system.err.println("本地文件不存在:" + localfilepath);
        return false;
    }
    try (fileinputstream fis = new fileinputstream(localfile)) {
        progressmonitorinputstream pmis = new progressmonitorinputstream(
            fis,
            localfile.length(),
            current -> {
                double percent = (double) current / localfile.length() * 100;
                system.out.printf("\r📤 上传进度: %.2f%% (%d/%d bytes)", percent, current, localfile.length());
            }
        );
        boolean done = ftpclient.storefile(remotefilename, pmis);
        system.out.println(); // 换行
        if (done) {
            system.out.println("✅ 上传完成: " + remotefilename);
            return true;
        } else {
            system.err.println("❌ 上传失败: " + remotefilename);
            return false;
        }
    } catch (ioexception e) {
        system.err.println("上传异常:" + e.getmessage());
        return false;
    }
}

调用示例:

uploader.uploadwithprogress("/large/video.mp4", "videos/video.mp4");

输出效果:

📤 上传进度: 47.32% (496210944/1048576000 bytes)

异常处理与重试机制

网络不稳定时,ftp 操作可能失败。我们可以加入重试逻辑:

public boolean uploadwithretry(string localfilepath, string remotefilename, int maxretries) {
    for (int attempt = 1; attempt <= maxretries; attempt++) {
        system.out.println("🔄 尝试第 " + attempt + " 次上传...");
        if (uploadfile(localfilepath, remotefilename)) {
            return true;
        }
        if (attempt < maxretries) {
            system.out.println("⏳ " + (5 * attempt) + " 秒后重试...");
            try {
                thread.sleep(5000 * attempt); // 指数退避
            } catch (interruptedexception e) {
                thread.currentthread().interrupt();
                break;
            }
        }
    }
    system.err.println("❌ 达到最大重试次数,放弃上传");
    return false;
}

性能优化建议

  1. 连接池:频繁创建/销毁连接开销大,建议使用连接池(如 apache commons pool)。
  2. 批量操作:尽量减少交互次数,比如一次列出多个文件而不是逐个查询。
  3. 压缩传输:对文本文件启用压缩(需服务器支持)。
  4. 并发上传:使用多线程同时上传多个文件(注意服务器并发限制)。

安全加固建议

虽然我们已经做了基础安全配置,但仍可进一步加固:

1. 使用 ftps(ftp over ssl)

修改 /etc/vsftpd.conf

ssl_enable=yes
rsa_cert_file=/etc/ssl/certs/ssl-cert-snakeoil.pem
rsa_private_key_file=/etc/ssl/private/ssl-cert-snakeoil.key
allow_anon_ssl=no
force_local_data_ssl=yes
force_local_logins_ssl=yes
ssl_tlsv1=yes
ssl_sslv2=no
ssl_sslv3=no

java 客户端需改用 ftpsclient

import org.apache.commons.net.ftp.ftpsclient;
// ...
this.ftpclient = new ftpsclient(true); // true 表示显式 ssl
((ftpsclient) ftpclient).execpbsz(0);
((ftpsclient) ftpclient).execprot("p");

2. 限制用户权限

创建 /etc/vsftpd.userlist 并添加允许登录的用户:

echo "ftpuser" | sudo tee -a /etc/vsftpd.userlist

然后在配置中启用:

userlist_enable=yes
userlist_file=/etc/vsftpd.userlist
userlist_deny=no  # 只允许列表中的用户登录

3. 启用日志审计

确保日志路径存在并定期轮转:

sudo touch /var/log/vsftpd.log
sudo chmod 644 /var/log/vsftpd.log

配置 logrotate(/etc/logrotate.d/vsftpd):

/var/log/vsftpd.log {
    weekly
    missingok
    rotate 12
    compress
    delaycompress
    notifempty
    create 644 root root
}

自动化脚本示例

你可以编写 shell 脚本定时备份网站文件到 ftp:

#!/bin/bash
# backup-to-ftp.sh
date=$(date +%y%m%d_%h%m%s)
backup_dir="/backup"
web_root="/var/www/html"
ftp_server="your.ftp.server"
ftp_user="ftpuser"
ftp_pass="password"
# 创建备份
tar -czf "$backup_dir/website_$date.tar.gz" -c /var/www html
# 上传到 ftp
ftp -n <<eof
open $ftp_server
user $ftp_user $ftp_pass
binary
put $backup_dir/website_$date.tar.gz
bye
eof
# 清理本地7天前的备份
find $backup_dir -name "website_*.tar.gz" -mtime +7 -delete
echo "✅ 备份完成: website_$date.tar.gz"

添加到 crontab:

crontab -e
# 每天凌晨2点执行
0 2 * * * /path/to/backup-to-ftp.sh

🆘 常见问题排查

问题1:530 login incorrect

  • 用户名或密码错误
  • 用户 shell 被设为 /usr/sbin/nologin → 改为 /bin/bash
  • 检查 /etc/shells 是否包含用户的 shell

问题2:550 permission denied

  • 目标目录无写权限 → chmod 755chown
  • allow_writeable_chroot=yes 未设置
  • selinux 阻止(centos)→ setsebool -p ftp_home_dir on

问题3:连接超时或卡住

  • 防火墙未开放被动模式端口
  • 客户端未启用被动模式 → enterlocalpassivemode()
  • 网络中间有 nat/代理 → 尝试主动模式(不推荐)

问题4:中文文件名乱码

  • 服务器和客户端编码不一致 → 统一使用 utf-8
  • 在 java 中设置:ftpclient.setcontrolencoding("utf-8")
  • 在 vsftpd.conf 中设置:utf8_filesystem=yes

总结

通过本文,你已经掌握了:

✅ 在 linux 上安装配置 vsftpd
✅ 创建安全的 ftp 用户和目录结构
✅ 配置防火墙和被动模式
✅ 使用 java 编写功能完整的 ftp 客户端
✅ 实现断点续传、进度监控、自动重试
✅ 安全加固与性能优化技巧

ftp 虽然“古老”,但在自动化部署、文件同步、备份归档等场景中依然高效可靠。结合 java 的强大生态,你可以轻松构建企业级文件传输解决方案。

最后提醒

生产环境中请务必使用 ftpssftp 替代明文 ftp,保护你的数据安全!

以上就是linux配置ftp服务器实现文件上传下载功能的详细内容,更多关于linux配置ftp文件上传下载的资料请关注代码网其它相关文章!

(0)

相关文章:

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论

验证码:
Copyright © 2017-2026  代码网 保留所有权利. 粤ICP备2024248653号
站长QQ:2386932994 | 联系邮箱:2386932994@qq.com