服务器规范名字与IP地址的转换

使用名字代替地址的原由

出于许多理由,我们应该使用名字而不是数值:名字比较容易记住;数值地址可以变动而名 字保持不变;随着往IPv6上转移,数值地址变得相当长,手工键入数值地址更易出错。本章讲 述在名字和数值地址间进行转换的函数:gethostbyname和gethostbyaddr在主机名字与IPv4 地址之间进行转换,getservbyname和getservbyport在服务名字和端口号之间进行转换。本 章还讲述两个协议无关的转换函数:getaddrinfo和getnameinfo,分别用于主机名字和IP地 址之间以及服务名字和端口号之间的转换。

域名系统

域名系统(Domain Name System, DNS)主要用于主机名字与IP地址之间的映射。主机名既可以是一个简单名字(simple name),例如Solaris或bsdi,也可以是一个全限定域名(Fully Qualified Domain Name, FQDN),例如Solaris.unpbook. com。

资源记录
DNS中的条目称为资源记录(resource record, RR)。我们感兴趣的RR类型只有若干个。
A A记录把一个主机名映射成一个32位的IPv4地址。举例来说,以下是 unpbook. com域中关于主机freebsd的4个DNS记录,其中第一个是一个A记录:
freebsd IN A 12.106.32.254
IN AAAA 3ffe:b80:If8d:1:a00:20ff:fea7:686b
IN MX 5 freebsd.unpbook.com.
IN MX 10 mailhost.unpbook. com.

AAAA 称为“四A”(quad A)记录的AAAA记录把一个主机名映射成一个128位的IPv6 地址。选择“四A”这个称呼是由于128位地址是32位地址的四倍。

PTR 称为“指针记录” (pointer record)的PTR记录把IP地址映射成主机名。对于IPv4 地址,32位地址的4字节先反转顺序,每字节都转换成各自的十进制ASCII值(0〜 255)后,再添上in-addr.arpa,结果字符串用于PTR查询。
对于IPv6地址,128位地址中的32个四位组先反转顺序,每个四位组都被转换成 相应的十六进制ASCII值(0〜9, a〜f)后,再添上ip6.arpa。
举例来说,上例中主机freebsd的两个PTR记录分别是254.32.106.12 . in- addr. arpa 和 b.6.8.6.7.a.e.f.f.f.0.2.0.0.a.0.1.0.0.0.d.8.f.l.0.8. b.O.e.f.f.3.ip6.arpa。

MX MX记录把一个主机指定作为给定主机的"邮件交换器"(mail
exchanger)。上例中主机freebsd有2个MX记录:第一个的优先级值为5,第二个的优先级值为10。 当存在多个MX记录时,它们按照优先级顺序使用,值越小优先级越高。

CNAME CNAME代表acanonical name"(规范名字),它的常见用法是为常用的服务(例 如ftp和www)指派CNAME记录。如果人们使用这些服务名而不是真实的主机 名,那么相应的服务挪到另一个主机时他们也不必知道。举例来说,我们名为Linux的主机有以下2个CNAME记录:
ftp IN CNAME linux. unpbook. com.
www IN CNAME linux.unpbook.com.

解析器和名字服务器

每个组织机构往往运行一个或多个名字服务器(name server),它们通常就是所谓的BIND(Berkeley Internet Name Domain的简称)程序。诸如我们在本书中编写的客户和服务器等应用程 序通过调用称为解析器(resolver)的函数库中的函数接触DNS服务器。常见的解析器函数是将在本章讲解的gethostbyname和gethostbyaddr,前者把主机名映射成IPv4地址,后者则执行 相反的映射。

图11.1展示了应用进程、解析器和名字服务器之间的一个典型关系。现在考虑编写应用程 序代码。解析器代码通常包含在一个系统函数库中,在构造应用程序时被链编(link-editing) 到应用程序中。另有些系统提供一个由全体应用进程共享的集中式解析器守护进程,并提供向 这个守护进程执行RPC的系统函数库代码。不论哪种情况,应用程序代码使用通常的函数调用来执行解析器中的代码,调用的典型函数是gethostbyname和gethostbyaddr

gethostbyname和gethostbyaddr函数,查找主机名

查找主机名最基本的函数是gethostbyname。如果调用成功,它就返回一个指向hostent 结构的指针,该结构中含有所查找主机的所有IPv4地址。这个函数的局限是只能返回IPv4地址。

#include<netdb.h>
struct hostent *gethostbyname(const char *hostname);
	返回值:若成功则为非空指针,若出错则为NULL且设置h_errno

struct hostent{
	char *h_name;		/*主机的官方(规范)名称*/
	char **h_ailases;	/*指向指向别名的指针数组的指针*/
	int h_addrtype;		/*主机地址类型:AF_INET */
	int h_length;		/*地址长度:4 */
	char **h_addr_list;	/*指向指向IPV4地址数组指针的指针 */
}

