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

C++Builder Winsock 實作 FTP 用戶端:從 Socket 連線到匿名登入

     編輯:關於C++

本文展示一個純 Winsock API 實作的 FTP 用戶端核心邏輯,涵蓋控制通道建立、伺服器回應解析、指令發送、匿名登入流程。雖然代碼基於舊版 Winsock 1.1 與阻塞式 Socket,但其協議解析、錯誤處理、狀態機設計對理解 FTP 協議與網路程式基礎仍具教學價值。

架構概覽

  • 控制通道:TCP Port 21,傳送 FTP 指令(USER、PASS、QUIT 等)與接收回應碼(220、230、530 等)。
  • 同步阻塞模型:send/recv 同步呼叫,簡單但會阻塞 UI 執行緒。
  • 回應碼解析:FTP 回應首行前 3 碼為數字代碼,如 220 服務就緒、230 登入成功、530 認證失敗。

核心模組解析

1. 回應碼擷取

UINT GetReplyCode(LPSTR lpszServerReply) {
    char c = *(lpszServerReply + 3);      // 暫存第 4 字元
    *(lpszServerReply + 3) = '\0';        // 截斷前 3 碼
    UINT nCode = atoi(lpszServerReply);   // 轉整數
    *(lpszServerReply + 3) = c;           // 還原
    return nCode;
}

2. 讀取伺服器回應

UINT ReadFTPServerReply(SOCKET hControlChannel) {
    char sReceiveBuffer[1024];
    int iLength = recv(hControlChannel, sReceiveBuffer, sizeof(sReceiveBuffer), 0);
    if (iLength == SOCKET_ERROR) {
        // WSAGetLastError() 取得錯誤碼,MessageBox 顯示
        return 999; // 自訂錯誤代碼
    }
    sReceiveBuffer[iLength] = '\0';
    MessageBox(NULL, sReceiveBuffer, "ReadFTPServerReply", MB_OK | MB_ICONINFORMATION);
    return GetReplyCode(sReceiveBuffer);
}

3. 發送 FTP 指令

UINT SendFTPCommand(SOCKET hControlChannel, LPSTR szCommandBuffer) {
    if (send(hControlChannel, szCommandBuffer, lstrlen(szCommandBuffer), 0) == SOCKET_ERROR) {
        // 錯誤處理
        return 999;
    }
    return ReadFTPServerReply(hControlChannel); // 發送後立即讀取回應
}

4. 匿名登入流程

UINT AnonymousFTPLogIn(SOCKET hControlSocket) {
    char *LoginCommand[] = {"USER anonymous\r\n", "PASS guest\r\n", NULL};
    int nReplyCode, iMsg = 0;
    do {
        nReplyCode = SendFTPCommand(hControlSocket, LoginCommand[iMsg++]);
    } while (LoginCommand[iMsg] && nReplyCode < 400); // 4xx/5xx 為失敗
    return nReplyCode; // 230 表示成功
}

5. 建立控制通道連線

SOCKET ConnectFTPControlSocket(LPSTR lpszHost) {
    LPHOSTENT lpHostEnt = gethostbyname(lpszHost);
    if (!lpHostEnt) return INVALID_SOCKET;

    SOCKET hControlSocket = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (hControlSocket == INVALID_SOCKET) return INVALID_SOCKET;

    LPSERVENT lpServEnt = getservbyname("ftp", NULL);
    short nProtocolPort = lpServEnt ? lpServEnt->s_port : htons(IPPORT_FTP); // 21

    SOCKADDR_IN sockAddr = {};
    sockAddr.sin_family = AF_INET;
    sockAddr.sin_port = nProtocolPort;
    sockAddr.sin_addr = *((LPIN_ADDR)*lpHostEnt->h_addr_list);

    if (connect(hControlSocket, (LPSOCKADDR)&sockAddr, sizeof(sockAddr)) == SOCKET_ERROR) {
        closesocket(hControlSocket);
        return INVALID_SOCKET;
    }
    // 讀取歡迎訊息(220)
    if (ReadFTPServerReply(hControlSocket) >= 400) {
        closesocket(hControlSocket);
        return INVALID_SOCKET;
    }
    return hControlSocket;
}

