FTP 基於雙通道架構:命令通道 (Port 21) 處理控制指令,資料通道 臨時建立傳輸檔案。常用命令分類:
| 類別 | 命令 | 說明 |
|---|---|---|
| 傳輸模式 | TYPE A / TYPE I | ASCII 文本 / Binary 二進制 |
| 目錄操作 | CWD, CDUP, PWD, LIST | 切換/列舉目錄 |
| 連線控制 | USER, PASS, QUIT | 認證與斷開 |
| 檔案上傳 | STOR, APPE, MPUT | 單/多檔上傳 |
| 檔案下載 | RETR, MGET | 單/多檔下載 |
| 資料連接 | PORT, PASV | 主動/被動模式協商埠口 |
| 檔案管理 | DELE, RNFR/RNTO, MKD/RMD | 刪除/重命名/建立刪除目錄 |
public class FtpServer {
private static String rootDir = "C:/ftp_root"; // 可透過 args[0] 指定
public static void main(String[] args) throws IOException {
if (args.length > 0) rootDir = args[0];
try (ServerSocket server = new ServerSocket(21)) {
System.out.println("FTP Server started on port 21, root: " + rootDir);
ExecutorService pool = Executors.newCachedThreadPool();
while (true) {
Socket client = server.accept();
pool.execute(new ClientHandler(client, rootDir));
}
}
}
}
out.println("220 Welcome to Java FTP Server");
String line;
while ((line = in.readLine()) != null) {
if (line.startsWith("USER ")) {
username = line.substring(5).trim();
out.println("331 Password required for " + username);
} else if (line.startsWith("PASS ")) {
// 這裡應對接真實認證系統 (DB/LDAP/檔案)
out.println("230 User " + username + " logged in");
break; // 進入命令迴圈
}
}
客戶端傳送 `PORT h1,h2,h3,h4,p1,p2`,IP = `h1.h2.h3.h4`,Port = `p1*256 + p2`
if (cmd.startsWith("PORT ")) {
String[] parts = cmd.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]);
dataSocket = new Socket(ip, port); // 主動連回客戶端
out.println("200 PORT command successful");
}
// RETR 下載
if (cmd.startsWith("RETR ")) {
String fileName = cmd.substring(5).trim();
File file = new File(currentDir, fileName);
out.println("150 Opening BINARY data connection for " + fileName);
try (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");
}
// STOR 上傳
if (cmd.startsWith("STOR ")) {
String fileName = cmd.substring(5).trim();
File file = new File(currentDir, fileName);
out.println("150 Opening BINARY data connection for " + fileName);
try (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");
}
if (cmd.equals("LIST")) {
out.println("150 Here comes the directory listing");
try (PrintWriter pw = new PrintWriter(dataSocket.getOutputStream())) {
File[] files = currentDir.listFiles();
for (File f : files) {
String perm = f.isDirectory() ? "drwxr-xr-x" : "-rw-r--r--";
pw.printf("%s 1 owner group %12d %s %s%n",
perm, f.length(),
new SimpleDateFormat("MMM dd HH:mm").format(new Date(f.lastModified())),
f.getName());
}
}
out.println("226 Directory send OK");
}
完整原始碼可參考 Apache Mina FtpServer 或 FTP4J 等成熟開源實現,生產環境建議直接整合而非自建。