07-1. FTP 協定:檔案傳輸協定

深入理解 FTP 主動被動模式與檔案傳輸原理

📁 FTP 協定:檔案傳輸協定

🎯 什麼是 FTP?

💡 比喻:網路硬碟
像 USB 隨身碟一樣,可以在電腦之間傳輸檔案
但透過網路,不需要實體接觸

FTP(File Transfer Protocol) 是一種用於在網路上傳輸檔案的標準協定,誕生於 1971 年,是網際網路最古老的協定之一。

為什麼需要 FTP?

HTTP vs FTP:

特性HTTPFTP
主要用途網頁瀏覽檔案傳輸
連線方式單一連線雙連線(控制+數據)
續傳⚠️ 需特殊處理✅ 內建支援
目錄瀏覽❌ 無✅ 有
權限管理⚠️ 有限✅ 完整
適用場景下載軟體、瀏覽網頁網站部署、備份

FTP 使用場景:

  • 🌐 網站部署(上傳 HTML/CSS/JS)
  • 💾 大檔案傳輸(影片、備份檔)
  • 📂 遠端檔案管理
  • 🔄 檔案同步

🏗️ FTP/SFTP 在網路模型中的位置

OSI 7 層模型

┌──────────────────────────────┬─────────────────┐
│ 7. Application Layer (應用層) │  FTP, SFTP      │ ← FTP/SFTP 在這裡
├──────────────────────────────┼─────────────────┤
│ 6. Presentation Layer (表示層)│  加密、壓縮      │
├──────────────────────────────┼─────────────────┤
│ 5. Session Layer (會話層)     │  建立、維護會話  │
├──────────────────────────────┼─────────────────┤
│ 4. Transport Layer (傳輸層)   │  TCP            │
├──────────────────────────────┼─────────────────┤
│ 3. Network Layer (網路層)     │  IP             │
├──────────────────────────────┼─────────────────┤
│ 2. Data Link Layer (資料鏈結層)│  Ethernet       │
├──────────────────────────────┼─────────────────┤
│ 1. Physical Layer (實體層)    │  網路線、光纖    │
└──────────────────────────────┴─────────────────┘

FTP/SFTP 位於第 7 層(應用層)

  • FTP 和 SFTP 都是應用層協定
  • 提供檔案傳輸服務
  • 支援上傳、下載、刪除等檔案操作

TCP/IP 4 層模型

┌─────────────────────────────┬─────────────────┐
│ 4. Application Layer (應用層) │  FTP, SFTP      │ ← FTP/SFTP 在這裡
├─────────────────────────────┼─────────────────┤
│ 3. Transport Layer (傳輸層)  │  TCP            │
├─────────────────────────────┼─────────────────┤
│ 2. Internet Layer (網際網路層)│  IP             │
├─────────────────────────────┼─────────────────┤
│ 1. Network Access (網路存取層)│  Ethernet       │
└─────────────────────────────┴─────────────────┘

FTP/SFTP 位於第 4 層(應用層)

  • 在 TCP/IP 模型中,都是應用層協定
  • 使用 TCP 作為傳輸層協定
  • TCP 提供可靠的檔案傳輸

檔案傳輸協定對比:

協定Port連線數加密安全性功能
FTP21 + 202 個❌ 明文⚠️ 不安全完整檔案管理
FTPS990 或 212 個✅ SSL/TLS✅ 安全完整檔案管理
SFTP221 個✅ SSH✅ 安全完整檔案管理
SCP221 個✅ SSH✅ 安全簡單複製

重點:

  • FTP 使用 TCP,有兩個連線
    • 控制連線(Port 21):傳送命令
    • 數據連線(Port 20 主動 / 隨機 port 被動):傳輸檔案

各協定特色:

FTP(File Transfer Protocol)- 1971 年:
- 明文傳輸(帳號、密碼、檔案內容都可被竊聽)
- 雙連線設計(控制 + 數據)
- 防火牆不友善(主動模式需開 Port 20)
- ⚠️ 不安全,僅建議內網使用

FTPS(FTP over SSL/TLS):
- FTP + SSL/TLS 加密
- Port 990(隱式)或 21(顯式)
- 仍是雙連線
- 比 FTP 安全,但設定較複雜

SFTP(SSH File Transfer Protocol):
- 完全不同於 FTP!基於 SSH
- 加密傳輸(SSH 通道)
- 單一連線(Port 22)
- ✅ 現代標準,推薦使用
- 詳見下一篇:07-2. SFTP 協定

SCP(Secure Copy Protocol):
- 基於 SSH(Port 22)
- 更簡單,只做「複製」
- 不支援續傳、列出目錄
- 詳見:07-3. SCP 協定

為什麼 FTP 使用 TCP?

檔案傳輸的需求:
1. 可靠性 ✅
   檔案必須完整無誤
   TCP 提供錯誤檢測和重傳

