Linux配置FTP服務器實現(xiàn)文件上傳下載功能
引言
FTP(File Transfer Protocol)作為最古老、最成熟的文件傳輸協(xié)議之一,至今仍在企業(yè)級應用中扮演重要角色。無論是部署靜態(tài)資源、備份數(shù)據(jù)庫,還是在不同系統(tǒng)間同步文件,F(xiàn)TP 服務都是不可或缺的基礎組件。本文將帶你從零開始,在 Linux 系統(tǒng)上搭建完整的 FTP 服務器,并通過 Java 編寫客戶端程序實現(xiàn)自動化上傳與下載功能。
為什么選擇 FTP?
雖然現(xiàn)代云存儲和 HTTP API 已經(jīng)非常流行,但 FTP 依然有其不可替代的優(yōu)勢:
- 簡單穩(wěn)定:協(xié)議成熟,兼容性極強。
- 權限控制靈活:可針對用戶、目錄設置讀寫權限。
- 斷點續(xù)傳支持:大文件傳輸更可靠。
- 廣泛支持:幾乎所有操作系統(tǒng)和編程語言都內置或提供庫支持。
- 低資源占用:輕量級服務,適合嵌入式或老舊設備。
小貼士:如果你需要加密傳輸,建議使用 SFTP 或 FTPS,它們是在 FTP 基礎上增加 SSL/TLS 加密的安全版本。
環(huán)境準備
我們將在一臺標準的 Ubuntu 22.04 LTS 服務器上進行操作。如果你使用的是 CentOS、Debian 或其他發(fā)行版,命令略有不同,我會在文中注明。
最小系統(tǒng)要求:
- 操作系統(tǒng):Ubuntu 22.04 / CentOS 8+
- 內存:≥ 512MB
- 磁盤空間:≥ 2GB(視文件大小而定)
- 網(wǎng)絡:開放 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,我們需要對其進行修改以滿足生產環(huán)境需求。
首先備份原始配置:
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 # 使用被動模式(推薦用于公網(wǎng)訪問) 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 # 限制最大客戶端數(shù) 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
創(chuàng)建專用 FTP 用戶
為了安全起見,不建議直接使用 root 或已有系統(tǒng)用戶進行 FTP 操作。我們創(chuàng)建一個專用用戶:
sudo adduser ftpuser
系統(tǒng)會提示你設置密碼和填寫一些信息(可以一路回車跳過)。接著,為該用戶創(chuàng)建專屬上傳目錄:
sudo mkdir -p /home/ftpuser/uploads sudo chown ftpuser:ftpuser /home/ftpuser/uploads sudo chmod 755 /home/ftpuser
權限說明:
- 755 表示所有者可讀寫執(zhí)行,組和其他人只讀執(zhí)行。
- 如果希望其他用戶也能上傳,可設為 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/。
外網(wǎng)訪問注意事項
如果你希望通過公網(wǎng) IP 訪問 FTP 服務器,請注意:
- 路由器端口轉發(fā):將 21 和被動端口范圍映射到內網(wǎng)服務器。
- 云服務商安全組:如 AWS、阿里云、騰訊云等,需在控制臺放行相應端口。
- 動態(tài) DNS(可選):如果你沒有固定公網(wǎng) IP,可使用 No-IP 或 DynDNS 服務綁定域名。
警告:FTP 協(xié)議本身是明文傳輸,包括用戶名和密碼。強烈建議僅在內網(wǎng)使用,或升級為 FTPS/SFTP。
FTP 工作模式圖解
FTP 有兩種工作模式:主動模式(Active)和被動模式(Passive)。理解它們對防火墻配置至關重要。

