首页 > 代码库 > apue和unp的学习之旅11——名字与数值地址转换

apue和unp的学习之旅11——名字与数值地址转换

//-------------------------------------1.为什么使用名字好-----------------------------------------

我们应该使用名字而不是数值来标识主机(例如数值206.6.226.33),服务器(例如端口13代表标准的daytime服务器),然而出于以下几个理由,我们应该使用名字而不是数值:

1).名字好记住

2).数值地址可以变动而名字保持不变

3).随着往IPv6上转移,数值地址变得相当长,手工键入地址很容易出错。


//-----------------------------------------2.域名系统---------------------------------------------

域名系统(Domain Name System,DNS)主要用于主机名字与IP地址之间的映射。

主机名:可以是一个简单名字,例如solaris或bsdi,也可以是一个全限定域名(Fully Qualified Domain Name,FQDN),例如solaris.unpbook.com。

DNS中的条目称为资源记录(resource record,RR)

A记录: A记录把一个主机名映射成一个32位的IPv4地址。

举例来说,以下是unpbook.com域中关于主机solaris的4个DNS记录,其中第一个是一个A记录:


AAAA记录:称为“四A”记录的AAAA记录把一个主机名映射成一个128位的IPv6地址。

PTR记录:称为指针记录的PTR记录把IP地址映射成主机名。

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


//-------------------------------------3.解析器和名字服务器---------------------------------------

每个组织机构往往运行一个或多个名字服务器,它们通常就是所谓的BIND(Berkley Internet Name Domain程序)。

诸如在本书编写的客户和服务器程序通过调用称为解析器(resolver)的函数库中的函数接触DNS服务器。常见的解析器函数有gethostbyname和gethostbyaddr。

下图展示了应用进程,解析器和名字服务器之间的一个典型的关系。


解析器代码通过读取其系统相关配置文件确定本组织机构的名字服务器们的所在位置(上图只展示了一个服务器,大多数组织机构运行多个名字服务器,处于可靠和冗余的目的,必须要设置多个名字服务器)文件/etc/resolv.conf通常包含本地名字服务器主机的IP地址。

解析器使用UDP向本地名字服务器发出查询,如果本地名字服务器不知道答案,它通常就会使用UDP在整个因特网上查询其他名字服务器,如果答案太长,超出了UDP消息的承载能力,本地名字服务器和解析器会自动切换到TCP。


//-------------------------------------4.gethostbyname函数---------------------------------------------

struct hostent

{

     char*  h_name;

     char** h_aliases;

     int    h_addrtype;

     int    h_length;

     int**  h_addr_list;

};


#include <netdb.h>

struct hostent* gethostbyname(const char* hostname);

                                          // ret:若成功则为非空指针,若出错则为NULL且设置h_errno


如果调用成功就返回一个指向hostent结构的指针,该结构中含有所查找主机的所有ipv4地址,这个函数的局限是只能返回IPv4地址。按照DNS的说法,gethostname执行的是对A记录的查询,它只能返回IPv4地址。即使该主机有IPv6地址,返回的也仅仅是IPv4地址。


//-------------------------------------5.gethostbyaddr函数----------------------------------------

#include <netdb.h>

struct hostent* gethostbyaddr(const char* addr, socklen_t len, int family);

                                           // ret:若成功则为非空指针,若出错则为NULL且设置h_errno


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

按照DNS的说法,gethostbyaddr在in_addr.arpa域中向一个名字服务器查询PTR记录。


//--------------------------------6.getservbyname函数和getservbyport--------------------------------

如果程序通过名字而不是端口号来指代一个服务,而且从名字到端口号的映射关系保存在一个文件中(/etc/services),那么即使端口号发生变动,我么你需要改动的仅仅是/etc/services文件中的某一行,而不必重新编译应用程序。getservbyname函数用于根据给定名字查找相应服务。

#include <netdb.h>

struct servent* getservbyname(const char* servname, const char* protoname);

                                         // ret:若成功则为非空指针,若出错则为NULL

struct servent

{

     char*  s_name;

     char** s_aliases;

     int    s_port;

     char*  s_proto;

};


注意,既然端口号已经是以网路字节序返回的,把它存放到套接字地址结构时绝对不能再调用htons。


#include  <netdb.h>

struct servent* getservbyport(int port, const char* protoname);


注意,port必须是网络字节序。


//-----------------------------------------7.getaddrinfo函数----------------------------------------

#include <netdb.h>

int  getaddrinfo(const char* hostname, const char* service, const struct addrinfo* hints, struct addrinfo** result);                      

                                                     //ret: 若成功则返回0,若出错则为非0

本函数通过result指针参数返回一个指向addrinfo结构链表的指针,而addrinfo结构定义在头文件<netdb.h>中。

struct addrinfo

{

     int ai_flags;

     int ai_famlily;

     int ai_socktype;

     int ai_protocol;

     socklen_t ai_addrlen;

     char* ai_canonname;

     struct sockaddr* ai_addr;

     struct sockaddr* ai_next; 

};

其中hostname参数是一个主机名或地址串(IPv4的点分十进制串或IPv6的十六进制数串),service参数是一个服务名或十进制端口号数串。

hints参数可以是一个空指针,也可以是一个指向某个addr_info结构的指针,调用者在这个结构中填入关于期望返回的信息类型的暗示。


//------------------------------------8.gai_strerror函数-----------------------------------------

#include <netdb.h>

const char* gai_strerror(int error);

                                                       // ret:指向错误描述信息字符串的指针


//-------------------------------------9.getnameinfo函数-----------------------------------------

#include <netdb.h>

int getnameinfo(const struct sockaddr* sockaddr, socklen_t addrlen, char* host, socklen_t hostlen,

char* serv, socklen_t servlen, int flag);

                                                       // ret:若成功则为0,若出错则非0

sockaddr指向一个套接字地址结构,其中包含待转换成直观可读的字符串的协议地址,该结构及其长度通常由accept,recvfrom,getsockname或getpeername返回。

待返回的2个直观可读字符串由调用者预先分配存储空间,host和hostlen指定主机字符串,serv和servlen指定服务字符串。如果调用者不想返回主机字符串,那就指定hostlen为0,同样,把servlen指定0就是不想返回服务字符串。

sock_ntop和getnameinfo的差别在于,前者不涉及DNS,只返回IP地址和端口号的一个可显示版本,后者通过尝试获取主机和服务的名字。