网络编程中各种名字与地址的转换

from: http://flyinwoods.blog.163.com/blog/static/937640742008991060317/

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

注意: 严格说来,FQDN也称为绝对名字,而且必须以一个点号结尾,不过用户往往省略结尾的点号。这个点号告知DNS解析器该名字是全限定的,从而不必搜索解析器自己维护的可能域名清单。

例子:
      freebsd       IN    A         12.106.32.254
                  IN    AAAA      3ffe:b80:1f8d:1:a00:20ff:fea7:686b
                  IN    MX        5 freebsd,unpbook.com.
                  IN    MX        10 mailhost.unpbook.com.

DNS中的条目称为资源记录(Resource Record 简称RR )。常见的有如下几个:
A:    A纪录是把一个主机名映射为一个32位的IPv4地址。
       例如:    freebsd IN A 12.106.32.254

AAAA:     称为“四A(quad A)”记录的AAAA记录把一个主机名映射为一个128位的IPv6地          址。比如: freebsd

PTR:      “指针记录(pointer record )”,把IP地址映射为主机名。对于IPv4地址,32位地址的4个字节先反转顺序,没个字节都转换成各自的十进制ASCII值(0--255)后,再添上in-addr.arpa, 结果字符串用于PTR查询。    比如上面的freebsd的PTR记录为:254.32.106.12.in-addr.arpa
          对于IPv6地址,128位地址中的32个4位组先反转顺序,每个4位组都被转换成相应的16进制ASCII(0-9,a-f)后,再添上ip6.arpa.如:
          b.6.8.6.7.a.e.f.f.f.0.2..0.a.0.1.0.0.0.d.8.f.1.0.8.b.0.e.f.f.3.ip6.arpa。
        
MX:       MX记录把右侧给定的主机指定为左侧给定的主机的“邮件交换器(mail.exchanger)”。见上面的例子:第一个优先级为5,第二个优先级为10.当存在多个MX记录时,按照优先级顺序使用,值越小优先级越高。

CNAME:        代表“canonical name(规范名字)”,它的常见用法是为常用的服务(如ftp和www)指派CNAME记录。如果使用这些服务而不是真实的主机名,那么相应的服务挪到另一个主机时也不必知道。比如: 
              ftp IN    CNAME     linux.uupbook.com.
              www IN    CNAME     linux.unpbook.com.
         
常见的解析器函数是gethostbyname和gethostbyaddr: 前者把主机名映射成IPv4地址,后者执行相反的操作。
解析器使用UDP向本地名字服务器发出查询,如果本地名字服务器不知道答案,它通常就会使用UDP在整个因特网上查询其他名字服务器。如果答案太长,超出了UDP消息的承载能力,本地名字服务器和解析服务器会自动切换到TCP.


#include<netdb.h>
struct    hostent *gethostbyname( const char *hostname );
成功:返回非空指针
出错:返回空指针,同时设置h_errno。
这个函数的局限在于只能返回IPv4地址,但是getaddrinfo函数能够返回同时处理IPv4和IPv6地址。

结构hostent如下:

struct    hostent
{
      char      *h_name;          // 主机官方名,也就是规范名
      char      **h_aliases;      //    主级别名列表
      char      h_addrtype;       //    主机地址类型: AF_INET
      int       h_length;         //    地址长度:4
      char      **h_addr_list;    //    IPv4地址列表
};

gethostbyname执行的是对A记录的查询。只能返回IPV4地址。
注意:所查询的正是主机名和所有别名都是以空字符结尾的C字符串。
当发生错误时,不设置errno变量,而是将全局整数变量h_errno设置为在头文件<netdb.h>中定义的下列常量之一:
      HOST_NOT_FOUND
      TRY_AGAIN
      NO_RECOVERY
      NO_DATA(等同于NO_ADDRESS)
NO_DATA错误表示指定的名字有效,但是他没有A记录。只有MX记录的主机名就是这样。

hstrerror函数以某个h_errno值作为唯一的参数,返回的是一个const char *指针,指向相应的错误的说明。


