首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> Java 程式設計 >> J2EE

Java 實作 FTP 伺服器核心架構與指令處理完整解析

     編輯:J2EE

FTP 協定核心指令分類

FTP(File Transfer Protocol)基於命令/回應模型,客戶端發送文字指令,伺服器回應三位數狀態碼與訊息。主要指令分類:

類別指令說明
傳輸模式TYPE A / IASCII(文字) / Binary(二進位) 模式切換
MODE S / B / C串流/區塊/壓縮傳輸模式(較少用)
目錄操作CWD <path>變更工作目錄
CDUP回上層目錄
PWD / LIST / NLST顯示當前路徑 / 詳細列表 / 精簡列表
檔案傳輸RETR <file>下載檔案
STOR <file>上傳檔案
APPE <file>斷點續傳(附加模式)
檔案管理DELE <file>刪除檔案
RNFR / RNTO重新命名(從/至)
MKD / RMD建立/刪除目錄
連線控制USER / PASS使用者認證
QUIT / ABOR離線 / 中止傳輸
資料連線PORT / PASV主動/被動模式資料埠協商

伺服器架構設計:多執行緒 + 雙通道

FTP 採用雙通道架構:

  • 控制通道(命令通道):TCP 21 埠,持久連線,傳送指令與回應。
  • 資料通道:臨時建立,傳輸檔案內容或目錄列表,傳輸完畢即關閉。

主流程設計

  1. 主執行緒綁定 ServerSocket(21) 監聽。
  2. 接受客戶端連線 accept(),為每個連線建立服務執行緒(FTPServer extends Thread)。
  3. 服務執行緒 run() 迴圈讀取指令、解析、派發處理、回應。
  4. 涉及檔案傳輸指令(RETR/STOR/LIST)時,依 PORT 或 PASV 協商建立資料 Socket 連線。

關鍵程式碼實作解析

1. 主伺服器啟動與執行緒分派

public class FTPServer extends Thread {
    private Socket clientSocket;
    private static String rootDir = "C:\\"; // 預設根目錄
    
    public static void main(String[] args) {
        if (args.length > 0) rootDir = args[0];
        try (ServerSocket server = new ServerSocket(21)) {
            System.out.println("FTP Server started on port 21");
            int clientId = 1;
            while (true) {
                Socket client = server.accept();
                new FTPServer(client, clientId++).start();
            }
        } catch (IOException e) { e.printStackTrace(); }
    }
    // 建構子、run() 等省略...
}

2. 指令解析與派發核心邏輯

public void run() {
    try (BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
         PrintWriter out = new PrintWriter(socket.getOutputStream(), true)) {
        
        out.println("220 Welcome to Java FTP Server");
        String line;
        while ((line = in.readLine()) != null && !done) {
            String cmd = line.split(" ")[0].toUpperCase();
            switch (cmd) {
                case "USER": handleUser(line, out); break;
                case "PASS": handlePass(line, out); break;
                case "CWD":  handleCwd(line, out); break;
                case "CDUP": handleCdup(out); break;
                case "PORT": handlePort(line, out); break;
                case "TYPE": handleType(out); break;
                case "RETR": handleRetr(line, out); break;
                case "STOR": handleStor(line, out); break;
                case "LIST": handleList(out); break;
                case "DELE": handleDele(line, out); break;
                case "QUIT": handleQuit(out); break;
                default: out.println("500 Unknown command");
            }
        }
    } catch (IOException e) { /* log */ }
}

3. 關鍵指令處理實作細節

PORT 指令解析(主動模式)

客戶端傳送:PORT h1,h2,h3,h4,p1,p2(IP 4 組 + 埠號高低位元組)。

private void handlePort(String line, PrintWriter out) {
    // 格式:PORT 192,168,1,100,4,50  -> IP:192.168.1.100  Port:4*256+50=1074
    String[] parts = line.substring(5).split(",");
    String ip = String.join(".", parts[0], parts[1], parts[2], parts[3]);
    int port = Integer.parseInt(parts[4]) * 256 + Integer.parseInt(parts[5]);
    this.dataHost = ip;
    this.dataPort = port;
    out.println("200 PORT command successful");
}

RETR(下載)與 STOR(上傳)對稱實作

private void handleRetr(String line, PrintWriter out) throws IOException {
    String filename = line.substring(5).trim();
    File file = new File(currentDir, filename);
    if (!file.exists()) { out.println("550 File not found"); return; }
    
    out.println("150 Opening BINARY data connection");
    try (Socket dataSocket = new Socket(dataHost, dataPort);
         FileInputStream fis = new FileInputStream(file);
         OutputStream dos = dataSocket.getOutputStream()) {
        byte[] buf = new byte[8192];
        int len;
        while ((len = fis.read(buf)) > 0) dos.write(buf, 0, len);
        out.println("226 Transfer complete");
    } catch (Exception e) { out.println("426 Transfer aborted"); }
}

private void handleStor(String line, PrintWriter out) throws IOException {
    String filename = line.substring(5).trim();
    File file = new File(currentDir, filename);
    out.println("150 Opening BINARY data connection");
    try (Socket dataSocket = new Socket(dataHost, dataPort);
         InputStream dis = dataSocket.getInputStream();
         FileOutputStream fos = new FileOutputStream(file)) {
        byte[] buf = new byte[8192];
        int len;
        while ((len = dis.read(buf)) > 0) fos.write(buf, 0, len);
        out.println("226 Transfer complete");
    } catch (Exception e) { out.println("426 Transfer aborted"); }
}

LIST 目錄列表傳輸

private void handleList(PrintWriter out) throws IOException {
    out.println("150 Opening ASCII data connection");
    try (Socket dataSocket = new Socket(dataHost, dataPort);
         PrintWriter dos = new PrintWriter(dataSocket.getOutputStream(), true)) {
        File dir = new File(currentDir);
        File[] files = dir.listFiles();
        if (files != null) {
            for (File f : files) {
                String type = f.isDirectory() ? "d" : "-";
                // 簡易權限表示,實際應依 RFC 3659 格式化
                dos.printf("%s rw-r--r-- 1 owner group %12d %s %s%n",
                    type, f.length(), 
                    new SimpleDateFormat("MMM dd HH:mm").format(new Date(f.lastModified())),
                    f.getName());
            }
        }
        out.println("226 Transfer complete");
    } catch (Exception e) { out.println("426 Transfer aborted"); }
}

進階議題與生產環境考量

議題說明
被動模式 (PASV/EPSV)伺服器開啟隨機埠等待客戶端連入,需防火牆開放埠範圍,現代客戶端預設使用
SSL/TLS 加密 (FTPS)控制/資料通道加密,需整合 SSLSocketFactory,支援 AUTH TLS 指令
虛擬使用者/權限控管整合資料庫或 LDAP,實作檔案級存取控制(ACL)
大檔案支援使用 RandomAccessFile 實作斷點續傳(REST 指令)
閒置超時與連線限制防止資源耗盡,需實作連線池與心跳機制
NIO/非阻塞架構高併發場景下可遷移至 Selector 或 Netty/MINA 框架

結語

本文以精簡核心代碼示範 Java 實作 FTP 伺服器的雙通道多執行緒架構、指令解析派發、PORT/PASV 協商與檔案傳輸邏輯。實際部署仍需補強安全性(FTPS)、效能(NIO/連線池)、管理功能(虛擬用戶/配額/日誌審計)等企業級特性。建議生產環境直接採用成熟開源專案(Apache FtpServer、vsftpd、ProFTPD)而非自行開發。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved