09-Man 手册‎ > ‎

9.05. getaddrinfo(), freeaddrinfo(), gai_strerror()

取得主机名称或服务的资讯,并将结果载入 struct sockaddr。

函数原型
#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" 或者是 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 的 linked list,而你可以透过这个 list 取得全部的 addresses(符合你在 hints 中指定的 address 类型)。

现在可能会取得一些因为某些理由无法正常运作的 addresses,所以 Linux man  手册提供的方法是不断用回圈读取 list,然後试着调用 socket()丶connect()(如果以 AI_PASSIVE flag 设定 server,则是 bind()),直到成功为止。

最後,当你处理完 linked list 之後,你需要调用 freeaddrinfo() 来释放内存空间(否则会发生 memory leak,这会让有些人不安。)

返回值
成功时返回零,或错误时返回非零。若返回非零值,你可以代入 gai_strerror() 函数取得一个文字版的错误讯息。

范例

// 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);
}

// 不断运行 loop,直到我们可以连接成功
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) {
  // loop 已经运行到 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);
}

// 不断运行 loop,直到我们可以成功绑定
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) {
  // 整个 loop 运行结束,到了 linked-list 结尾都无法成功绑定(bind)
  fprintf(stderr, "failed to bind socket\n");
  exit(2);
}

freeaddrinfo(servinfo); // 使用完毕时释放这个数据结构的空间
参照
gethostbyname()getnameinfo()
Comments