#include<netdb>
struct    hostent *gethostbyaddr( const char *addr, socklen_t len, int family );
成功: 返回非空指针
出错: 返回空指针,同时设置h_errno
该函数试图由一个二进制的IP地址找到相应的主机名,与gethostbyname的行为刚好相反。
addr参数实际上不是char *类型,而是一个指向存放IPv4地址的某个in_addr结构的指针;len参数实际上不是这个结构的大小;对于IPv4地址为4.family参数为AF_IENT。

gethostbyaddr在in_addr.arpa域中向一个名字服务器查询PTR记录。


服务业通常靠名字来认知,getservbyname函数用于根据给定名字查找相应服务。

#include<netdb.h>
struct    servent *getservbyname( const char *servname, const char *protoname );
成功:返回非空指针;
失败:空指针;
本函数返回非空指针

struct    servent
{
      char      *s_name;          //    服务的正是名字
      char      **s_aliases;      //    别名列表
      int       s_port;           //    服务端口号
      char      *s_proto;         //    使用的协议
};

服务名参数servname必须指定。如果同时指定了协议(即protoname参数为非空指针),那么指定的服务必须有匹配的协议。有些因特网服务既用于TCP也用于UDP提供(如DNS)。如果protoname没有指定而servname指定服务支持多个协议,那么返回那个端口号取决于实现。一般来说,支持多个协议的服务旺旺使用相同的TCP端口号和UDP端口号。

servent结构中关心的主要是端口号。
注意: 该端口号是以网络字节的顺序返回的,因此把它存放到套接口地质结构时绝对不能调用htons。


函数getservbyport用于根据给定端口号和可选协议查找相应的服务。
#include<netdb.h>
struct    servent *getservbyport( int port, const char *protoname );
返回: 非空指针表示成功, 空指针表示失败。
注意: port的值必须为网络字节序,
ptr = getservbyport( htons(53), "udp");     // DNS using UDP


getaddrinfo:

#include<netdb.h>
int getaddrinfo( const char *hostname, const char *service, const struct addrinfo *hints, struct addrinfo **result );
返回: 0 -- 成功;    非0 -- 出错。

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

struct    addrinfo
{
      int       ai_flags;
      int       ai_family;                // AF_XXX
      int       ai_socktype;              //    SOCK_XXX
      int       ai_protocol;              //    0 或 IPPROTO_XXX
      socklen_t     ai_addrlen;           //    ai_addr的长度
      char          *ai_canonname;        //  
      struct    sockaddr      *ai_addr;
      struct    addrinfo      *ai_next;
};

该函数中hostname参数是一个主机名或地址串,(IPv4 的点分十进制或者IPv6的十六进制数串)。 service参数是一个服务名或十进制端口号数串。
hints参数可以是一个空指针,也可以是一个指向某个addrinfo结构的指针,调用者在这个结构中填入关于期望返回的信息类型的暗示。比如如果指定的服务既支持TCP也支持UDP,那么调用者可以把hints结构中的ai_socktype成员设置成SOCK_DGRAM,使得返回的仅仅是适用于数据包套接口的信息。

hints结构中调用这可以设置的成员有:
      ai_flags:     零个或多个在一起的AI_xxx值
      ai_family:    某个AF_xxx值
      ai_socktype:      某个SOCK_xxx值
      ai_protocol
    
如果hints的参数是一个空指针,本函数就假设ai_flag,ai_socktype和ai_protocol的值均为0,ai_family的值为AF_UNSPEC。

如果本函数返回成功,那么由result参数指向的变量已被填入一个指针,它指向的是有其中的ai_next成员串接起来的addrinfo结构链表,可以导致返回多个addrinfo结构的情形如下:
      1     如果与hostname参数关联的地址有多个,那么适用于所请求地址簇的每个地址都返回一个对应的结构。
      2     如果service参数指定的服务支持多个套接口类型,那么每个套接口类型都可能返回一个对应的结构,具体取决于hints结构的ai_socktype成员。
    
在addrinfo结构中返回的信息可现成用于socket调用,随后现成用语是和客户的connect或者sendto调用,或者是适合于服务器的bind调用。
socket的参数就是addrinfo结构中的ai_family、ai_socktype和ai_protocol成员。connect或bind的第2个和第三个参数就是该结构中的ai_addr(一个指向适当类型套接口地址的指针,地址结构的内容由getaddrinfo函数填写)和ai_addrlen(这个套接口地址结构的大小)成员。

