FTP 協議雙通道設計(命令通道 + 數據通道)是網絡編程經典案例。本文記錄從 Linux C++ 遷移到 Windows VC6 Winsock 的實戰經驗,深度解析 PORT/PASV 模式、阻塞陷阱、端口字節序轉換。
| 特性 | PORT(主動模式) | PASV(被動模式) |
|---|---|---|
| 發起方 | 客戶端告知 IP:Port,服務端主動連接 | 服務端告知 IP:Port,客戶端主動連接 |
| 命令格式 | PORT h1,h2,h3,h4,p1,p2 | PASV → 響應 227 (h1,h2,h3,h4,p1,p2) |
| 端口計算 | port = p1*256 + p2 | 同理 |
| 防火牆友好度 | 差(需客戶端開放入站端口) | 好(僅服務端開放) |
| NAT 穿透 | 困難 | 較易(服務端通常有公網 IP) |
// ❌ 錯誤:VC6 send 第二參數為 LPSTR (char*),但長度用 sizeof 導致發送緩衝區垃圾數據 send(cmdSock, cmdBuf, sizeof(cmdBuf), 0); // ✅ 正確:用 strlen 獲取有效長度 send(cmdSock, cmdBuf, strlen(cmdBuf), 0);
現象: 發送後陷入阻塞,服務端收到亂碼無法解析命令。
// ❌ 錯誤:端口未轉網絡字節序 sockaddr_in dataAddr; dataAddr.sin_port = port; // 主機序(小端) // ✅ 正確:htons 轉換 dataAddr.sin_port = htons(port); // 網絡序(大端)
排查歷程: 防火牆正常、FlashFXP 可連、端口計算正確 → 最後發現 htons 缺失。網絡編程鐵律:所有網絡傳輸的整數(端口、IP、長度)必須用 htons/htonl/ntohs/ntohl 轉換。
數據通道接收到類似:
-rw-r--r-- 1 user group 1234 Jan 1 12:00 file.txt
drwxr-xr-x 2 user group 4096 Jan 1 11:00 dir
需處理:
建議:優先使用 MLSD(RFC 3659)獲取機器可解析的事實標準格式。
客戶端 服務端
│ │
├─ socket() → connect(21) ──────►│
│◄──── 220 Welcome ─────────────┤
├─ USER anonymous ─────────────►│
│◄──── 331 Password required ───┤
├─ PASS xxx ───────────────────►│
│◄──── 230 Login successful ────┤
├─ TYPE I ─────────────────────►│ (二進制模式)
│◄──── 200 Type set ────────────┤
├─ PASV ──────────────────────►│ (進入被動模式)
│◄──── 227 (192,168,1,100,4,186)┤ (IP: 192.168.1.100, Port: 4*256+186=1210)
│ │
├─ socket() → connect(1210) ───►│ (建立數據通道)
├─ LIST ──────────────────────►│
│◄──── 150 Opening data ────────┤
│ (數據通道接收目錄列表) │
│◄──── 226 Transfer complete ───┤
├─ QUIT ──────────────────────►│
│◄──── 221 Goodbye ─────────────┤
│ │
| 項目 | Linux (POSIX) | Windows (Winsock) |
|---|---|---|
| 頭文件 | <sys/socket.h> | <winsock2.h> |
| 庫 | 無需額外鏈接 | #pragma comment(lib,"ws2_32.lib") |
| 初始化 | 無 | WSAStartup(MAKEWORD(2,2), &wsaData) |
| 清理 | 無 | WSACleanup() |
| 錯誤碼 | errno | WSAGetLastError() |
| 關閉套接字 | close(fd) | closesocket(s) |
| send/recv 類型 | const void*, void* | const char*, char* (LPSTR) |
| IOCTL | ioctl/fcntl | ioctlsocket |
std::format + std::span 可大幅簡化協議解析代碼