9.1. 概述
本章讲述在名字和数值地址间进行转换的函数:gethostbyname和gethostbyaddr在主机名字与IP地址间进行转换,getservbyname和getservbyport在服务器名字和端口号间进行转换。
9.2. 域名系统
DNS(Domain Name System)主要用于主机名与IP地址间的映射。主机名可以是简单名字,如solaris或bsdi,也可以是全限定域名FQDN(Fully Qualified Domain Name),如solaris.kohala.com
DNS中的条目称为资源记录RR(resource record):
A -- A记录将主机名映射为32位的IPv4地址。
AAAA -- AAAA记录将主机名映射为128位的IPv6地址。
PTR -- PTR记录(称为"指针记录")将IP地址映射为主机名。对于IPv4地址,32位地址的四个字节顺序反转,每个字节都转换成他的十进制ASCII值(0~255),然后附上in-addr.arpa,结果串用于PTR查询。对于IPv6地址,128位地址中的32个4位组顺序发转,每组被转换成相应的十六进制ASCII值(0~9, a~f),并附上ip6.int。
MX -- MX记录指定一主机作为某主机的“邮件交换器”。
CNAME -- CNAME代表“canonical name(规范名字)”,其常见的用法是为常用服务如ftp和www指派一个CNAME记录。
解析器和名字服务器
组织运行一个或多个名字服务器(name server), 它们通常就是所谓的BIND(Berkeley Internet Name Domain)程序。各种应用程序,如本书中我们编写的客户和服务器程序,通过调用称为解析器(resolver)的库中的函数来与DNS服务器联系。最常见的解析器函数是gethostbyname和gethostbyaddr。
文件/etc/resolv.conf一般包含本地名字服务器的IP地址。
DNS替代方法
不使用DNS也可以得到名字和地址信息,最常用的替代方法为静态主机文件(一般为文件/etc/hosts)或网络信息系统NIS(Network Information System)。
9.3. gethostbyname函数
#include <netdb.h>
struct hostent * gethostbyname(const char * hostname); // 返回: 非空指针-成功, 空指针-出错,同时设置h_errno
此函数返回的非空指针指向下面的hostent结构:
struct hostent
{
char * h_name; /* official (canonical) name of host */
char * * h_aliases; /* pointer to array of pointers to alias names */
int h_addrtype; /* host address type: AF_INET or AF_INET6 */
int h_length; /* length of address: 4 or 16 */
char * * h_addr_list; /* ptr to array of ptrs with IPv4 or IPv6 addrs */
};
#define h_addr h_addr_list[0] /* first address in list */
gethostbyname与我们所介绍的其他套接口函数不同之处在于:当发生错误时,他不设置errno,而是将全局整数h_errno设置为定义在头文件<netdb.h>中的下列常值中的一个:
HOST_NOT_FOUND
TRY_AGAIN
NO_RECOVERTY
NO_DATA(等同于NO_ADDRESS) 表示指定的名字有效,但它既没有A记录,也没有AAAA记录。只有MX记录的主机名就是这样的例子。
下面是一个调用gethostbyname的简单例子,它可有任意数目的命令行参数,输出所有返回的信息:
#include "unp.h"
int main(int argc, char * * argv)
{
char * ptr, * * pptr;
char str[INET6_ADDRSTRLEN];
struct hostent * hptr;
while(--argc > 0)
{
ptr = * ++argv;
if((hptr = gethostbyname(ptr)) == NULL)
{
err_msg("gethostbyname error for host: %s: %s", ptr, hstrerror(h_errno));
continue;
}
printf("official hostname: %s \n", hptr->h_name);
for(pptr = hptr->h_aliases; * pptr != NULL; pptr++)
printf("\t alias: %s \n", *pptr);
switch(hptr->h_addrtype)
{
case AF_INET:
#ifdef AF_INET6:
case AF_INET6:
#endif
pptr = hptr->h_addr_list;
for( ; * pptr != NULL; pptr++)
printf("\t address: %s \n", inet_ntop(hptr->h_addrtype, *pptr, str, sizeof(str)));
break;
default:
err_ret("unknown address type");
break;
}
}
exit(0);
}
9.4. RES_USE_INET6解析器选项
9.5. gethostbyname2函数与IPv6支持
#include <netdb.h>
struct hostent * gethostbyname2(const char * hostname, int family); //返回:非空指针-成功,空指针-出错 同时设置h_errno
描述函数gethostbyname和选项RES_USE_INET6的行为的一个方法是看看它的源代码
struct hostent * gethostbyname(const char * name)
{
struct hostent * hp;
if((_res.options & RES_INIT) == 0 && res_init() == -1)
{
h_errno = NETDB_INTERNAL;
return (NULL);
}
if(_res.options & RES_USE_INET6)
{
hp = gethostbyname2(name, AF_INET6);
if(hp)
return (hp);
}
return (gethostbyname2(name, AF_INET));
}
9.6. gethostbyaddr函数
#include <netdb.h>
struct hostent * gethostbyaddr(const char * addr, size_t len, int family); //返回:非空指针-成功,空指针-出错 同时设置h_errno
9.7. uname函数
#include <sys/utsname.h>
int uname(struct utsname * name); // 返回:非负值-成功,-1-出错
此函数装填结构utsname,其地址由调用者传递:
#define UTS_NAMESIZE 16
#define UTS_NODESIZE 256
struct utsname
{
char sysname[_UTS_NAMESIZE]; /* name of this operating system */
char nodename[_UTS_NODESIZE]; /* name of this node */
char release[_UTS_NAMESIZE]; /* O.S release level */
char version[_UTS_NAMESIZE]; /* O.S version level */
char machine[_UTS_NAMESIZE]; /* hardware type */
};
为了确定本地主机的IP地址,我们调用uname以得到主机名字,然后调用gethostbyname以得到它的所有IP地址。
#include "unp.h"
#include <sys/utsname.h>
char * * my_addrs(int * addrtype)
{
struct hostent * hptr;
struct utsname myname;
if(uname(&myname) < 0)
return(NULL);
if((hptr = gethostbyname(myname.nodename)) == NULL)
return(NULL);
*addrtype = hptr->h_addrtype;
return(hptr->h_addr_list);
}
函数返回值是结构hostent的成员h_addr_list,即指向IP地址的指针数组。我们还通过指针参数返回地址族。
9.8. gethostname函数
#include <unistd.h>
int gethostname(char * name, size_t namelen); //返回:0-成功,-1-出错
name是指向主机名存储位置的指针,namelen是此数组的大小。
9.9. getservbyname和getservbyport函数
服务器就像主机一样,也常常是由名字来标识的。
#include <netdb.h>
struct servent * getservbyname(const char * servname, const char * protoname); //返回: 非空指针-成功,空指针-出错
此函数返回一个指向下面所示结构的指针:
struct servent
{
char * s_name; /* official service name */
char * * s_aliases; /* alias list */
int s_port; /* port number, network-byte order */
char * s_proto; /* protocol to use */
};
服务名servname必须指定,如果还指定了一个协议(即protoname为非空指针),则结果表项也必须有匹配的协议。结构servent中我们关心的主要成员是端口号。由于端口号是以网络字节序返回的,在将它存储于套接口地址结构时,绝对不能调用htons,对此函数的典型调用是:
sturct servent * sptr;
sptr = getservbyname("domain", "udp"); /* DNS using UDP */
sptr = getservbyname("ftp", "tcp"); /* FTP using TCP */
sptr = getservbyname("ftp", NULL); /* FTP using TCP */
sptr = getservbyname("ftp", "udp"); /* this call will fall */
在给定端口号和可选协议后可以使用getservbyport函数查找相应的服务:
#include <netdb.h>
struct servent * getservbyport(int port, const char * protname); //返回:非空指针-成功,空指针-出错
port值必须为网络字节序,对此函数的典型调用是:
struct servent * sptr;
sptr = getservbyport(htons(53), "udp"); /* DNS using UDP */
sptr = getservbyport(htons(21), "tcp"); /* FTP using TCP */
sptr = getservbyport(htons(21), NULL); /* FTP using TCP */
sptr = getservbyport(htons(21), "udp"); /* this call will fail */
对于UDP,由于没有服务使用端口21,所以最后一个调用将失败。
9.10 其它网络相关信息
9.11. 小结