2. 順序性 ✅
   檔案內容必須按順序到達
   TCP 保證封包順序

3. 流量控制 ✅
   大檔案傳輸需要流量控制
   TCP 提供視窗機制

UDP 不適合:
❌ 不可靠(可能丟包)
❌ 無順序保證
❌ 檔案可能損壞

🏗️ FTP 架構

雙連線設計

💡 特色:FTP 使用「兩個」連線
客戶端                             FTP 伺服器
  │                                     │
  ├─ 控制連線(Port 21)──────────────>│  發送命令
  │  USER, PASS, LIST, RETR, STOR      │
  │                                     │
  │<─ 數據連線(Port 20 或動態)────────┤  傳輸檔案
  │  實際檔案內容                        │

控制連線(Control Connection):

  • Port 21
  • 持續保持
  • 傳送命令(LIST, GET, PUT)
  • 回應狀態碼

數據連線(Data Connection):

  • Port 20(主動模式)或動態 Port(被動模式)
  • 臨時建立
  • 傳輸實際檔案內容
  • 傳輸完成後關閉

🔀 主動模式 vs 被動模式

主動模式(Active Mode)

💡 比喻:伺服器主動打電話給你
客戶端 (192.168.1.100)           FTP 伺服器 (ftp.example.com)
  │                                     │
  ├─ PORT 192.168.1.100,1234 ────────>│  告訴伺服器「請連線到我的 1234 port」
  │  (控制連線 Port 21)                 │
  │                                     │
  │<─ 200 OK ─────────────────────────┤
  │                                     │
  │<─ 數據連線(Port 20 → 1234)───────┤  伺服器主動連線到客戶端
  │  傳輸檔案                            │

問題:NAT/防火牆

客戶端(內網)         NAT          FTP 伺服器
192.168.1.100                      ftp.example.com
  │                    │                 │
  ├─ PORT 命令 ───────>├────────────────>│
  │  (告訴伺服器連到 192.168.1.100:1234) │
  │                    │                 │
  │                    │<─ 嘗試連線 ──────┤
  │                    │  到 192.168.1.100:1234
  │                    │  ❌ 失敗(內網 IP)
  │                    │                 │
  │<─ 連線失敗 ─────────┤                 │

問題:
1. 伺服器無法連到內網 IP (192.168.1.100)
2. 防火牆阻擋入站連線

被動模式(Passive Mode)

💡 比喻:客戶端主動打電話給伺服器
解決 NAT/防火牆問題
客戶端 (192.168.1.100)           FTP 伺服器 (ftp.example.com)
  │                                     │
  ├─ PASV ────────────────────────────>│  請求被動模式
  │  (控制連線 Port 21)                 │
  │                                     │
  │<─ 227 (192,168,2,10,200,100) ──────┤  告訴客戶端「請連線到我的 51300 port」
  │   (192.168.2.10:51300)              │  (200*256 + 100 = 51300)
  │                                     │
  ├─ 數據連線(→ 51300)──────────────>│  客戶端主動連線到伺服器
  │  傳輸檔案                            │

Port 計算:

227 Entering Passive Mode (192,168,2,10,200,100)
                           │  │  │ │  │   │
                           IP位址    Port

IP: 192.168.2.10
Port: 200 * 256 + 100 = 51300

解決 NAT 問題:

客戶端(內網)         NAT          FTP 伺服器
192.168.1.100    203.0.113.5     ftp.example.com
  │                    │                 │
  ├─ PASV ────────────>├────────────────>│
  │                    │<─ 227 ───────────┤
  │                    │  伺服器 Port: 51300
  │                    │                 │
  ├─ 連線到 51300 ────>├────────────────>│
  │  ✅ 成功(出站連線) │                 │

🔧 FTP 命令

控制命令

USER <username>    - 使用者名稱
PASS <password>    - 密碼
PWD                - 顯示當前目錄
CWD <directory>    - 切換目錄
LIST               - 列出檔案
RETR <filename>    - 下載檔案
STOR <filename>    - 上傳檔案
DELE <filename>    - 刪除檔案
MKD <directory>    - 建立目錄
RMD <directory>    - 刪除目錄
QUIT               - 結束連線

FTP 會話範例

C: USER alice
S: 331 Password required for alice

C: PASS secret123
S: 230 User alice logged in

C: PWD
S: 257 "/" is current directory

C: LIST
S: 150 Opening data connection
S: -rw-r--r-- 1 alice users 1234 Jan 15 10:00 file.txt
S: drwxr-xr-x 2 alice users 4096 Jan 14 09:00 folder
S: 226 Transfer complete

C: CWD folder
S: 250 CWD command successful

C: RETR document.pdf
S: 150 Opening data connection (12345 bytes)
S: (傳輸 12345 bytes)
S: 226 Transfer complete