在这里插入图片描述
gethostbyname与我们介绍过的其他套接字函数的不同之处在于:当发生错误时,它不设置errno变量,而是将全局整数变量h_errno设置为在头文件<netdb. h>中定义的下列常值之一:
HOST_NOT_FOUND;
TRY_AGA工N;
NO_RECOVERY;
NO_DATA (等同于NO_ADDRESS)。

NO_DATA错误表示指定的名字有效,但是它没有A记录。只有MX记录的主机名就是这样的 一个例子。
如今多数解析器提供名为hstrerror的函数,它以某个h_errno值作为唯一的参数,返回的是一个const char *指针,指向相应错误的说明。

gethostbyaddr函数使用一个二进制的IP地址找到相应的主机名,与gethostbyname的行为刚好相反。

#include<netdb.h>

struct hostent *gethostbyaddr(const char *addr, socklen_t len, int family);
	返回:若成功则为非空指针,若出错则为NULL且设置h_errno

addr参数实际上不是char *类型,而是一个指向存放IPv4地址的某个in_addr结构的指针; len参数是这个结构的大小:对于IPv4地址为4。family参数为AF_INET。

getservbyname和getservbyport函数,查找服务名

从名字到端口号的映射关系保存在一个文件中(通常是 /etc/services),那么即使端口号发生变动,我们需修改的仅仅是/etc/services文件中的某 一行,而不必重新编译应用程序。getservbyname函数用于根据给定名字查找相应服务。
赋予各个服务的端口号规范列表由IANA通过http://www.iana.org/assignments/port-num- bers维护。/etc/services文件通常包含IANA维护的规范赋值列表的某个子集。

#include<netdb.h>

struct servent *getservbyname(const char *servname, const char *protoname);
	返回:若成功则为非空指针,若出错则为NULL
struct servent{
	char *s_name;		/*规范服务名称*/
	char **s_aliases;	/*别名列表*/
	int s_port;			/*端口号,以网络字节序存储*/
	char *s_proto;		/*使用的协议*/
}

服务名参数servname必须指定。如果同时指定了协议(即protoname参数为非空指针),那么指定服务必须有匹配的协议。
servent结构中我们关心的主要字段是端口号。既然端口号是以网络字节序返回的,把它存放到套接字地址结构时绝对不能调用htons。

getsevbyport函数用于根据端口号和可选协议查找相应服务。

#include<netdb.h>

struct servent *getservbyport(int port,const char *protoname);
	返回:若成功返回非空指针,若出错返回NULL

注意:port参数的值必须为网络字节序

getaddrinfo函数

getaddrinfo函数能够处理名字到地址以及服务到端口这两种转换,返回的是一个sockaddr结构而不是一个地址列 表。这些sockaddr结构随后可由套接字函数直接使用。

#include<netdb.h>
int getaddrinfo(const char *hostname,const char *service,
	const struct addrinfo *hints,struct addrinfo **result);
	返回:若成功则为0,若出错则为非0
struct addrinfo{
int		ai_flags;			/* AI_PASSIVE, AI_CANONNAME */
int 	ai_family;			/* AF_xxx */
int 	ai_socktype;		/* S0CK_xxx */
int 	ai_protocol;		/* 0 or IPPROTO_xxx for IPv4 and IPv6 */
socklen_t ai_addrlen;		/* ai_addr的长度* /
char 	*ai_canonname;		/*ptr指向主机的规范名称*/
struct sockaddr *ai_addr;	/*ptr指向套接字地址结构*/
struct addrinfo*ai_next;	/*ptr指向链表中的下一个结构*/
}

其中hostname参数是一个主机名或地址串(IPv4的点分十进制数串或IPv6的十六进制数串)。service参数是一个服务名或十进制端口号数串。
hints参数可以是一个空指针,也可以是一个指向某个addrinfo结构的指针,调用者在这个结构中填入关于期望返回的信息类型的暗示。举例来说,如果指定的服务既支持TCP也支持UDP (例如指代某个DNS服务器的domain服务),那么调用者可以把hints结构中的ai_socktype成员 设置为SOCK_DGRAM,使得返回的仅仅是适用于数据报套接字的信息。
hints结构中调用者可以设置的成员有:
ai_flags (零个或多个或在一起的AI_xxx值);
ai_family (某个AF_xxx值);
ai_socktype (某个SOCK_xx值);
ai_protocol。

