# 网络，第 2 部分：使用 getaddrinfo

> 原文：<https://github.com/angrave/SystemProgramming/wiki/Networking%2C-Part-2%3A-Using-getaddrinfo>

## 如何使用`getaddrinfo`将主机名转换为 IP 地址？

函数`getaddrinfo`可以将人类可读域名（例如`www.illinois.edu`）转换为 IPv4 和 IPv6 地址。实际上它将返回 addrinfo 结构的链表：

```c
struct addrinfo {
    int              ai_flags;
    int              ai_family;
    int              ai_socktype;
    int              ai_protocol;
    socklen_t        ai_addrlen;
    struct sockaddr *ai_addr;
    char            *ai_canonname;
    struct addrinfo *ai_next;
};
```

它非常易于使用。例如，假设您想在 [www.bbc.com](http://www.bbc.com) 中找到网络服务器的数字 IPv4 地址。我们分两个阶段完成。首先使用 getaddrinfo 构建可能的连接的链表。其次使用`getnameinfo`将二进制地址转换为可读形式。

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

struct addrinfo hints, *infoptr; // So no need to use memset global variables

int main() {
  hints.ai_family = AF_INET; // AF_INET means IPv4 only addresses

  int result = getaddrinfo("www.bbc.com", NULL, &hints, &infoptr);
  if (result) {
    fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(result));
    exit(1);
  }

  struct addrinfo *p;
  char host[256],service[256];

  for(p = infoptr; p != NULL; p = p->ai_next) {

    getnameinfo(p->ai_addr, p->ai_addrlen, host, sizeof(host), service, sizeof(service), NI_NUMERICHOST);
    puts(host);
  }

  freeaddrinfo(infoptr);
  return 0;
}
```

典型输出：

```
212.58.244.70
212.58.244.71 
```

## [www.cs.illinois.edu](http://www.cs.illinois.edu) 如何转换为 IP 地址？

魔法！不用说，使用称为“DNS”（域名服务）的系统。如果计算机没有在本地保存答案，则它会将 UDP 数据包发送到本地 DNS 服务器。该服务器又可以查询其他上游 DNS 服务器。

## DNS 安全吗？

DNS 本身很快但不安全。 DNS 请求未加密，容易受到“中间人”攻击。例如，咖啡店互联网连接可以轻易地破坏您的 DNS 请求并发回特定域的不同 IP 地址

## 如何连接到 TCP 服务器（例如 Web 服务器？）

TODO 连接到远程计算机需要三个基本系统调用：

```
getaddrinfo -- Determine the remote addresses of a remote host
socket  -- Create a socket
connect  -- Connect to the remote host using the socket and address information 
```

`getaddrinfo`调用成功，创建`addrinfo`结构的链接列表，并将给定指针设置为指向第一个。

套接字调用创建一个传出套接字并返回一个描述符（有时称为“文件描述符”），可以与`read`和`write`等一起使用。在这种意义上，它是`open`的网络模拟打开文件流 - 除了我们尚未将套接字连接到任何东西！

最后，connect 调用尝试连接到远程计算机。我们传递原始套接字描述符以及存储在 addrinfo 结构中的套接字地址信息。存在不同种类的套接字地址结构（例如，IPv4 与 IPv6），其可能需要更多存储器。因此，除了传递指针外，还传递了结构的大小：

```c
// Pull out the socket address info from the addrinfo struct:
connect(sockfd, p->ai_addr, p->ai_addrlen)
```

## 如何释放为 addrinfo 结构的链表分配的内存？

作为最顶层`addrinfo`结构上的清理代码调用`freeaddrinfo`的一部分：

```c
void freeaddrinfo(struct addrinfo *ai);
```

## 如果 getaddrinfo 失败，我可以用`strerror`打印出错误吗？

没有。`getaddrinfo`的错误处理有点不同：

*   返回值 _ 是 _ 的错误代码（即不使用`errno`）
*   使用`gai_strerror`获取等效的短英文错误文本：

```c
int result = getaddrinfo(...);
if(result) { 
   const char *mesg = gai_strerror(result); 
   ...
}
```

## 我可以只请求 IPv4 或 IPv6 连接吗？仅 TCP？

是!使用传递给`getaddrinfo`的 addrinfo 结构来定义您想要的连接类型。

例如，要通过 IPv6 指定基于流的协议：

```c
struct addrinfo hints;
memset(hints, 0, sizeof(hints));

hints.ai_family = AF_INET6; // Only want IPv6 (use AF_INET for IPv4)
hints.ai_socktype = SOCK_STREAM; // Only want stream-based connection
```

## 那些使用`gethostbyname`的代码示例呢？

旧函数`gethostbyname`已弃用;这是将主机名转换为 IP 地址的旧方法。端口地址仍需要使用 htons 功能手动设置。使用较新的`getaddrinfo`编写支持 IPv4 和 IPv6 的代码要容易得多

## 这很容易！？

是的，不是。创建一个简单的 TCP 客户端很容易 - 但网络通信提供了许多不同的抽象级别，并且可以在每个抽象级别设置几个属性和选项（例如我们没有谈到可以操作选项的`setsockopt`插座）。有关详细信息，请参阅[指南](http://www.beej.us/guide/bgnet/output/html/multipage/getaddrinfoman.html)。