如果在hints的ai_flags设置了AI_CANONNAME标志,那么本函数返回的第一个addrinfo结构的ai_canonname成员指向所查找的主机的规范名字。例如;
    
      struct    addrinfo      hints, *res;
      bzero( &hints, sizeof(hints) );
      hints.ai_flags = AI_CANONNAME;
      hints.ai_family = AF_INET;
      getaddrinfo( "freebsd4", "domain", &hints, &res );
    
如果系统没有实现除TCP外的其他SOCK_STREAM(注意: SOCK_STREAM还包括比如SCTP等)协议,那么套接口类型值为SOCK_STREAM的那两个addrinfo结构协议值可为0;同样的,如果系统没有实现UDP外的其他SOCK_DGRAM协议,套接口类型值为SOCK_DGRAM的那两个addrinfo结构协议值可为0.最安全的做法是让getaddrinfo总是返回明确的协议值。

对于getaddrinfo函数调用一些常见的输入:

1     指定hostname和service。这是TCP或UDP客户进程调用getaddrinfo的常见输入。该调用返回后,TCP客户在一个循环中针对每个返回的IP地址,逐一调用sock和connect,直到一个连接成功,或者所有的地址尝试完毕为止。而UDP客户,由getaddrinfo填入的套接口地质结构用于调用sendto或connect。如果客户能够判断第一个地址看来不工作,那么可以尝试其余的地址。
如果客户清楚自己只处理一种类型的套接口,那么应该把hints结构的ai_socktype成员设置成SOCK_STREAM或者SOCK_DGRAM.

2     典型的服务器进程只值定service而不指定hostname,同时在hints结构中指定AI_PASSIVE标志(表示套接口将被被动打开)。返回的套接口地质结构中应含有一个值为INADDR_ANY(IPv4)或者IN6ADDR_ANY_INIT(IPv6)的IP地址。TCP服务器随后调用socket、bind和listen。如果服务器想要malloc另一个套接口地址结构以从recvfrom获取客户的地址,那么返回的ai_addrlen值给出了这个套接口地址结构的大小。
同样,如果服务器清楚自己只处理一种套接口,那么应该把hints结构的ai_socktype成员设置成SOCK_STREAM或者SOCK_DGRAM。这样可以避免返回多个结构,其中可能包含错误的ai_socktype值。

3     服务器程序的另一种设计方法是使用select或者poll函数让服务器进程处理多个套接口。这种情形下,服务器将遍历由getaddrinfo返回的整个addrinfo结构链表,并为每个创建一个套接口,再使用select或者poll。


gai_strerror:
#include<netdb.h>
const char *gai_strerror( int error );
返回: 指向错误描述消息字符串的指针
该函数根据getaddrinfo返回的错误返回一个指向对应的出错信息串的字符串的指针。

freeaddrinfo:

#include<netdb.h>
void      freeaddrinfo( struct addrinfo *ai );
ai参数应指向由getaddrinfo返回的第一个addrinfo结构。这个链表中的所有结构以及由它们所指向的任何动态存储空间(譬如套接口地质结构和规范主机名)都被释放掉。

加入我们调用getaddrinfo,遍历所返回的addrinfo结构链表后找到所需的结构。如果我们为保存其信息而仅仅复制这个addrinfo结构,然后调用freeaddrinfo,那就引入了一个潜藏的错误。原因在于这个addrinfo结构本身指向动态分配的内存空间(用于存放套接口地质结构和可能有的规范主机名),因此我们保存的结构指向的内存空间已在调用freeaddrinfo时返回给系统,稍后可能用于其他目的。

一般的流程是:
      struct addrinfo hints, *res, *ressave;
      bzero( &hints, sizeof(hints) );
      hints.ai_flags = AF_xxx;
      ......
      if( getaddrinfo( hostname, service, &hints, &res ) != 0 )
          do_error(...);
        
       ressave = res;
     
       do
       {
       }while( (res = res->ai_next) != NULL );
     
       ....
     
       freeaddrinfo( ressave );   // 这里很重要,不能是res,因为res已经改变了

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---出错
        
