第八章 主机名称与主机地址

来源:互联网 发布:office for mac通用 编辑:程序博客网 时间:2024/04/29 23:42

第八章 主机名称与主机地址

8.1 主机名称与主机地址解析

将主机名称解析映射到主机地址的过程称为主机名称解析。相反地,将主机地址解析映射到主机名称的过程我们称之为主机地址解析。这两个过程是相对应的。
在WinSock API 中,主机名称解析函数是gethostbyname()和WSAAsyncGetHostByName()。实现主机地址解析的函数是gethostbyaddr()和WSAAsyncGetHostByAddr()。

8.1.1 hostent结构

struct  hostent {    char    FAR * h_name;           /* official name of host */    char    FAR * FAR * h_aliases;  /* alias list */    short   h_addrtype;             /* host address type */    short   h_length;               /* length of address */    char    FAR * FAR * h_addr_list;/* list of addresses */#define h_addr  h_addr_list[0]      /* address, for backward compat */};
h_name: 包括了公开的主机名称。h_aliases: 指向一个字符串指针数组(字符串以空NULL结尾)。每个字符串指向一个别名,别名是主机的一个备用名称,是公开主机名的同义词。在域名系统数据库中,每个别名均拥有一个规范名(CNAME)用来标识主机在DNS中的名称。当用户需要解析一个别名时,首先得到的是一个DNS中使用的规范名称,然后通过这个规范名称检索主机的地址。h_addrtype: 表示了h_addr_list域指向的地址类型。目前TCP/IP结构中只用两种值:AF_INET或者PF_INET。h_length: 表示域h_addr_list所指向的每个地址的字节长度。根据域h_addrtype所标识的地址类型值的不同,h_length的值有所不同。如TCP/IP(h_addrtype = AF_INET)中,h_length的值总为4.h_addr_list: 指向一个按照网络字节顺序的网络地址的指针数组(由于主机可能拥有多个网络接口,所以一些主机拥有多个网络地址)。这组指针的末尾有一个标识结束的空指针(NULL)。域h_length表示每个地址的长度。宏h_addr的定义是为了简化初始地址的访问过程(数组的第一个元素)h_addr_list[0]

8.1.2 主机名称解析

应用程序只有拥有主机地址才能与其进行通信。WinSock的监听、连接、发送和接收函数使用的都是网络地址,而不是主机名。然而,人们通常使用的都是主机名称,因此需要首先执行主机名称解析才能发起通信。
主机名称解析包含两个函数。两者的主要不同是操作方法的不同,但在缓存区的使用方面两者也有很大的不同。
gethostbyname():在主机名称解析完成前,gethostbyname()一致处在阻塞状态。WinSock将结果存储在共享的系统缓存中。
WSAAsyncGetHostByName(): 结果立即返回。当主机名称解析完成时,Winsock异步地通知应用程序。WinSock将结果存储在用用程序提供的缓存区中。
需要注意的是,我们不能把一个IP地址字符串,如”128.127.0.1”作为主机名输入函数,这将使得函数由于不能识别主机而返回错误信息WSAHOST_NOT_FOUND。
当应用程序提示用户输入一个目的主机是,应该允许用户输入一个主机地址或主机名。这就要求应用程序可以正确处理这两种情况。

