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

C 語言 Socket 實現 HTTP GET 請求訪問 WebService:原生 TCP、阻塞超時、響應解析

     編輯:關於C語言

C 語言 Socket 實現 HTTP GET 請求訪問 WebService:原生 TCP、阻塞超時、響應解析

在不引入 libcurl、libhttp 等第三方庫的前提下,使用原生 BSD Socket 實現 HTTP/1.0 GET 請求,獲取 JSON 格式響應。適用於嵌入式 Linux、交叉編譯環境、極簡容器等無法依賴重型庫的場景。

一、完整可編譯代碼

// http_get.c
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define RECV_TIMEOUT_SEC 5
#define BUF_SIZE 4096

int main(int argc, char *argv[]) {
    if (argc != 4) {
        fprintf(stderr, "Usage: %s   \n", argv[0]);
        fprintf(stderr, "Example: %s 127.0.0.1 80 /api/data\n", argv[0]);
        return 1;
    }
    const char *ip = argv[1];
    int port = atoi(argv[2]);
    const char *path = argv[3];

    // 1. 創建 TCP Socket
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0) { perror("socket"); return 1; }

    // 2. 設置接收超時(防止掛死)
    struct timeval tv = {RECV_TIMEOUT_SEC, 0};
    if (setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv)) < 0) {
        perror("setsockopt SO_RCVTIMEO");
    }

    // 3. 填充服務端地址
    struct sockaddr_in serv_addr = {0};
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(port);
    if (inet_pton(AF_INET, ip, &serv_addr.sin_addr) <= 0) {
        fprintf(stderr, "Invalid IP address\n");
        close(sockfd);
        return 1;
    }

    // 4. 連接
    if (connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
        perror("connect");
        close(sockfd);
        return 1;
    }

    // 5. 構造 HTTP/1.0 GET 請求(簡化版,無 Host 頭可能導致某些服務器 400)
    char request[512];
    int req_len = snprintf(request, sizeof(request),
                           "GET %s HTTP/1.0\r\n"
                           "Host: %s\r\n"
                           "Connection: close\r\n"
                           "\r\n",
                           path, ip);
    if (req_len >= sizeof(request)) {
        fprintf(stderr, "Request too long\n");
        close(sockfd);
        return 1;
    }

    // 6. 發送請求
    if (send(sockfd, request, req_len, 0) != req_len) {
        perror("send");
        close(sockfd);
        return 1;
    }
    printf("Request sent (%d bytes)\n", req_len);

    // 7. 接收響應(循環讀取直到 EOF 或超時)
    char response[BUF_SIZE];
    int total = 0;
    while (1) {
        int n = recv(sockfd, response + total, BUF_SIZE - total - 1, 0);
        if (n < 0) {
            if (errno == EAGAIN || errno == EWOULDBLOCK) {
                fprintf(stderr, "recv timeout\n");
            } else {
                perror("recv");
            }
            break;
        }
        if (n == 0) break; // 對端關閉 (FIN)
        total += n;
        if (total >= BUF_SIZE - 1) break; // 緩衝區滿
    }
    response[total] = '\0';
    printf("Received %d bytes\n", total);

    // 8. 簡易解析:跳過頭部,定位體開始 (\r\n\r\n)
    char *body = strstr(response, "\r\n\r\n");
    if (body) {
        body += 4; // 跳過 \r\n\r\n
        printf("--- Response Body ---\n%s\n", body);
    } else {
        printf("--- Raw Response ---\n%s\n", response);
    }

    close(sockfd);
    return 0;
}

二、編譯與運行

$ gcc http_get.c -o http_get
$ ./http_get 127.0.0.1 80 /test
Request sent (78 bytes)
Received 215 bytes
--- Response Body ---
{"status": "ok", "data": [1,2,3]}

三、關鍵技術點解析

環節關鍵點說明
Socket 創建AF_INET, SOCK_STREAMIPv4 TCP 流式套接字
超時控制SO_RCVTIMEO防止 recv 無限阻塞,生產環境必加
地址轉換inet_pton比 inet_addr 更現代,支持 IPv6 擴展
HTTP 請求格式Host 頭 + Connection: closeHTTP/1.0 需 Host 頭(虛擬主機),close 避免連接複用複雜性
發送可靠性檢查 send 返回值短請求通常一次發完,長請求需循環發送
接收循環recv 返回 0=EOF, -1=錯誤需處理 EAGAIN/EWOULDBLOCK(超時)、EINTR(信號中斷)
響應解析strstr("\r\n\r\n")簡易定位體部,生產環境需完整 HTTP 解析器(如 http-parser)

四、生產級擴展清單

  1. HTTPS 支持:集成 mbedTLS / wolfSSL,在 SSL_CTX 上調用 SSL_read/write 替代 recv/send。
  2. 完整 HTTP 解析:狀態機解析狀態行、頭部(Content-Length、Transfer-Encoding: chunked)、體分塊接收。
  3. 連接池/Keep-Alive:複用 Socket,解析 Connection: keep-alive,管理空閒超時。
  4. DNS 解析:用 getaddrinfo 替代硬編碼 IP,支持域名、IPv6、多地址重試。
  5. 錯誤重試/熔斷:指數退避重試、統計失敗率觸發熔斷。
  6. 非阻塞 + epoll/kqueue/IOCP:高併發場景下單線程管理萬級連接。

五、常見避坑

  • 忘記 \r\n\r\n 結尾:HTTP 頭部必須以雙 CRLF 結束,否則服務器持續等待頭部結束。
  • Host 頭缺失:HTTP/1.1 強制要求 Host,HTTP/1.0 建議加上,否則虛擬主機路由失敗返回 400。
  • 緩衝區溢出:recv 時預留結束符空間 BUF_SIZE - total - 1。
  • 大端序端口:htons(port) 必須調用,否則連接錯誤端口。
  • 超時值類型:struct timeval 秒/微秒,非毫秒。

總結

原生 Socket 實現 HTTP GET 核心在於:正確構造請求報文、可靠發送接收循環、超時保護、簡易響應分層。代碼量約 80 行,零依賴,是理解 HTTP 協議本質、排查網絡故障、嵌入式輕量級集成的利器。生產系統建議逐步遷移至成熟庫(libcurl, cpp-httplib, cpp-netlib)以覆蓋完整協議細節與安全特性。

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