其中ai_flags成员可用的标志值及其含义如下。

  • AI_PASSIVE 套接字将用于被动打开。
  • AI_CANONNAME 告知getaddrinfo函数返回主机的规范名字。
  • AI_NUMERICHOST 防止任何类型的名字到地址映射,hostname参数必须是一个地址串。
  • AI_NUMERICSERV 防止任何类型的名字到服务映射, service参数必须是一个十进制端口号数串。
  • AI_V4MAPPED 如果同时指定ai_family成员的值为AF_INET6,那么如果没有可用的AAAA记录,就返回与A记录对应的IPv4映射的IPv6地址。
  • AI_ALL 如果同时指定AI_V4MAPPED标志,那么除了返回与AAAA记录对应的IPv6地址外,还返回与A记录对应的IPv4映射的IPv6地址。
  • AI_ADDRCONFIG 按照所在主机的配置选择返回地址类型,也就是只查找与所在主机回馈接口以外的网络接口配置的IP地址版本一致的地址。

如果hints参数是一个空指针,本函数就假设ai_flag、ai_socktype和ai_protocol的值均为0, ai_family的值为AF_UNSPEC。
如果本函数返回成功(0),那么由result参数指向的变量已被填入一个指针,它指向的是由其中的ai_next成员串接起来的addrinfo结构链表。可导致返回多个addrinfo结构的情形有以下两个。
(1)如果与hostname参数关联的地址有多个,那么适用于所请求地址族(可通过如小结构的 ai_family成员设置)的每个地址都返回一个对应的结构。
(2)如果service参数指定的服务支持多个套接字类型,那么每个套接字类型都可能返回一个对应的结构,具体取决于hints结构的ai_socktype成员。
(注意,getaddrinfo的多数实现认为只能按照由ai_socktype成员请求的套接字类型端口号数串到端口的转换,如果没有指定这个成员,那就返回一个错误。)

举例来说,如果在没有提供任何暗示信息的前提下,请求查找有2个IP地址的某个主机上的 domain服务,那将返回4个addrinfo结构,分别是:
第一个IP地址组合SOCK_STREAM套接字类型;
第一个IP地址组合S OCK_DGRAM套接字类型;
第二个IP地址组合SOCK_STREAM套接字类型;
第二个IP地址组合SOCK_DGRAM套接字类型。

在addrinfo结构中返回的信息可现成用于socket调用,随后现成用于适合客户的connect 或sendto调用,或者是适合服务器的bind调用。
在这里插入图片描述
最安全的做法是让getaddrinfo总是返回明确的协议值。
图11.6汇总了根据指定的服务名(可以是一个十进制端口号数串)和ai_socktype暗示信息为每个通过主机名查找获得的IP地址返回addrinfo结构的数目。
在这里插入图片描述
典型的服务器进程只指定service而不指定hostname,同时在hints结构中指定AI_PASSIVE标志。返回的套接字地址结构中应含有一个值为INADDR_ANY (对于IPv4 )或 IN6ADDR_ANY_INIT(对于IPv6)的IP地址。TCP服务器随后调用socket、bind和listen。

gai_strerror函数

图11-7给出了可由getaddrinfo返回的非0错误值的名字和含义。gai_strerror以这些值为它的唯一参数,返回一个指向对应的出错信息串的指针。

#include<netdb.h>
const char *gai_strerror(int error);
	返回:指向错误描述消息字符串的指针

在这里插入图片描述

freeaddrinfo函数

getaddrinfo返回的所有存储空间都是动态获取的(譬如来自malloc调用),包括 addrinfo结构、ai_addr结构和ai_canonname字符串。这些存储空间通过调用freeaddrinfo 返还给系统。

#include<netdb.h>

void freeaddrinfo(struct addrinfo *ai);

ai参数应指向由getaddrinfo返回的第一个addrinfo结构。这个链表中的所有结构以及由它们指向的任何动态存储空间(譬如套接字地址结构和规范主机名)都被释放掉。

图11-8汇总了getaddrinfo如何处理IPv4和IPv6地址。“结果” 一栏是在给定前三栏的变量后,该函数返回给调用者的结果。“行为” 一栏则说明该函数如何获取这些结果。

在这里插入图片描述

getnameinfo函数

getnameinfo是getaddrinfo的互补函数,它以一个套接字地址为参数,返回描述其中的主机的一个字符串和描述其中的服务的另一个字符串。

#include <netdb.h>
int getnameinfo (const struct sockaddr *sockaddr, socklen_t addrlen,
	char *host, socklen_t hostlen,
	char *serv, socklen_t servlen, int flags);
	返回:若成功则为0,若出错则为非0(见图11-7

待返回的2个直观可读字符串由调用者预先分配存储空间。调用者不想返回某一个值,则将对应的len置0。
图11-20中给出了6个可指定的标志,用于改变getnameinfo的操作。
在这里插入图片描述
NI_NOFQDN标志导致返回的主机名第一个点号之后的内容被截去。
对于这些标志有意义的组合(例如NI_DGRAM和NI_NUMERICHOST),可以把其中各个标志逻辑或在一起。

来源:UNIX网络编程卷一第十一章

  • 11
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值