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

Linux 下 C++ 實現 HTTP GET/POST 客戶端(基於 Socket)

     編輯:關於C++

背景

無第三方庫(如 libcurl)環境下,使用 Linux 原生 Socket API 實現 HTTP/1.1 客戶端,支持 Keep-Alive、非阻塞連接、超時控制。


架構設計

  • HttpRequest 類:封裝 Socket 生命週期、請求構造、發送接收、URL 解析。
  • 非阻塞 + select:連接、發送、接收均可設置超時,避免永久阻塞。
  • Keep-Alive 復用:成員 m_iSocketFd 保持連接,下次請求先檢查可寫性復用。

核心模塊

1. URL 解析


// 提取 host、port、path
// https://host:port/path?query
// https 暫不支持(需 SSL/TLS 層)
int getPort(const string& url) { /* 默認 80 */ }
string getHost(const string& url) { /* 域名或 IP */ }
string getPath(const string& url) { /* /path?query */ }
string getIP(const string& url) { 
    // 域名 → gethostbyname → inet_ntoa 
    // IP 直通
}

2. HTTP 報文構造


string buildRequest(const string& method, const string& url, const string& body) {
    string host = getHost(url);
    string path = getPath(url);
    string req = method + " " + path + " HTTP/1.1\r\n";
    req += "Host: " + host + "\r\n";
    req += "Connection: Keep-Alive\r\n";
    req += "User-Agent: CustomCppClient/1.0\r\n";
    req += "Accept: */*\r\n";
    if (method == "POST") {
        req += "Content-Type: application/x-www-form-urlencoded\r\n";
        req += "Content-Length: " + to_string(body.size()) + "\r\n\r\n";
        req += body;
    } else {
        req += "\r\n";
    }
    return req;
}

3. 非阻塞連接與超時


int sock = socket(AF_INET, SOCK_STREAM, 0);
fcntl(sock, F_SETFL, O_NONBLOCK); // 非阻塞
connect(sock, (sockaddr*)&addr, sizeof(addr));
// select 監聽寫就緒,超時 3.5s
fd_set wset; FD_ZERO(&wset); FD_SET(sock, &wset);
timeval tv = {3, 500000};
int ret = select(sock+1, nullptr, &wset, nullptr, &tv);
if (ret <= 0 || !FD_ISSET(sock, &wset)) { /* 超時/失敗 */ }
// 進一步 getsockopt SO_ERROR 確認連接真正成功

4. 發送與接收


// 發送
send(sock, req.c_str(), req.size(), 0);

// 接收循環
string response;
char buf[4096];
while (true) {
    int n = recv(sock, buf, sizeof(buf), 0);
    if (n > 0) response.append(buf, n);
    else if (n == 0) break; // 對端關閉
    else if (errno == EINTR || errno == EAGAIN) continue;
    else break; // 錯誤
}

5. Keep-Alive 復用邏輯


// 成員變量
int m_iSocketFd = -1;

// 請求前檢查
if (m_iSocketFd != -1) {
    fd_set wset; FD_ZERO(&wset); FD_SET(m_iSocketFd, &wset);
    timeval tv = {0, 0}; // 非阻塞檢查
    if (select(m_iSocketFd+1, nullptr, &wset, nullptr, &tv) > 0 && FD_ISSET(m_iSocketFd, &wset)) {
        // 進一步檢查 SO_ERROR 為 0
        // 復用發送
    }
}
// 復用失敗或首次請求 → 重新建立連接

完整類定義(簡化版)


class HttpRequest {
public:
    HttpRequest() : m_iSocketFd(-1) {}
    ~HttpRequest() { if (m_iSocketFd != -1) close(m_iSocketFd); }
    
    bool Get(const string& url, string& response) {
        return request("GET", url, "", response);
    }
    bool Post(const string& url, const string& data, string& response) {
        return request("POST", url, data, response);
    }
private:
    int m_iSocketFd;
    bool request(const string& method, const string& url, const string& data, string& response);
    // 內部輔助:parseURL, buildRequest, connectWithTimeout, sendRecv, checkSocketReusable
};

使用示例


int main() {
    HttpRequest http;
    string resp;
    
    if (http.Get("https://www.baidu.com", resp)) {
        cout << "GET 成功,長度: " << resp.size() << endl;
        // cout << resp.substr(0, 500) << endl;
    }
    
    if (http.Post("https://httpbin.org/post", "key=value&foo=bar", resp)) {
        cout << "POST 成功,長度: " << resp.size() << endl;
    }
    return 0;
}

局限性與擴展方向

局限解決方案
不支持 HTTPS集成 OpenSSL(SSL_CTX, SSL_connect, SSL_read/write)
不支持 Chunked Transfer解析 Transfer-Encoding: chunked 分塊讀取
不支持重定向 301/302解析 Location 頭遞歸請求
無連接池多 host 維護 unordered_map>
同步阻塞改用 epoll/kqueue + 狀態機實現異步

總結

基於 Socket 手寫 HTTP 客戶端是理解網絡編程基礎的最佳練習。掌握:

  1. HTTP/1.1 報文格式與 Keep-Alive 機制
  2. 非阻塞 Socket + select/poll/epoll 事件驅動模型
  3. 超時控制、錯誤碼處理(EINTR/EAGAIN/EWOULDBLOCK)
  4. 域名解析(gethostbyname/getaddrinfo)

生產環境請直接使用 libcurl 或 cpp-httplib(單頭文件、支持 HTTPS/異步),本代碼僅供教學與特殊無依賴場景參考。

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