  1. gethostbyname()
    gethostbyname()使用主机名访问WinSock实际应用的一个主机数据库(例如主机表、域名系统、网络信息服务等),并返回完整的主机信息,其中包括公开主机名、所有已知的别名及接口地址。
    struct hostent FAR *WSAAPI gethostbyname(        const char FAR * name    );

若解析成功,函数将返回一个指向hostent结构的指针,它指向存储结构的系统缓存,若解析失败,将返回一个空指针。操作失败的情况下,可以通过调用WSAGetLastError()函数对错误进行跟踪。最常见的错误值是WSAHOST_NOT_FOUND,它是发生在不能解析主机名的时候。用户应该及时备份系统缓存的内容,如果没有这么做,后面调用的非函数可能会覆盖前面函数的返回结果。

2.WSAAsyncGetHostByName()

HANDLE WSAAPI WSAAsyncGetHostByName(        HWND hWnd,                  // window to rcv msg on completion        u_int wMsg,                 // message to be rcvd on completion        const char FAR * name,      // ptr to name of host to be resolved        char FAR * buf,             // ptr to data area to rcv hostent data        int buflen                  // size of data area buf above);

hWnd: 当主机名称解析完成后,打算接收异步通知消息的窗口的句柄。
wMsg:当主机名称解析操作完成后,WinSock发送给用户的消息值。
name: 一个以空(NULL)结尾的主机名字符串。
buf:一个指向缓存区地址的指针,WinSock可以从这个缓存区中复制解析成功的主机信息。
buflen:buf参数指向的缓存区大小。
WSAAsyncGetHostByName()函数总是在操作完成之前返回结果。解析操作在后台执行,解析完成后向应用程序发送异步通知消息。若操作失败,用户可以调用WSAGetLastError()函数来获取失败的原因。若操作成功,函数返回一个非0的异步查询句柄,用户可以通过这个句柄识别后续的异步消息响应。

注意:在主机名称解析过程完成时,buf所指向的存储区必须是可用的。所以在调用WSAAsyncGetHostByName()函数后,虚要确定已经将一个可用的持久性的缓存区分配给了用户。也就是说需要一个全局的缓存区或静态的缓存区,而不是在调用WSAAsyncGetHostByName()函数时动态创建的缓存区。

8.1.3 地址解析

struct hostent FAR * WSAAPI gethostbyaddr(   const char FAR * addr,           // ptr to address in network byte order   int len,                         // length of address (4 if af == AF_INET)   int type                         // type of address (AF_INET == TCP/IP));

addr: 带解析的主机地址。主机地址应该是按照网络字节顺序存储的
len:网络地址的字节长度。
type:in_addr中网络地址的类型。

2.WSAAsyncGetHostByAddr()

HANDLE WSAAPI WSAAsyncGetHostByAddr(    HWND hWnd,              // window to rcv msg on completion    u_int wMsg,             // mesage to be rcvd on completion    const char FAR * addr,  // ptr to address of host    int len,                // length of address    int type,               // type of address    char FAR * buf,         // ptr to data area to rcv hostent data    int buflen              // size of data area buf above);

hWnd:当主机地址解析操作完成后,要接收异步通知消息的窗口句柄。
wMsg:当主机地址解析操作完成后,WinSock发送给用户的消息的值。
addr:待解析的主机地址。
len:网络地址的字节长度。
type:in_addr中网络地址的类型。
buf:一个指向缓存区地址的指针,WinSock可以从这个缓存区中复制解析成功的主机信息。
buflen:buf参数指向的缓存区大小。

8.2 主机表、域名系统和网络信息服务

8.2.1 主机表

一个包含主机名和相应网络地址的文件成为主机表。
使用本地主机主机表的优点有以下几个:
1. 由于不存在网络的输入、输出操作,所以主机名称解析过程快。尤其在通过串行线连接或DNS服务器距离很远或网速慢、不可达等情况下,本地主机表的优点尤为突出。
2. 允许用户有自己的映射表。用户可以为主机添加一些域名服务器不知道的别名、条目等。
但是主机表也有以下几个缺点:
1. 一般的网络中不可能维持一个包含所有主机的列表。
2. 内容更新比较困难
3. 本地主机中的主机表内容与真实的网络地址之间存在差异。

8.2.2 域名系统

由于不可能每个主机都维护一个主机表,所以就有了取代这种静态主机表的一个动态分布式的数据库系统——域名系统。DNS的最大特点是各个服务器间共享主机的信息。DNS的更新是一个接一个的。当一台服务器不知道主机请求时,它将把请求发送给其他的服务器。
主机名的语法描述如下:
[host name.][subdomain.[subdomian.]…][domain][.]
一个完整的域名包含了各个部分及最后的点。若应用程序不提供一个完整的域名是,TCP/IP实施可以通过添加确实构建的方法完成主机名称解析。
若Winsock被配置为使用DNS进行主机名称解析,那么主机名称解析或地址解析函数的调用会产生一个DNS服务器查询请求。查询类型如下:
1. 带有完整主机名的”IP地址”(A)域名解析查询。
2. 带有IP地址字节逆转和in-addr.arpa顶级域的指针记录(PTR)地址解析请求。
3. 所有记录的查询请求(× or ANY)。

8.2.3 网络信息服务

Sun公司的网络信息服务提供与DNS相似的域名解析服务。其通常与DNS一起进行域名解析。确切地说,网络信息服务(Network Information Service,NIS)在一个网络内提供主机间的/etc/hosts文件的同步。
网络信息服务很少有WinSock应用程序使用。

8.3 本地主机信息

通过Windows Sockets应用程序接口(API)得不到本地主机的IP地址。因此应用程序只能通过两种方法找到本地主机的IP地址。但是,这两种方法都是间接的,不可靠的。
1. 首先调用gethostname(),然后调用gethostbyname()(或者调用WSAAsyncGetHostByName()函数)。
2. 连接好的socket可以调用getsockName()。
由于使用这两种方法都存在错误。所以通常将两者结合起来一起使用以求降低出错的可能性。
gethostname()将本地主机名复制到应用程序提供的缓存区中。

int WSAAPI gethostname(     // 0 on success, or SOCKET_ERROR    char FAR * name,        // buffer to receive host name    int namelen             // length of name buffer );

name:WinSock将本地主机名复制到的目标缓存区
namelen:name缓存区的字节长度。
不同的实现方式有着不同的表现特征:
1. 函数执行成功并返回空字符串(NULL)
2. 报告出错并返回WSAHOST_NOT_FOUND错误
3. 返回一个特殊的主机名,gethostbyname()函数将这个主机名当成是本地的主机名。

即使应用程序得到了本地主机名,WinSock规范也不能保证将主机名称解析到本地IP地址。

8.4 网络地址与格式化

一个网络地址可以有两种格式:数字和字符串。一个IP地址是一个32为的无符号整数,如0x807F3201(按照网络字节顺序,即大端格式)。另一种标准字符串表达式,因特网点分表示法,如 128.127.50.1.
WinSock分别用两个函数实现数字形式的IP地址与字符串形式的IP地址见的相互转换:inet_ntoa()和inet_addr()。

8.4.1 in_addr结构
网络地址数据结构包含一个按照网络字节顺序存储的32位网络协议地址。

struct in_addr {    union {        struct { u_char s_b1,s_b2,s_b3,s_b4; } S_un_b;        struct { u_short s_w1,s_w2; } S_un_w;        u_long S_addr;} S_un;#define s_addr  S_un.S_addr                                /* can be used for most tcp & ip code */#define s_host  S_un.S_un_b.s_b2                                /* host on imp */#define s_net   S_un.S_un_b.s_b1                                /* network */#define s_imp   S_un.S_un_w.s_w2                                /* imp */#define s_impno S_un.S_un_b.s_b4                                /* imp # */#define s_lh    S_un.S_un_b.s_b3                                /* logical host */};

用户可以通过下面的宏来访问具体的地址内同,从而将IP地址进行分解。
s_addr:按照网络字节顺序的32为完整IP地址值。多数使用in_addr结构的应用程序发现使用这个宏非常方便。
s_net:网络地址的第一个字节,它标识了IP地址的类型(例如A类:0~127,B类:128~191,C类:192~223,D类(多播):224——239,E类(预留):240~255)。
s_host、s_imp、s_impno,s_lh:这些都是不会被使用到的宏,属于Berkeley Sockets的遗留物。

通用地址
INADDR_ANY:标识任意的IP地址。在调用bind()函数请求网络系统分配默认接口地址前,应用程序通常调用这个宏来初始化sockaddr_in结构的sin_addr域。
INADDR_NONE:标识一个无效的IP地址。WinSock调用inet_addr()函数后,返回该值表示操作失败。
INADDR_BROADCAST: 标识了标准的IP受限广播地址”255.255.255.255”的32位值。应用程序通常需要使用这个宏来发送和接收标准的广播地址。
INADDR_LOOPBACK:标识了标准的IP回环地址”127.0.0.1”。

8.4.2 inet_addr()

inet_addr():将标准网络点分记法的字符串转换为网络自己顺序的数字型地址。

unsigned long WSAAPI inet_addr(    const char FAR * cp // a char string in Internet "." format);

cp:点分记法的IP地址,由4个0~255之间的十进制数字组成,数字间用点分隔
这个函数的失败原因主要有两个:字符串不是一个有效的IP地址表示(格式错误或数值超出范围),或者是由于所指向的缓存区是虚假的。
如果用户填写的IP地址不完整,这个函数会智能地补全缺失内容。其方法如下:
如果用户提供IP地址形式为三段内容(a.b.c)那么将把最后一段的内容作为一个16位的值处理。例如,如果用户给出的地址是”128.127.6”,inet_ntoa()函数将把它转换成”128.127.0.6”;如果用户给出的地址是”128.127.12866”,那么inet_ntoa()函数将把它转换成”128.127.50.66”。三段标记便于定义B类地址xxx.net.host(xxx 是一个128~191之间的数值,表明是一个B类IP地址)。
如果用户提供了IP地址四段中的两段内容(a.b),那么它将把最后一段的内容作为一个24位的值传给地址最有段德部分。例如,它将”92.65”转换成”92.0.0.65”,将”92.9876”转换为”92.0.38.148”。WinSock的监听、连接、发送和接收函数使用的都是网络地址,而不是主机名。然而,人们通常使用的都是主机名称,因此需要首先执行主机名称解析才能发起通信。如果用户提供了IP地址四段中的一段内容,那么将直接存储这一段的内容,不进行任何字节的调整。例如,如果用户输入的内容为”65”,那么结果为”0.0.0.65”;如果用户输入的内容为”2155819555”,那么结果为”128.127.50.35”。

8.4.3 inet_ntoa()

inet_ntoa()讲一个32位的数字标记法网络地址转换为因特网标准点分记法的ASCII字符串

char FAR * WSAAPI inet_ntoa(    struct in_addr in       // Internet address stucture);

in:IP地址值,任何32位的值都可以。
这个函数基本上是不会失败的。它是一个检查返回值是否为空指针的好方法。

8.5 协议族与地址族

hostent结构的h_addrtype域与因特网socket结构的sockaddr_in中的sin_family域相对应。它们可以取值为:AF_INET或者PF_INET。

0 0
原创粉丝点击