sockaddr指向一个套接口地址结构,其中包含带转换成直观可读的字符串的协议地址,addrlen是这个结构的长度。该结构及其长度通常由accept、recvfrom、getsockname、或者getpeername返回。
带返回的两个直观可读字符串由调用者预先分配存储空间: host和hostlen指定主机字符串, serv和servlen指定服务字符串。如果调用者不想返回主机字符串,那就指定hostlen为0.同样把servlen指定为0就是不想返回服务字符串。<netdb.h>中定义了2个常值用于分配这两个存储空间:NI_MAXHOST给出主机字符串存储空间的最大长度:1025; NI_MAXSERV给出服务名字的最大长度,值为32。

flags的值:
NI_DGRAM         数据报服务
NI_NAMEREQD      如不能从地址解析出名字则返回错误
NI_NOFQDN        只返回FQDN的主机名部分
NI_NUMERICHOST   以数串格式返回主机字符串
NI_NUMBERICSCOPE     以数串格式返回范围标识字符串
NI_NUMBERICSERV      以数串格式返回服务字符串

如果知道处理的是数据报套接口时,调用者应该设置NI_DGRAM标志,因为再套接口结构中给出的仅仅是IP地址和端口号,getnameinfo无法就此确定所用协议(TCP/UDP)。


注意:
gethostbyname、gethostbyaddr、getservbyname和getservbyport这四个函数是不可重入的,因为它们都返回指向同一个静态结构的指针。

inet_pton和inet_aton总是可重入的。

getaddrinfo可重入的原因在于由它调用的函数都可重入;也就是说,它应该调用可重入版本的gethostbyname和getserbyname。本函数返回的结构全部存放在动态分配内存空间的原因之一就是允许它重入。
getnameinfo可重入的原因在于它调用的函数都是可重入的:也就是说,它应该调用可重入版本的gethostbyname和getserbyname。它的2个结果字符串(分别为主机名和服务名)由调用者分配存储空间,从而允许它可重入。

Our focus in this chapter has been on hostnames and IP addresses and service names and their port numbers. But looking at the bigger picture, there are four types of information (related to networking) that an application might want to look up: hosts, networks, protocols, and services. Most lookups are for hosts (gethostbyname and gethòstbyaddr), with a smaller number for services (getservbyname and getservbyaddr), and an even smaller number for networks and protocols.

All four types of information can be stored in a file and three functions are defined for each of the four types:

  1. getXXXent function that reads the next entry in the file, opening the file if necessary.

  2. setXXXent function that opens (if not already open) and rewinds the file.

  3. An endXXXent function that closes the file.

Each of the four types of information defines its own structure, and the following definitions are provided by including the <netdb.h> header: the hostentnetentprotoent, and serventstructures.

In addition to the three getset, and end functions, which allow sequential processing of the file, each of the four types of information provides some keyed lookup functions. These functions go through the file sequentially (calling the getXXXent function to read each line), but instead of returning each line to the caller, these functions look for an entry that matches an argument. These keyed lookup functions have names of the form getXXXbyYYY. For example, the two keyed lookup functions for the host information are gethostbyname (look for an entry that matches a hostname) and gethostbyaddr (look for an entry that matches an IP address). Figure 11.21 summarizes this information.

How does this apply when the DNS is being used? First, only the host and network information is available through the DNS. The protocol and service information is always read from the corresponding file. We mentioned earlier in this chapter (with Figure 11.1) that different implementations employ different ways for the administrator to specify whether to use the DNS or a file for the host and network information.

Second, if the DNS is being used for the host and network information, then only the keyed lookup functions make sense. You cannot, for example, use gethostent and expect to sequence through all entries in the DNS! If gethostent is called, it reads only the /etc/hosts file and avoids the DNS.

Although the network information can be made available through the DNS, few people set this up. [Albitz and Liu 2001] describes this feature. Typically, however, administrators build and maintain an /etc/networks file and it is used instead of the DNS. The netstat program with the -i option uses this file, if present, and prints the name for each network. However, classless addressing (Appendix A) makes these functions fairly useless, and these functions do not support IPv6 at all, so new applications should avoid using network names.

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值