# getaddrinfo(), freeaddrinfo(), gai\_strerror()

取得主機名稱或服務的資訊，並將結果載入 struct sockaddr。

## 函式原型

```c
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>

int getaddrinfo(const char *nodename, const char *servname,
                const struct addrinfo *hints, struct addrinfo **res);

void freeaddrinfo(struct addrinfo *ai);

const char *gai_strerror(int ecode);

struct addrinfo {
  int ai_flags; // AI_PASSIVE, AI_CANONNAME, ...
  int ai_family; // AF_xxx
  int ai_socktype; // SOCK_xxx
  int ai_protocol; // 0 (auto) or IPPROTO_TCP, IPPROTO_UDP

  socklen_t ai_addrlen; // ai_addr 的長度
  char *ai_canonname; // canonical name for nodename
  struct sockaddr *ai_addr; // 二進制格式位址
  struct addrinfo *ai_next; // linked list 中的下個資料結構
};
```

## 說明

getaddrinfo() 是很優秀的函式，可以傳回特別的主機名稱資訊（比如它的 IP address）以及為你載入 struct sockaddr，要注意一些細節（像是 IPv4 或 IPv6）。它會取代舊有的 gethostbyname() 及 getservbyname() 函式。下列的說明包含許多資訊，可能看起來有點難，但實際上卻很簡單。先看看範例是值得的。

你感興趣的 host name 在 node name 參數中，address 可以是個 host name，像 "[www.example.com](http://www.example.com)" 或者是 IPv4 或 IPv6 位址（以字串傳遞）。如果你使用 AI\_PASSIVE flag，那這個參數也可以是 NULL（參考下面）。

servname 參數基本上就是 port number，它可以是個 port number（以字串傳遞，如 "80"），或者是個 service name，像是 "http"、"tftp"、"smtp"、或 "pop" 等。常見的 service name 可以在 IANA Port List \[42] 或你的 /etc/services 中找到。

最後的 input 參數是 hints，這就是你要定義 getaddinfo() 函式要做什麼事的地方，使用以前先用 memset() 將整個資料結構清為零，在用它之前，我們先看一下需要設定的欄位。

ai\_flags 可以設定成各種東西，但是這邊有件重要的事情。（可以用 OR 位元運算［｜］指定多個 flags）完整的 flags 清單請參考你的 man 使用手冊。

AI\_CANONNAME 會讓 ai\_canonname 填上主機的 canonical (real) name，AI\_PASSIVE 讓 IP address 填上 INADDR\_ANY （IPv4）或 in6addr\_any（IPv6）；這讓之後在呼叫 bind() 時，可以自動用目前 host 的 address 來填上 struct sockaddr 的 IP address。這在設定 server 且你不想要寫死 address 時非常好用。

如果你使用 AI\_PASSIVE flag，那麼你可以在 nodename 中傳遞 NULL（因為 bind() 在之後會幫你填上）

\[42] <http://www.iana.org/assignments/port-numbers>

繼續談輸入的參數，你應該會想要將 ai\_family 設定為 AF\_UNSPEC，這樣可以讓 getaddrinfo() 知道 IPv4 與 IPv6 addresses 都需要查詢。你也能自己以 AF\_INET 或 AF\_INET6 自訂要使用 IPv4 或 IPv6。

再來，socktype 欄位應該要設定為 SOCK\_STREAM 或 SOCK\_DGRAM，取決與你需要哪種類型的 socket。

最後，你可以將 ai\_protocol 保留為 0，可以自動選擇你的 protocol type。

在你取得全部的東西之後，你終於可以呼叫 getaddrinfo() 了！

當然，這個地方開始有趣了，res 會指向一個 struct addrinfo 的鏈結串列，而你可以透過這個串列取得全部的 addresses（符合你在 hints 中指定的 address 類型）。

現在可能會取得一些因為某些理由無法正常運作的 addresses，所以 Linux man 使用手冊提供的方法是不斷用迴圈讀取串列，然後試著呼叫 socket()、connect()（如果以 AI\_PASSIVE flag 設定 server，則是 bind()），直到成功為止。

最後，當你處理完鏈結串列之後，你需要呼叫 freeaddrinfo() 來釋放記憶體（否則會發生 memory leak，這會讓有些人不安。）

## 傳回值

成功時傳回零，或錯誤時傳回非零值。若傳回非零值，你可以代入 gai\_strerror() 函式取得一個文字版的錯誤訊息。

## 範例

```c
// client 連線到 server 的程式碼
// 透過 stream socket 連線到 www.example.com 的 port 80 (http)
// 不是 IPv4 就是 IPv6

int sockfd;
struct addrinfo hints, *servinfo, *p;
int rv;

memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC; // 設定 AF_INET6 表示強迫使用 IPv6
hints.ai_socktype = SOCK_STREAM;

if ((rv = getaddrinfo("www.example.com", "http", &hints, &servinfo)) != 0) {
  fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
  exit(1);
}

// 不斷執行迴圈，直到我們可以連線成功
for(p = servinfo; p != NULL; p = p->ai_next) {
  if ((sockfd = socket(p->ai_family, p->ai_socktype,　p->ai_protocol)) == -1) {
      perror("socket");
      continue;
  }

  if (connect(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
    close(sockfd);
    perror("connect");
    continue; 
  }

  break; // if we get here, we must have connected successfully
}

if (p == NULL) {
  // 迴圈已經執行到 list 的最後，都無法連線
  fprintf(stderr, "failed to connect\n");
  exit(2);
}

freeaddrinfo(servinfo); // 釋放 servinfo 記憶體空間

// code for a server waiting for connections
// namely a stream socket on port 3490, on this host's IP
// either IPv4 or IPv6.

int sockfd;
struct addrinfo hints, *servinfo, *p;
int rv;

memset(&hints, 0, sizeof hints);
hints.ai_family = AF_UNSPEC; // 使用 AF_INET6 表示一定要用 IPv6
hints.ai_socktype = SOCK_STREAM;
hints.ai_flags = AI_PASSIVE; // 使用我的 IP address

if ((rv = getaddrinfo(NULL, "3490", &hints, &servinfo)) != 0) {
  fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(rv));
  exit(1);
}

// 不斷執行迴圈，直到我們可以成功綁定
for(p = servinfo; p != NULL; p = p->ai_next) {
  if ((sockfd = socket(p->ai_family, p->ai_socktype,
      p->ai_protocol)) == -1) {
    perror("socket");
    continue;
  }

  if (bind(sockfd, p->ai_addr, p->ai_addrlen) == -1) {
    close(sockfd);
    perror("bind");
    continue;
  }

  break; // 若執行到這行，表示我們一定已經成功連線
}

if (p == NULL) {
  // 整個迴圈執行完畢，到了 linked-list 結尾都無法成功綁定（bind）
  fprintf(stderr, "failed to bind socket\n");
  exit(2);
}

freeaddrinfo(servinfo); // 使用完畢時釋放這個資料結構的空間
```

## 參考

gethostbyname(), getnameinfo()


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://beej-zhcn.netdpi.net/man/getaddrinfo-_freeaddrinfo-_gaistrerror.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
