/blog_posts/web_coding/6 名字与地址转换编程

6.1 概述

现在的网络中,都是使用名字来访问服务器的,而非使用地址来访问的。读者在此可能要问用地址不是也能访问吗,为什么还要转成名字呢?这个问题的答案很简单,试想下,如果是使用地址来访问服务器,那将要记住许许多多的数字,同时我们也很难记准确,这给我们造成了很大的不便,同时如果地址变了我们也要改变地址,但如果我们使用名字,我们就可以只记这个名字,这对使用是很方便的。

那它们是怎么转换的呢?答案就是将要在本章介绍名字与地址的转换函数: gethostbyname 和 gethostbyaddr 在主机名字与 IP 地址间进行转换, getservbyname 和 getservbyport 在服务名字和端口号间进行转换来实现的,并且还有介绍两个与协议无关的 getaddrinfo 和 getnameinfo 函数。通过本章的学习,相信读者就能有所了解了。

6.2 域名系统

域名系统就是通常说的 DNS(Domain Name System)系统,而 DNS 系统主要用于主机名与 IP 地址间的映射,同时还可以通过主机名或主机地址获取主机的相关信息。

那么在 DNS 系统中是怎样实现主机名与 IP 地址间的映射的呢?答案就是通过资源记录来实现的。在 DNS 中的条目称为资源记录RR(resource record) 。它们主要有以下几类:

A

A记录将一个域名地址对应于一个 32bit 的 IPv4 地址,比如:

NET IN A 202.121.68.5

NET是主机名称,是使用的一台主机。 IN 表示属于某种类型的记录, 202.121.68.5 是 IPv4 的地址。

AAAA

AAAA 记录(称为“四A”记录)将主机名映射为 128 位的 IPv6 地址。选择“四A”的称法是由 IPv6 的 128 位地址是 32 位地址的四倍。比如:

NET IN AAAA 551b:df00:ce3e:e200:20:800:2b37:6426

NS

NS记录用于指定一个域名服务器,它负责定义由哪个域名服务器负责管理维护本区域的记录。比如:

NET IN NS server.cuit.edu.cn

MX

MX记录用于指定一台主机的域名,所以发送到本域的电子邮件都由这台主机接收。比 如:

mail.cuit.edu.cn IN MX server.cuit.edu.cn
NET IN MX NET
    IN MX server.cuit.edu.cn

前一条记录的意思是指定由 server.cuit.edu.cn 主机来接收发送到 mail.cuit.edu.cn 这个域的 电子邮件。后两条记录的意思是发送到 NET.cuit.edu.cn 的电子邮件首先由自己接收,如果失败再由主机 server.cuit.edu.cn 接收。

PTR

PTR 记录(称为“指针记录”)将 IP 地址映射为主机名。对于 IPv4 地址,32 位地址的四个字节顺序反转,每个字节都转换成它的十进制 ASCII 值(0~255 ),然后附上 in_addr.arpa,结果串用于 PTR 查询。对于 IPv6 地址, 128 位地址中的 32 个 4 位组顺序反转 ,每组被转换成相应的十六进制 ASCII 值(0~9,a~f ),并附上 ip6. int 比如:

NET IN PTR 5.68.121.202.in addr.arpa
    IN PTR 3.e.3.e.8.7.0.2.0.0.8.0.0.2.0.0.0.0.2.e.e.3.e.c.0.0.f.d.b.f.f.5.ip6.int

前一条是IPv4 的 PTR 记录,后一条是 IPv6 的 PTR 记录。

CNAME

CNAME 代表“ canonical name(规范名字)”,其作用是允许主机建立别名。如果人们使用这些别名 而不是实际上的主机名,则它在服务挪到其他主机上时是透明的。例如,主机 NET 的 CNAME 。如下:

ftp IN CANME NET.cuit.edu.cn.

6.3 gethostbyname 函数★

计算机通常是通过可读的名字被人们认识的,到现在为止,本书中的所有例子都有意使用 IP 地址而不是名字,所以应该确切地知道,对于诸如 connctt 和 sendto 这样的函数,有什么东西进入了套接口地址结构,而对于诸如 accept 和 recvfrom 这样的函数,它们返回的是什么我们也知道。但是,大多数应用程序应该处理名字而不是地址。当往 IPv6 转移时,这显得尤为正确和重要,因为 IPv6 地址十六进制数串 比 IPv4 点分十进制数串要长得多。

gethostbyname 函数

在本节介绍查找主机名最基本的函数是 gethostbyname ,该函数执行如果成功,它返回一个指向结构 hostent 的指针,该结构中包含了该主机的所有 IPv4 地址或 IPv6 地址。如果失败返回空指针。

NET.cuit.edu.cn
# include <netdb.h>
struct hostent *gethostbyname(const char *hostname);

参数 hostname 是主机的域名地址, 函数将查询的结果作为参数返回。如果失败返回空指针,如果成功此参数返回的非空指针指向如下的 hostent 结构 ,如图 6-1 所示是 hostent 结构实例。

hostent 结构

struct hostent{
    char *h_name; 主机的正式名称
    char **h_aliases; 主机的别名列表
    int h_addrtype; 主机地址类型
    int h_length; 主机地址长度
    char **h_addr_list; 主机 IP 地址的列表
}
# define h_addr h_addr_list[0] 在列表中的第一个地址

