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

C 語言獲取網頁源碼實戰:Socket 實現 HTTP 客戶端完整流程

     編輯:關於C語言

從零構建 HTTP 獲取器:Socket 編程實戰

利用 C 語言 Socket 直接實現 HTTP 請求,是理解網絡協議棧、掌握底層通信細節的最佳練習。本文記錄從 URL 解析到完整接收響應的全流程實現邏輯與關鍵技術點。

整體流程架構

URL 解析 → DNS 解析 → Socket 創建 → Connect → 發送 GET 請求 → 循環接收響應 → 內存拼接 → 釋放資源

關鍵步驟深度解析

1. URL 解析:提取 Host、Path、Port

輸入 www.baidu.com/1.html → Host: www.baidu.com, Path: /1.html, Port: 80 (默認)。需處理帶端口、帶協議頭、IP 直連等多種格式。

2. DNS 解析:gethostbyname()

struct hostent *host = gethostbyname(hostname); // hostname 如 "www.baidu.com"
if (!host) { /* 錯誤處理 */ }
struct in_addr *addr = (struct in_addr*)host->h_addr_list[0];
char *ip = inet_ntoa(*addr); // 獲取 IP 字符串

注意:gethostbyname 非線程安全,現代代碼推薦使用 getaddrinfo。

3. 構建 sockaddr_in 並連接

struct sockaddr_in server_addr = {0};
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(port);
server_addr.sin_addr.s_addr = inet_addr(ip); // 或從 hostent 複製
connect(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr));

4. 構造並發送 HTTP GET 請求

char request[1024];
snprintf(request, sizeof(request),
    "GET %s HTTP/1.1\r\n"
    "Host: %s\r\n"
    "Connection: close\r\n"
    "\r\n", path, host);
send(sockfd, request, strlen(request), 0);

關鍵點:必須以 \r\n\r\n 結束請求頭;Connection: close 避免長連接導致 recv 阻塞等待超時。

5. 動態接收響應數據(核心難點)

響應體大小未知,需動態擴容緩衝區:

char *buffer = NULL;
size_t buf_size = 0, buf_len = 0;
char tmp[4096];
int bytes;

while ((bytes = recv(sockfd, tmp, sizeof(tmp), 0)) > 0) {
    if (buf_len + bytes >= buf_size) {
        buf_size = buf_size ? buf_size * 2 : 8192;
        buffer = realloc(buffer, buf_size);
    }
    memcpy(buffer + buf_len, tmp, bytes);
    buf_len += bytes;
}
buffer[buf_len] = '\0'; // 字符串終止
  • 使用 realloc 指數增長策略(×2),均攤 O(1) 復制成本。
  • 設置接收超時:setsockopt(sock, SOL_SOCKET, SO_RCVTIMEO, &timeout, sizeof(timeout)) 防止無限阻塞。

6. 響應處理與編碼問題

  • 頭體分離:查找 \r\n\r\n 分隔符,頭部通常為 ASCII/ISO-8859-1,體部編碼由 Content-Type: charset=... 指定。
  • 編碼轉換:若需顯示中文,需根據 charset (UTF-8/GBK) 使用 iconv 轉換為本地編碼。
  • 數據 vs 字符串:Socket 傳輸的是字節流,非文本,二進制文件(圖片)同樣適用此接收邏輯。

完整主函數框架

int main(int argc, char *argv[]) {
    if (argc < 2) { printf("Usage: %s \n", argv[0]); return 1; }
    
    char *url = argv[1];
    char host[256], path[1024];
    int port = 80;
    parse_url(url, host, path, &port); // 自行實現
    
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    // ... DNS, Connect, Send, Recv 邏輯 ...
    
    printf("響應內容:\n%s\n", buffer);
    free(buffer);
    close(sockfd);
    return 0;
}

遺留挑戰與進階方向

  • HTTPS 支持:需集成 OpenSSL/mbedTLS,在 Socket 之上建立 TLS 隧道。
  • Chunked Transfer Encoding:解析分塊傳輸響應體。
  • 非阻塞 + epoll:實現高併發異步 HTTP 客戶端。
  • 健壯性:重定向處理、Cookie 管理、連接池復用。

總結

手寫 HTTP 客戶端雖然「重複造輪子」,但能深度理解 TCP 三次握手、HTTP 協議文本格式、動態內存管理、網絡錯誤處理等核心底層技能。這是從「調用庫」進階為「理解原理」的關鍵一步。

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