6. 主程式入口

int PASCAL WinMain(HINSTANCE hInstance, HINSTANCE, LPSTR, int) {
    WSADATA wsaData;
    if (WSAStartup(0x0101, &wsaData)) { /* 載入失敗 */ return 0; }

    SOCKET hControlChannel = ConnectFTPControlSocket("ftp.example.com");
    if (hControlChannel != INVALID_SOCKET) {
        if (AnonymousFTPLogIn(hControlChannel) == 230) { // 登入成功
            SendFTPCommand(hControlChannel, "QUIT\r\n");
        }
        closesocket(hControlChannel);
    }
    WSACleanup();
    MessageBox(NULL, "THE END!!", "Quick FTP", MB_OK | MB_ICONEXCLAMATION);
    return 0;
}

關鍵技術細節與現代化改良

原代碼特徵問題/限制現代最佳實踐
Winsock 1.1 (0x0101)舊版,缺乏 IPv6、重疊 I/O、條件接受等新功能WSAStartup(MAKEWORD(2,2), &wsaData) 使用 Winsock 2.2
阻塞式 send/recvUI 凍結、無法並發多連線非同步 I/O:WSAAsyncSelect/WSAEventSelect/IOCP/std::async + Boost.Asio
硬編碼主機/帳號無法複用、安全性差設定檔/命令列參數/安全儲存憑證
MessageBox 直接在網路函式顯示錯誤邏輯與 UI 耦合、無法單元測試回傳錯誤碼/拋出例外,由上層 UI 決定呈現方式
單一緩衝區 recv 假設一次收完整行TCP 流無訊息邊界,可能黏包/拆包實作緩衝區累積,解析 \r\n 結尾的完整行
無資料通道 (PORT/PASV) 實作無法上傳/下載檔案、列表目錄需實作主動/被動模式資料連線、傳輸型別 (ASCII/Binary)

現代 C++ 重構骨架

class FtpClient {
    SOCKET ctrl_sock_ = INVALID_SOCKET;
    std::string host_, user_, pass_;
    std::vector<char> recv_buf_;
    
public:
    FtpClient(std::string host, std::string user="anonymous", std::string pass="guest")
        : host_(std::move(host)), user_(std::move(user)), pass_(std::move(pass)) {}
    
    bool Connect() {
        // Winsock 2.2 初始化、getaddrinfo (支援 IPv6)、非阻塞 connect + select
        // 建立控制通道、讀取 220
    }
    
    bool Login() {
        return SendCmd("USER " + user_) == 331 && 
               SendCmd("PASS " + pass_) == 230;
    }
    
    int SendCmd(const std::string& cmd) {
        std::string data = cmd + "\r\n";
        send(ctrl_sock_, data.data(), data.size(), 0);
        return ReadReplyCode(); // 解析多行回應、處理黏包
    }
    
    // 需額外實作:PASV/PORT、LIST、RETR、STOR、TYPE I/A、QUIT
    // 建議改用成熟庫:libcurl、Boost.Beast、Poco::Net::FTPClientSession
};

總結

此範例雖簡陋,但完整示範了 FTP 控制通道的連線建立、指令/回應協議、狀態流轉。實務開發中:

  1. 優先使用成熟庫(libcurl、Boost.Beast、Poco),避免重造輪子與安全漏洞。
  2. 若必須自寫,採用 Winsock 2.2 + 非同步 I/O + 完整緩衝區管理。
  3. 將協議邏輯與 UI 解耦,便於測試與跨平台。
  4. 支援 PASV 模式(穿透 NAT/防火牆)、TLS/SSL (FTPS)、IPv6。

理解底層協議,才能在高層庫出問題時精準診斷、在特殊需求時正確擴充。

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