早期網路程式設計需手動呼叫 gethostbyname() 做 DNS 查詢,再自行填充 sockaddr_in 結構,不僅繁瑣且易出錯,更無法支援 IPv6。現代 getaddrinfo() 將 DNS 解析、服務名稱查詢、位址結構填充一站式完成,且原生支援雙協議棧,是現代網路程式的必經關卡。
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
int getaddrinfo(const char *node, // 主機名或 IP 位址 (如 "www.example.com")
const char *service, // 服務名 (如 "http") 或埠號字串 (如 "80")
const struct addrinfo *hints, // 輸入條件
struct addrinfo **res); // 輸出:鏈結串列頭指標
NULL 配合 AI_PASSIVEaddrinfo 結構,指定想要的協議族、通訊類型等條件addrinfo 結構鏈結串列的指標。可能包含多個結果(IPv4/IPv6 多重位址),需遍歷嘗試struct addrinfo {
int ai_flags; // AI_PASSIVE, AI_CANONNAME, 等
int ai_family; // AF_INET, AF_INET6, AF_UNSPEC
int ai_socktype; // SOCK_STREAM, SOCK_DGRAM
int ai_protocol; // 0, IPPROTO_TCP, IPPROTO_UDP
socklen_t ai_addrlen; // ai_addr 大小
struct sockaddr *ai_addr; // 指向 sockaddr_in 或 sockaddr_in6
char *ai_canonname; // 規範主機名
struct addrinfo *ai_next; // 鏈結串列下一節點
};
int status;
struct addrinfo hints, *servinfo;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC; // IPv4 或 IPv6 皆可
hints.ai_socktype = SOCK_STREAM; // TCP
hints.ai_flags = AI_PASSIVE; // 自動填入本機 IP
// NULL 表示綁定本機所有介面
if ((status = getaddrinfo(NULL, "3490", &hints, &servinfo)) != 0) {
fprintf(stderr, "getaddrinfo error: %s\n", gai_strerror(status));
exit(1);
}
// servinfo 現在指向一個或多個 addrinfo 節點
// ... 後續建立 socket、bind、listen ...
freeaddrinfo(servinfo); // 記得釋放鏈結串列!
AF_UNSPEC:達成協議無關性,系統自動回傳 IPv4 與 IPv6 位址AI_PASSIVE:搭配 node=NULL,自動取得本機位址,避免硬編碼 IPgai_strerror():將錯誤碼轉為可讀字串,errno 不適用於此函數int status;
struct addrinfo hints, *servinfo;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
// 客戶端不需 AI_PASSIVE
status = getaddrinfo("www.example.net", "3490", &hints, &servinfo);
if (status != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(status));
exit(1);
}
// 遍歷 servinfo 鏈結串列嘗試連線...
freeaddrinfo(servinfo);
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <arpa/inet.h>
int main(int argc, char *argv[]) {
if (argc != 2) {
fprintf(stderr, "Usage: %s hostname\n", argv[0]);
return 1;
}
struct addrinfo hints, *res, *p;
int status;
char ipstr[INET6_ADDRSTRLEN];
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
if ((status = getaddrinfo(argv[1], NULL, &hints, &res)) != 0) {
fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(status));
return 2;
}
printf("IP addresses for %s:\n\n", argv[1]);
for (p = res; p != NULL; p = p->ai_next) {
void *addr;
char *ipver;
if (p->ai_family == AF_INET) { // IPv4
struct sockaddr_in *ipv4 = (struct sockaddr_in *)p->ai_addr;
addr = &(ipv4->sin_addr);
ipver = "IPv4";
} else { // IPv6
struct sockaddr_in6 *ipv6 = (struct sockaddr_in6 *)p->ai_addr;
addr = &(ipv6->sin6_addr);
ipver = "IPv6";
}
inet_ntop(p->ai_family, addr, ipstr, sizeof(ipstr));
printf(" %s: %s\n", ipver, ipstr);
}
freeaddrinfo(res);
return 0;
}

res 有效getaddrinfo() 是現代網路程式的統一入口,它屏蔽了協議細節,讓開發者專注於應用邏輯。掌握其 hints 設定與 res 鏈結串列遍歷模式,是撰寫健壯、可移植、支援 IPv6 網路程式的關鍵第一步。