C: STOR upload.zip
S: 150 Ready to receive data
C: (傳輸檔案)
S: 226 Transfer complete

C: QUIT
S: 221 Goodbye

💻 Python 使用 FTP

基本連線

from ftplib import FTP

# 連線到 FTP 伺服器
ftp = FTP('ftp.example.com')

# 登入
ftp.login(user='alice', passwd='password')

# 顯示歡迎訊息
print(ftp.getwelcome())

# 顯示當前目錄
print(ftp.pwd())

# 列出檔案
ftp.dir()  # 詳細列表
# 或
files = ftp.nlst()  # 只列出檔案名
for file in files:
    print(file)

# 關閉連線
ftp.quit()

下載檔案

from ftplib import FTP

ftp = FTP('ftp.example.com')
ftp.login('alice', 'password')

# 下載檔案
filename = 'document.pdf'
with open(filename, 'wb') as local_file:
    ftp.retrbinary(f'RETR {filename}', local_file.write)

print(f"{filename} 下載完成")

ftp.quit()

上傳檔案

from ftplib import FTP

ftp = FTP('ftp.example.com')
ftp.login('alice', 'password')

# 上傳檔案
filename = 'upload.zip'
with open(filename, 'rb') as local_file:
    ftp.storbinary(f'STOR {filename}', local_file)

print(f"{filename} 上傳完成")

ftp.quit()

目錄操作

# 切換目錄
ftp.cwd('/public_html')

# 建立目錄
ftp.mkd('new_folder')

# 刪除目錄
ftp.rmd('old_folder')

# 刪除檔案
ftp.delete('file.txt')

# 重新命名
ftp.rename('old_name.txt', 'new_name.txt')

被動模式

from ftplib import FTP

ftp = FTP('ftp.example.com')

# 啟用被動模式(預設)
ftp.set_pasv(True)

# 或使用主動模式
# ftp.set_pasv(False)

ftp.login('alice', 'password')
# ... 其他操作

斷點續傳

import os
from ftplib import FTP

def resume_download(ftp, remote_file, local_file):
    """斷點續傳下載"""

    # 檢查本地檔案大小
    if os.path.exists(local_file):
        local_size = os.path.getsize(local_file)
    else:
        local_size = 0

    # 從中斷處繼續下載
    with open(local_file, 'ab') as f:
        ftp.retrbinary(
            f'RETR {remote_file}',
            f.write,
            rest=local_size  # 從此位置開始
        )

# 使用
ftp = FTP('ftp.example.com')
ftp.login('alice', 'password')
resume_download(ftp, 'large_file.zip', 'local_file.zip')

🎓 常見面試題

Q1:FTP 主動模式和被動模式有什麼不同?

答案:

核心差異:「誰」主動建立數據連線?

主動模式(Active):

伺服器主動連線到客戶端

客戶端:告訴伺服器「我開了 Port 1234,請連過來」
伺服器:好的,我從 Port 20 連到你的 1234

問題:防火牆/NAT 阻擋入站連線

被動模式(Passive):

客戶端主動連線到伺服器

客戶端:請告訴我你的 Port
伺服器:我開了 Port 51300,請連過來
客戶端:好的,我連到你的 51300

優點:解決 NAT/防火牆問題(出站連線)

圖解:

主動模式:
Client ─ PORT 1234 ──> Server (Port 21)
Client <─ Data ─────── Server (Port 20 → 1234) ❌ NAT問題

被動模式:
Client ─ PASV ──────> Server (Port 21)
Client <─ 227 (Port) ─ Server
Client ─ Data ──────> Server (Port 51300) ✅ 解決NAT

記憶技巧:

  • 主動(Active):伺服器像業務員,主動上門(打入站連線)
  • 被動(Passive):伺服器像店面,等客人上門(接出站連線)

Q2:如何提升 FTP 傳輸速度?

答案:

1. 使用被動模式:

# 被動模式通常比主動模式快
ftp.set_pasv(True)

2. 調整緩衝區大小:

# 增大緩衝區(預設 8192 bytes)
ftp.retrbinary('RETR file.zip', local_file.write, blocksize=262144)  # 256 KB

3. 平行下載多個檔案:

from concurrent.futures import ThreadPoolExecutor
from ftplib import FTP

def download_file(filename):
    ftp = FTP('ftp.example.com')
    ftp.login('alice', 'password')
    with open(filename, 'wb') as f:
        ftp.retrbinary(f'RETR {filename}', f.write)
    ftp.quit()

# 平行下載
files = ['file1.zip', 'file2.zip', 'file3.zip']
with ThreadPoolExecutor(max_workers=3) as executor:
    executor.map(download_file, files)

4. 壓縮後傳輸:

import zipfile