现在对以上结构体稍作介绍 。

  • h_name 用于存放主机的正式名称。
  • h_ aliases 用于存放该主机可能具有的多个别名,就像该主机提供多个网络服务时,将使用多个不同的域名地址别名。
  • 在 IPv4 下,h_addrtyped 的值为 AF_INET, 而在 IPv6 下为 AF_INET6。
  • 在 IPv4 下 h_length 的值是 4 字节地址信息,而在 IPv6 下 h_lengt h 的值是 16 字节地址信息。
  • h_addr_list 是以字符串形式存放的 IP 地址。

图 6-1 显示了一个 hostent 结构 IPv4 的示例。

1622191894683

gethostbyname 的解析过程

下面讨论 gethostbyname 函数是怎样工作的呢?原来 gethostbyname 函数首先在 /etc/hosts 文件查找是否有匹配的主机名。如果没有,则根据在域名解析配置文件 /etc/resolv.conf 中指定的本地域名服务器的地址,向本地域名服务器发送地址解析请求。如果本地域名服务器能够解析,则返回 UDP 数据包说明结果,否则本地域名服务器将向上一层的域名服务器发送域名解析请求。 如图 6-2 显示 gethostbyname 查找地址的过程。

1622213901790

发生错误时

在介绍过 gethostbyname 函数解析过程后,现在来详细了解它。 gethostbyname 函数和以前的那些套接口函数不同之处是:当发生错误时,它不设置 errno ,而是将全局整数 h_errno 设置为定义在头文件<netdb.h>中的下列常值中的一个:

  • HOST_NOT_FOUND :指定的域名地址不可知。
  • TRY_AGAIN:域名服务器发生错误,程序可以过一段时间再次查询。
  • NO_RECOVERY:域名服务器发生错误。
  • NO_ADDRESS(等同于NO_DATA):指定的域名地址有效,但它既没有A记录,也没有AAAA记录。

NO_RECOVERY 和 TRY_AGAIN 说明了域名服务器现在错误状态的严重程度,对于 TRY_AGAIN ,说明域名服务器发生了小的可恢复的错误,而 NO_RECOVERY ,通常是比较严重的错误。

实例

下面举个例子来看看 gethostbyname 函数(如图 6-2 所示):

1622214085846

1622214097624这个程序比较简单,让我们一起来了解一下:

第 4 行,在main 函数中有两个参数,形参argc 是指命令行中参数的个数(文件名也作为一个参数),形参argv 是一个指向字符串的指针数组。

第 9~12 行,当命令行执行的命令和规则时,查看是否输入了主机地址。

第 13~17 行,获取主机名并进行判断,如果出错就跳出程序。

第 21 行,复制主机地址。

第 22~25 行,输出主机地址。

通过以上程序,便可以获得主机的主机名,别名等相关信息了!这是一个很有用的程序!

6.6 gethostbyaddr 函数★

与 6.3 节中介绍的类似, gethostbyaddr 函数的作用是可以查询指定的 IP 地址对应的主机域名地址。函数的形式如下:

# include <netdb.h>
struct hostent *gethostbyaddr (const char *addr, size_t len, int family);

返回:非空指针 成功,空指针 出错,同时设置 h_errno。

该函数同样返回一个指向结构 hostent 的指针。

而在参数中,参数 addr 不是 char * 类型,而是一个真正指向含有 IPv4 或 IPv6 地址的结构 in_addr 或 in6_addr 的指针; len 是此结构的大小:对于 IPv4 地址为 4 ,对于 IPv6 地址为 16 ;参数 family 或为 AF_INET 或为 AF_INET6 。

通过这个函数可以在 /etc/named conf 文件中的域 in_add.arpa 中为 IPv4、IPv6 提供地址在域名系统中的查询 PTR 纪录的功能。


虽然 gethostbyaddr 函数能提供对于 IPv4 和 IPv6 的支持,但是对于 IPv6 仍有一些差别的,这些差别可以通过以下三点体现出来:

  • 如果family是AF_INET6,1en是16,且地址是IPv4映射的IPv6地址,则在域in_addr.arpa中查找地址的低32位(IPv4地址部分)。
  • 如果family是AF_INET6,1en是16,且地址是IPv4兼容的IPv6地址,则在域in_addr.arpa中查找地址的低32位(IPv4地址部分)。
  • 如果被查找的是IPv4地址(或参数family为AF_INET,或上述两种情况中的一个成立)且设置解析器选项RES_USE_INET6,则返回的地址被转换为一个IPv4映射的IPv6地址,即是h_addrtype为AF_INET6,h_length为16。

对于 gethostbyaddr 函数,一般的用户就调用它来检查所返回 hostent 结构的 h_name 成员可能还有别名 。在第 6.14 节介绍它的一个例子。

6.7 uname 函数

该函数功能如同它的名字一样,就是返回当前主机的名字,它返回的信息比较完整,会在下一节看到也是一样返回主机名字的另一函数,但它返回的信息就相对少点了。

6.8 gethostname 函数

该函数同样返回当前主机的名字,但返回信息比上节介绍的函数少,该函数形式如下:

# include <unistd.h>
int gethostname char(* name , size_t namelen);

6.9 getservbyname 和 getservbyport 函数

getservbyname 函数

在这节讨论的函数 getservbyname 可以通过服务名来获取服务器端口号。

getservbyport 函数

下一个讨论的函数是 getservbyport 函数,该函数的作用是通过端口号来获取服务名,它是反向的查询服务名称即是功能与 getservbyname 函数相反。