在不引入 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_STREAM | IPv4 TCP 流式套接字 |
| 超時控制 | SO_RCVTIMEO | 防止 recv 無限阻塞,生產環境必加 |
| 地址轉換 | inet_pton | 比 inet_addr 更現代,支持 IPv6 擴展 |
| HTTP 請求格式 | Host 頭 + Connection: close | HTTP/1.0 需 Host 頭(虛擬主機),close 避免連接複用複雜性 |
| 發送可靠性 | 檢查 send 返回值 | 短請求通常一次發完,長請求需循環發送 |
| 接收循環 | recv 返回 0=EOF, -1=錯誤 | 需處理 EAGAIN/EWOULDBLOCK(超時)、EINTR(信號中斷) |
| 響應解析 | strstr("\r\n\r\n") | 簡易定位體部,生產環境需完整 HTTP 解析器(如 http-parser) |
SSL_read/write 替代 recv/send。Connection: keep-alive,管理空閒超時。getaddrinfo 替代硬編碼 IP,支持域名、IPv6、多地址重試。recv 時預留結束符空間 BUF_SIZE - total - 1。htons(port) 必須調用,否則連接錯誤端口。struct timeval 秒/微秒,非毫秒。原生 Socket 實現 HTTP GET 核心在於:正確構造請求報文、可靠發送接收循環、超時保護、簡易響應分層。代碼量約 80 行,零依賴,是理解 HTTP 協議本質、排查網絡故障、嵌入式輕量級集成的利器。生產系統建議逐步遷移至成熟庫(libcurl, cpp-httplib, cpp-netlib)以覆蓋完整協議細節與安全特性。