# 壓縮檔案
with zipfile.ZipFile('archive.zip', 'w', zipfile.ZIP_DEFLATED) as zf:
    zf.write('large_file1.txt')
    zf.write('large_file2.txt')

# 上傳壓縮檔(比傳輸多個小檔案快)
ftp.storbinary('STOR archive.zip', open('archive.zip', 'rb'))

5. 使用 SFTP 壓縮選項:

ssh.connect(
    hostname='sftp.example.com',
    username='alice',
    password='password',
    compress=True  # 啟用 SSH 壓縮
)

6. 網路優化:

# Linux 調整 TCP 緩衝區
sudo sysctl -w net.core.rmem_max=16777216
sudo sysctl -w net.core.wmem_max=16777216

Q3:如何實作 FTP 斷點續傳?

答案:

原理:

1. 檢查本地檔案大小
2. 告訴伺服器從該位置開始傳輸
3. 追加寫入本地檔案

Python 實作:

import os
from ftplib import FTP

def resume_download(ftp, remote_file, local_file):
    """FTP 斷點續傳下載"""

    # 檢查本地檔案是否存在
    if os.path.exists(local_file):
        local_size = os.path.getsize(local_file)
        mode = 'ab'  # 追加模式
    else:
        local_size = 0
        mode = 'wb'  # 新建模式

    # 取得遠端檔案大小
    remote_size = ftp.size(remote_file)

    if local_size == remote_size:
        print("檔案已下載完成")
        return

    print(f"已下載:{local_size} bytes")
    print(f"檔案大小:{remote_size} bytes")
    print(f"剩餘:{remote_size - local_size} bytes")

    # 從中斷處繼續下載
    with open(local_file, mode) as f:
        ftp.retrbinary(
            f'RETR {remote_file}',
            f.write,
            rest=local_size  # 從此位置開始
        )

    print("下載完成")

# 使用
ftp = FTP('ftp.example.com')
ftp.login('alice', 'password')

# 第一次下載(可能中斷)
try:
    resume_download(ftp, 'large_file.zip', 'local.zip')
except KeyboardInterrupt:
    print("下載中斷")

# 重新執行,自動續傳
resume_download(ftp, 'large_file.zip', 'local.zip')

ftp.quit()

Q4:如何保護 FTP 伺服器?

答案:

1. 限制 IP 存取:

# vsftpd 設定 (/etc/vsftpd.conf)
tcp_wrappers=YES

# /etc/hosts.allow
vsftpd: 192.168.1.0/24

# /etc/hosts.deny
vsftpd: ALL

2. 使用 chroot 限制目錄:

# vsftpd 設定
chroot_local_user=YES  # 限制使用者在家目錄

3. 限制頻寬:

# vsftpd 設定
local_max_rate=1000000  # 1 MB/s
anon_max_rate=500000    # 500 KB/s

4. 記錄日誌:

# vsftpd 設定
xferlog_enable=YES
xferlog_file=/var/log/vsftpd.log

# 監控異常活動
tail -f /var/log/vsftpd.log

5. 使用 Fail2Ban 防止暴力破解:

# /etc/fail2ban/jail.local
[vsftpd]
enabled = true
port = ftp,ftp-data,ftps,ftps-data
logpath = /var/log/vsftpd.log
maxretry = 3
bantime = 3600  # 封鎖 1 小時

6. 定期更新與稽核:

# 檢查 FTP 版本
vsftpd -v

# 更新
sudo apt-get update
sudo apt-get upgrade vsftpd

# 檢查登入記錄
sudo grep "FAIL LOGIN" /var/log/vsftpd.log

📝 總結

FTP 協定核心要點:

  • 雙連線設計 📁

    • 控制連線(Port 21):傳送命令
    • 數據連線(Port 20/動態):傳輸檔案
  • 兩種模式 🔀

    • 主動模式:伺服器主動連線(有 NAT 問題)
    • 被動模式:客戶端主動連線(推薦)
  • 安全性問題 ⚠️

    • 明文傳輸(密碼、檔案內容可被竊聽)
    • 建議:內網使用或改用 SFTP

記憶口訣:「雙控數,主被動」

  • :雙連線(控制+數據)
  • 控數:控制連線(21)+ 數據連線(20)
  • 主被動:主動模式 vs 被動模式

使用建議:

✅ 適合場景:
- 內網環境(安全性要求低)
- 速度優先(無加密開銷)
- 舊系統相容性需求

⚠️ 不適合場景:
- 公網環境(建議用 SFTP)
- 傳輸敏感資料(建議用 SFTP)
- 需要強認證(建議用 SFTP)

🔗 延伸閱讀

  • 下一篇:07-2. SFTP 協定
  • 相關文章:07-3. SCP 協定
  • RFC 959(FTP):https://tools.ietf.org/html/rfc959
  • vsftpd 官方文件:https://security.appspot.com/vsftpd.html
0%