在 Linux C/C++ 網路程式開發中,域名解析是建立連線的第一步。傳統 gethostbyname 與現代 getaddrinfo 共存多年,二者在介面設計、IPv6 支援、執行緒安全性等方面差異顯著。本文通過原型分析、代碼實戰與最佳實踐,幫助開發者選擇正確工具。
#include <netdb.h>
struct hostent *gethostbyname(const char *name);
結構體 hostent:
h_name:正式主機名h_aliases:別名列表h_addrtype:地址族 (AF_INET)h_length:地址長度 (4 bytes)h_addr_list:IPv4 地址陣列 (網路序)缺陷:
❌ 僅支援 IPv4
❌ 非執行緒安全(靜態緩衝區)
❌ 無法獲取 Socket 類型/協議資訊
❌ 錯誤處理依賴全域 h_errno
int getaddrinfo(const char *node, const char *service,
const struct addrinfo *hints,
struct addrinfo **res);
核心優勢:
✅ 協議無關(IPv4/IPv6 雙棧)
✅ 執行緒安全(調用者分配記憶體)
✅ 輸出 addrinfo 鏈表,含 ai_family/ai_socktype/ai_protocol/ai_addr 完整 Socket 參數
✅ 統一錯誤碼,配合 gai_strerror() 易診斷
struct hostent *hp = gethostbyname("www.example.com");
if (!hp) { herror("gethostbyname"); return -1; }
for (int i=0; hp->h_addr_list[i]; ++i) {
char ip[INET_ADDRSTRLEN];
inet_ntop(AF_INET, hp->h_addr_list[i], ip, sizeof(ip));
printf("%s\n", ip);
}
struct addrinfo hints = {0}, *res, *p;
hints.ai_family = AF_UNSPEC; // IPv4/IPv6 皆可
hints.ai_socktype = SOCK_STREAM; // TCP
int err = getaddrinfo("www.example.com", "80", &hints, &res);
if (err) { fprintf(stderr, "%s\n", gai_strerror(err)); return -1; }
for (p=res; p; p=p->ai_next) {
char ip[INET6_ADDRSTRLEN];
void *addr = (p->ai_family==AF_INET) ?
&((struct sockaddr_in*)p->ai_addr)->sin_addr :
&((struct sockaddr_in6*)p->ai_addr)->sin6_addr;
inet_ntop(p->ai_family, addr, ip, sizeof(ip));
printf("%s\n", ip);
}
freeaddrinfo(res); // 必須釋放
| 場景 | 推薦介面 | 理由 |
|---|---|---|
| 新專案/跨平台/長期維護 | getaddrinfo | 標準化、IPv6 就緒、執行緒安全 |
| 舊代碼維護/極簡 IPv4 工具 | gethostbyname | 相容性、代碼量極小 |
| 高效能非阻塞解析 | c-ares / getaddrinfo_a | 異步非阻塞,避免阻塞事件循環 |
AI_ADDRCONFIG 標誌可自動過濾本機不支援的協議族,減少連線失敗。/etc/resolv.conf 與 /etc/nsswitch.conf 配置對解析行為的影響。拥抱 getaddrinfo,編寫面向未來的網路代碼。