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

Beej's Guide to Network Programming 系列連載 08:getaddrinfo() — 現代網路程式的基石

     編輯:關於C語言

前言:告別手動填結構的時代

早期網路程式設計需手動呼叫 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);     // 輸出:鏈結串列頭指標

關鍵輸入參數

  • node:目標主機名稱或 IP 字串。伺服器端綁定本機時傳 NULL 配合 AI_PASSIVE
  • service:埠號字串("3490")或標準服務名("http"、"ftp"、"telnet"、"smtp")
  • hints:預先填好的 addrinfo 結構,指定想要的協議族、通訊類型等條件

輸出參數

  • res:指向 addrinfo 結構鏈結串列的指標。可能包含多個結果(IPv4/IPv6 多重位址),需遍歷嘗試

addrinfo 結構體定義

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,自動取得本機位址,避免硬編碼 IP
  • gai_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);

完整示範程式:showip.c — 列印主機所有 IP 位址

#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;
}

流程圖解:getaddrinfo 呼叫與鏈結串列遍歷

getaddrinfo 流程圖

重要提醒與最佳實踐

  1. 必須檢查回傳值:非零即失敗,勿假設 res 有效
  2. 遍歷鏈結串列:第一個節點不一定可用,需嘗試每個節點直到成功
  3. 記得 freeaddrinfo():避免記憶體洩漏
  4. IPv6 測試環境:國內 IPv6 部署程度不一,測試時請確認環境支援

結語

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

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