圖表解讀:
- 主動模式中,服務器主動連接客戶端的數(shù)據(jù)端口 —— 對客戶端防火墻不友好。
- 被動模式中,客戶端連接服務器的數(shù)據(jù)端口 —— 更適合現(xiàn)代網(wǎng)絡環(huán)境。
- 我們前面配置的就是被動模式。
Java 實現(xiàn) FTP 客戶端 —— Apache Commons Net
現(xiàn)在進入重頭戲 —— 用 Java 編寫 FTP 客戶端程序,實現(xiàn)自動上傳、下載、列出目錄等功能。
第一步:添加 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 是一個強大的網(wǎng)絡協(xié)議庫,支持 FTP、SMTP、POP3、Telnet 等多種協(xié)議。官方文檔詳見:Apache Commons Net
Java 代碼實戰(zhàn) —— 基礎上傳下載
下面是一個完整的 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("上傳過程中發(fā)生異常:" + 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("下載過程中發(fā)生異常:" + 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());
}
}
/**
* 創(chuàng)建遠程目錄
*/
public boolean makeDirectory(String dirPath) {
try {
boolean success = ftpClient.makeDirectory(dirPath);
if (success) {
System.out.println("? 目錄創(chuàng)建成功: " + dirPath);
return true;
} else {
System.err.println("? 目錄創(chuàng)建失敗: " + dirPath);
return false;
}
} catch (IOException e) {
System.err.println("創(chuàng)建目錄異常:" + 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("斷開連接時發(fā)生異常:" + 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. 創(chuàng)建子目錄
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 ? 目錄創(chuàng)建成功: test-dir ?? 文件上傳成功: test-dir/uploaded-file.txt ?? 當前目錄: test-dir ---------------------------------------- [FILE] uploaded-file.txt (18 bytes) ?? 文件下載成功: /tmp/downloaded-file.txt ?? 已斷開 FTP 連接
高級功能 —— 斷點續(xù)傳與進度監(jiān)控
對于大文件傳輸,斷點續(xù)傳和進度條是剛需。Apache Commons Net 支持這些功能。
斷點續(xù)傳上傳:
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("?? 斷點續(xù)傳完成: " + remoteFileName);
return true;
} else {
System.err.println("? 斷點續(xù)傳失敗: " + remoteFileName);
return false;
}
} catch (IOException e) {
System.err.println("斷點續(xù)傳異常:" + 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)
異常處理與重試機制
網(wǎng)絡不穩(wěn)定時,F(xiàn)TP 操作可能失敗。我們可以加入重試邏輯:
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); // 指數(shù)退避
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
}
System.err.println("? 達到最大重試次數(shù),放棄上傳");
return false;
}性能優(yōu)化建議
- 連接池:頻繁創(chuàng)建/銷毀連接開銷大,建議使用連接池(如 Apache Commons Pool)。
- 批量操作:盡量減少交互次數(shù),比如一次列出多個文件而不是逐個查詢。
- 壓縮傳輸:對文本文件啟用壓縮(需服務器支持)。
- 并發(fā)上傳:使用多線程同時上傳多個文件(注意服務器并發(fā)限制)。
安全加固建議
雖然我們已經(jīng)做了基礎安全配置,但仍可進一步加固:
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. 限制用戶權限
創(chuàng)建 /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 腳本定時備份網(wǎng)站文件到 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" # 創(chuàng)建備份 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點執(zhí)行 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 755或chown allow_writeable_chroot=YES未設置- SELinux 阻止(CentOS)→
setsebool -P ftp_home_dir on
問題3:連接超時或卡住
- 防火墻未開放被動模式端口
- 客戶端未啟用被動模式 →
enterLocalPassiveMode() - 網(wǎng)絡中間有 NAT/代理 → 嘗試主動模式(不推薦)
問題4:中文文件名亂碼
- 服務器和客戶端編碼不一致 → 統(tǒng)一使用 UTF-8
- 在 Java 中設置:
ftpClient.setControlEncoding("UTF-8") - 在 vsftpd.conf 中設置:
utf8_filesystem=YES
總結
通過本文,你已經(jīng)掌握了:
? 在 Linux 上安裝配置 vsftpd
? 創(chuàng)建安全的 FTP 用戶和目錄結構
? 配置防火墻和被動模式
? 使用 Java 編寫功能完整的 FTP 客戶端
? 實現(xiàn)斷點續(xù)傳、進度監(jiān)控、自動重試
? 安全加固與性能優(yōu)化技巧
FTP 雖然“古老”,但在自動化部署、文件同步、備份歸檔等場景中依然高效可靠。結合 Java 的強大生態(tài),你可以輕松構建企業(yè)級文件傳輸解決方案。
最后提醒
生產環(huán)境中請務必使用 FTPS 或 SFTP 替代明文 FTP,保護你的數(shù)據(jù)安全!
以上就是Linux配置FTP服務器實現(xiàn)文件上傳下載功能的詳細內容,更多關于Linux配置FTP文件上傳下載的資料請關注腳本之家其它相關文章!
相關文章
Linux使用vmstat監(jiān)控系統(tǒng)性能的示例方法
vmstat命令是最常見的Linux/Unix監(jiān)控工具,可以展現(xiàn)給定時間間隔的服務器的狀態(tài)值,包括服務器的CPU使用率,內存使用,虛擬內存交換情況,IO讀寫情況,本文給大家介紹了Linux使用vmstat監(jiān)控系統(tǒng)性能的示例方法,需要的朋友可以參考下2025-03-03

