在做 C 語言網絡編程或模擬 HTTP 客戶端時,第一步就離不開“把域名解析為 IP 地址”這一步。很多人可能直接復制粘貼一段
gethostbyname
的代碼,但未必真正理解它的原理。
本篇博客將圍繞一個經典函數:
char *host_to_ip(const char *hostname)
深入剖析 DNS 解析過程、IP 地址轉換機制,并進一步帶你了解 HTTP 請求是如何基于 TCP 通信進行的。
一、核心函數:host_to_ip
是干什么的?
char *host_to_ip(const char *hostname) {struct hostent *host_entry = gethostbyname(hostname); // 1. DNS 查詢if (host_entry) {return inet_ntoa(*(struct in_addr*)host_entry->h_addr_list[0]); // 2. IP 轉換}return NULL;
}
這段代碼的目標是:將一個域名(如 www.baidu.com)轉換為對應的 IP 地址字符串(如 "14.215.177.39")。
二、代碼詳解:每一步到底在干什么?
第一步:DNS 解析
struct hostent *host_entry = gethostbyname(hostname);
這個函數調用了底層的 DNS 解析邏輯,流程如下:
-
檢查系統的 DNS 緩存或
/etc/hosts
-
若無記錄,則構造 DNS 請求報文,通過 UDP 協議發送到 DNS 服務器(如 114.114.114.114)
-
等待服務器響應,返回域名對應的 IP 地址
-
封裝在
struct hostent
結構體中
第二步:IP 轉換為字符串
inet_ntoa(*(struct in_addr*)host_entry->h_addr_list[0]);
host_entry->h_addr_list[0]
是原始的 4 字節 IP 地址(網絡字節序),不能直接打印。
所以我們使用 inet_ntoa()
把它轉換成字符串形式:
-
輸入:一個
struct in_addr
類型的二進制 IP 地址 -
輸出:可讀字符串,如
"14.215.177.39"
三、這段代碼在 HTTP 請求中的位置
[輸入 URL] -> [解析域名 -> IP] -> [建立 TCP 連接] -> [發送 HTTP 請求] -> [接收響應]↑就在這一步!
也就是說,host_to_ip()
實際完成的是整個 HTTP 通信的第一步:獲取目標服務器的 IP 地址。
如果這一步失敗(如 DNS 解析失敗、沒有網絡),后續的 socket 連接和 HTTP 請求就完全無法進行。
四、HTTP 是如何發送請求的?(基于 TCP)
HTTP 是一個應用層協議,它不能直接和服務器通信,而是借助 TCP 作為底層傳輸通道。流程如下:
1. 客戶端通過 DNS 得到目標 IP(host_to_ip 實現)
2. 使用 socket 與服務器 IP 的 80(HTTP)或 443(HTTPS)端口建立 TCP 三次握手連接
3. 連接成功后,發送 HTTP 請求報文
4. 服務器返回 HTTP 響應報文
5. 客戶端接收、解析、展示結果
五、完整代碼
/*** 將主機名(域名)轉換為對應的 IP 地址字符串* 例如:輸入 "www.baidu.com",返回 "14.215.177.39"*/
char *host_to_ip(const char *hostname) {// 通過 DNS 解析主機名,返回主機信息結構體指針struct hostent *host_entry = gethostbyname(hostname); // gethostbyname 是阻塞式調用// 檢查解析是否成功if (host_entry) {/*** host_entry->h_addr_list 是一個指針數組,存儲所有解析到的 IP 地址(可能多個)* 每個元素是一個 struct in_addr* 類型(指向網絡字節序的 IP)* 這里取第一個 IP(通常是優先級最高的)** 需要將該地址強制轉換為 struct in_addr*,傳給 inet_ntoa 進行轉換** 注意:inet_ntoa 返回的是靜態內存,不能多線程共享或多次直接使用返回值*/return inet_ntoa(*(struct in_addr*)host_entry->h_addr_list[0]);}// 解析失敗,返回 NULLreturn NULL;
}
https://github.com/0voice