在名字和数值地址间进行转换的函数:
gethostbyname和gethostbyaddr:在主机名字与IPv4地址之间进行转换。仅仅支持IPv4.
getservbyname和getservbyport:在服务名字和端口号之间进行转换。
getaddrinfo和getnameinfo:用于主机名字和IP地址之间以及服务名字和端口号之间的转换。(这两个函数是协议无关的)
域名系统(Domain Name System,简称DNS)主要用于主机名字和IP地址之间的映射。
资源记录
DNS中的条目称为资源记录(resource record,简称RR)。
RR类型:
A A记录把一个主机名映射成一个32位的IPv4地址。
AAAA AAAA记录把一个主机名映射成一个128位的IPv6地址。
PTR 称为“指针记录(pointer record)”,它把IP地址映射成主机名。
解析器和名字服务器
每个组织机构往往运行一个或多个名字服务器(name server),它们通常就是所谓的BIND(Berkeley Internet Name Domain)程序。应用程序通过调用称为解析器(resolver)的函数库中的函数接触DNS服务器。常见的解析器函数是gethostbyname和gethostbyaddr:前者把主机名映射成IPv4地址,后者则执行相反的映射。
下图展示了应用进程、解析器和名字服务器之间的一个典型关系。
解析器代码通过读取其系统相关配置文件确定本组织机构的名字服务器们(为可靠和冗余的目的,大多数组织机构运行多个名字服务器)的所在位置。文件/etc/resolv.conf通常包含本地名字服务器主机的IP地址。
DNS替代方法
不使用DNS也可能获取名字和地址信息。常用的替代方法有静态主机文件(通常是/etc/hosts文件)、网络信息系统(Network Information System,简称NIS)以及轻权目录访问协议(Lightweight Directory Access Protocol,简称LDAP)。
查找主机名最基本的函数是gethostbyname。如果调用成功,它就返回一个指向hostent结构的指针,该结构中含有所查找主机的所有IPv4地址。这个函数的局限是只能返回IPv4地址,而getaddrinfo函数能够同时处理IPv4地址和IPv6地址。
#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 */ int h_length; /* length of address: 4 */ char **h_addr_list; /* ptr to array of ptrs with IPv4 addrs */ };
gethostbyname与其他套接口函数的不同之处在于:当发生错误时,它不设置errno变量,而是将全局整数变量h_errno设置在头文件<netdb.h>中定义的下列常值之一:
HOST_NOT_FOUND
TRY_AGAIN
NO_RECOVERY
NO_DATA(等同于NO_ADDRESS)
多数解析器提供名为hstrerror的函数,它以某个h_errno值作为唯一的参数,返回的是一个const char *指针,指向相应错误的说明。
gethostbyaddr函数试图由一个二进制IP地址找到相应的主机名,与gethostbyname的行为刚好相反。
#include <netdb.h> struct hostent *gethostbyaddr(const char *addr, socklen_t len, int family); 返回值:非空指针——成功;空指针——出错,同时设置h_errno
本函数返回一个同样指向hostent结构的指针。
addr参数实际上不是char *类型,而是一个指向存放IPv4地址的某个in_addr结构的指针;
len参数是这个结构的大小:对于IPv4地址为4;
family参数为AF_INET。
服务也通常靠名字来认知。如果我们在程序代码中通过其名字而不是端口号来指代一个服务,而且从名字到端口号的映射关系保存在一个文件中(通常是/etc/services),那么即使端口号发生变动,我们需修改的仅仅是/etc/services文件中的某一行,而不必重新编译应用程序。
getservbyname函数用于根据给定的名字查找相应服务。
#include <netdb.h> struct servent *getservbyname(const char *servname, const char *protoname); 返回:非空指针——成功;空指针——出错
本函数返回的非空指针指向如下的servent结构:
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参数为非空指针),那么指定服务必须有匹配的协议。有些因特网服务既用TCP也用UDP提供(例如DNS),其他因特网服务则仅仅支持单个协议(例如FTP要求使用TCP)。如果protoname未指定而servname指定服务支持多个协议,那么返回哪个端口号取决于实现。
servent结构中我们关心的主要是端口号。既然端口号是以网络字节序返回的,把它存放到套接口地址结构时绝不能调用htons。
本函数的典型调用如下:
struct servent *sptr; sptr = getservbyname("domain", "udp"); sptr = getservbyname("ftp", "tcp");
getservbyport函数用于根据给定端口号和可选协议查找相应服务。
#include <netdb.h> struct servent *getservbyport(int port, const char *protoname); 返回:非空指针——成功;空指针——出错
port参数的值必须为网络字节序。本函数的典型调用如下:
struct servent *sptr; sptr = getservbyport(htons(53), "udp"); sptr = getservbyport(htons(21), "tcp");
getaddrinfo函数能够处理名字到地址以及服务到端口这两种转换,它解决了把主机名和服务名转换成套接口地址结构的问题,返回的是一个sockaddr结构的链表而不是一个地址清单。这些sockaddr结构随后可由套接口函数直接使用。
#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; /* AI_PASSIVE, AI_CANONNAME */ int ai_family; /* AF_xxx */ int ai_socktype; /* SOCK_xxx */ int ai_protocol; /* 0 or IPPROTO_xxx for IPv4 and IPv6 */ socklen_t ai_addrlen; /* length of ai_addr */ char *ai_canonname; /* ptr to canonical name for host */ struct sockaddr *ai_addr; /* ptr to socket address structure */ struct addrinfo *ai_next; /* ptr to next structure in linked list */ };
其中,
hostname参数是一个主机名或地址串(IPv4的点分十进制数串或IPv6的十六进制数串)。
service参数是一个服务名或十进制端口号数串。
hints参数可以是一个空指针,也可以是一个指向某个addrinfo结构的指针,调用者在这个结构中填入关于期望返回的信息类型的暗示。
hints结构中调用者可以设置的成员有:
其中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_flags、ai_sokctype和ai_protocol的值均为0,ai_family的值为AF_UNSPEC。
如果本函数返回成功(0),那么由result参数指向的变量已被填入一个指针,它指向的是由其中的ai_next成员串接起来的addrinfo结构链表。可导致返回多个addrinfo结构的情形有以下两个:
如果与hostname参数关联的地址有多个,那么适用于所请求地址族(可通过hints结构的ai_family成员设置)的每个地址都返回一个对应的结构。
如果service参数指定的服务支持多个套接口类型,那么每个套接口类型都可能返回一个对应的结构,具体取决于hints结构的ai_socktype成员。
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指向一个套接口地址结构。
待返回的2个直观可读的字符串由调用者预先分配存储空间:host和hostlen指定主机字符串;serv和servlen指定服务字符串。如果调用者不想返回主机字符串,那就指定hostlen为0。同样,把servlen指定为0就是不想返回服务字符串。头文件<netdb.h>中定义了2个常值用于分配这两个存储空间:NI_MAXHOST给出主机字符串存储空间的最大长度,值为1025;NI_MAXSERV给出服务字符串存储空间的最大长度,值为32.
6个可指定的标志flags,用于改变getnameinfo的操作:
常值 | 说明 |
NI_DGRAM NI_NAMEREQD NI_NOFQDN NI_NUMERICHOST NI_NUMERICSCOPE NI_NUMERICSERV |
数据报服务 若不能从地址解析出名字则返回错误 只返回FQDN的主机名部分 以数串格式返回主机字符串 以数串格式返回范围标识字符串 以数串格式返回服务字符串 |
四类网络相关信息总结如下表:
信息 | 数据文件 | 结构 | 键值查找函数 |
主机 网络 协议 服务 |
/etc/hosts /etc/networks /etc/protocols /etc/services |
hostent netent protoent servent |
gethostbyaddr,gethostbyname getnetbyaddr,getnetbyname getprotobyname,getprotobynumber getservbyname,getservbyport |