(3)地址查询
理想情况,一个应用程序不用知道套接字地址的内部结构。如果一个应用程序简单地把套接字地址作为sockaddr结构传递并且不依赖任何协议相关特性,那么应用程序就可以在提供同样类型服务的不同协议上面工作。
以前BSD网络软件提供了访问各种网络配置信息的接口。在前面我们简单讨论了网络数据文件以及用来访问它们的函数。在这个章节,我们用一个更详细的方式来讨论它们并且介绍一些新的查询地址信息的函数。
这些函数返回的网络地址信息可以被保存在许多的地方。它们可以被保存在静态的文件中(例如/etc/hosts,/etc/services等);或者他们可以一个命名服务来管理,例如DNS(Domain Name System)或者NIS(Network Information Service)。我们不用考虑这些信息存放在哪里,我们可以使用同样的函数来访问它们。
主机信息
调用gethostent可以知道一个给定的计算机系统的hosts结构。
#include <netdb.h>
struct hostent *gethostent(void);
返回:如果成功,返回一个指针,如果错误返回NULL。
void sethostent(int stayopen);
void endhostent(void);
如果主机数据库文件没有被打开,那么gethostent将会打开它。gethostent函数返回文件中的下一条entry。sethostent函数会打开文件,或者如果文件已经打开,那么就重新回到文件的开始。endhostent函数关闭文件。
当gethostent返回的时候,我们获取到一个指向hostent结构的指针,这个指针可能会指向一个静态数据缓存,这个静态数据缓存在每次调用gethostent的时候都会被覆盖。hostent结构的定义至少有以下几个成员:
struct hostent {
char *h_name; /* name of host */
char **h_aliases; /* pointer to alternate host name array */
int h_addrtype; /* address type */
int h_length; /* length in bytes of address */
char **h_addr_list; /* pointer to array of network addresses */
.
};
返回的地址使用网络字节次序表示。
有两个额外的函数gethostbyname和gethostbyaddr以前被包含在hostent函数中,但是现在它们将会被作废。我们马上就会看到可以替代他们的东西了。
网络信息
我们可以使用一些类似的接口获得网络名称以及代号。
#include <netdb.h>
struct netent *getnetbyaddr(uint32_t net, int type);
struct netent *getnetbyname(const char *name);
struct netent *getnetent(void);
以上函数返回:如果成功返回指针,如果错误返回NULL。
void setnetent(int stayopen);
void endnetent(void);
netent结构至少包含如下成员:
struct netent {
char *n_name; /* network name */
char **n_aliases; /* alternate network name array pointer */
int n_addrtype; /* address type */
uint32_t n_net; /* network number */
.
};
网络号码(n_net)以网络字节次序表示。地址类型是某一个地址常量(例如AF_INET)。
协议信息
我们可以通过如下的函数在协议名称和号码之间进行映射。
#include <netdb.h>
struct protoent *getprotobyname(const char *name);
struct protoent *getprotobynumber(int proto);
struct protoent *getprotoent(void);
以上函数返回:如果成功返回指针,如果错误返回NULL。
void setprotoent(int stayopen);
void endprotoent(void);
通过POSIX.1定义的protoent结构至少应当包含如下成员:
struct protoent {
char *p_name; /* protocol name */
char **p_aliases; /* pointer to alternate protocol name array */
int p_proto; /* protocol number */
.
};
服务信息
服务通过地址部分的端口号码部分来表示。每个服务提供一个公共的端口号码。我们可以通过getservbyname函数将一个服务名称映射成一个端口,通过getservbyport函数将一个端口映射成一个服务名称,或者通过getservent函数顺序扫描服务数据库。
#include <netdb.h>
struct servent *getservbyname(const char *name, const char *proto);
struct servent *getservbyport(int port, const char *proto);
struct servent *getservent(void);
以上返回:如果成功返回指针,如果错误返回NULL。
void setservent(int stayopen);
void endservent(void);
servent结构的定义至少包含如下的成员:
struct servent {
char *s_name; /* service name */
char **s_aliases; /* pointer to alternate service name array */
int s_port; /* port number */
char *s_proto; /* name of protocol */
.
};
主机、服务与地址之间的映射
POSIX.1定义了一些新的函数可以让一个应用程序将一个主机名称和服务名称映射成一个地址,以及进行相反的映射。这些函数替代了原来的gethostbyname和gethostbyaddr函数。
主机、服务映射成地址
getaddrinfo函数允许我们将一个主机名称和服务名称映射成一个地址。
#include <sys/socket.h>
#include <netdb.h>
int getaddrinfo(const char *restrict host, const char *restrict service,
const struct addrinfo *restrict hint, struct addrinfo **restrict res);
返回值:如果成功返回0,如果错误返回非0的错误代码。
void freeaddrinfo(struct addrinfo *ai);
我们需提供主机名称,服务名称,或者两者都提供。如果我们只提供一个名称,那么另外一个应该是空指针。主机名称可以是一个节点名称,或者点分十进制表示的主机地址。
getaddrinfo函数返回一个addrinfo结构的链表。我们可以使用freeaddrinfo将一个或者多个这些结构释放,这取决于有多少个这个结构通过ai_next成员被链接。
addrinfo结构的定义,至少包含如下的成员:
struct addrinfo {
int ai_flags; /* customize behavior */
int ai_family; /* address family */
int ai_socktype; /* socket type */
int ai_protocol; /* protocol */
socklen_t ai_addrlen; /* length in bytes of address */
struct sockaddr *ai_addr; /* address */
char *ai_canonname; /* canonical name of host */
struct addrinfo *ai_next; /* next in list */
.
};
我们可以提供一个可选的hint(参见getaddrinfo函数的参数)来符合特定的标准。这个hint是一个模板,它只使用ai_family,ai_flags,以及ai_socktype成员,用来过滤地址。剩下的整数成员必须被设置成0,并且指针成员被设置为空。下表列出了对于ai_flags成员我们可以使用的flags,以便定制地址和名称的处理方式。
-
addrinfo结构的flags
+----------------------------------------------------------------------------------------+ | Flag | Description | |----------------+-----------------------------------------------------------------------| | AI_ADDRCONFIG | 请求配置的是哪种地址类型(IPv4或者IPv6)。 | |----------------+-----------------------------------------------------------------------| | AI_ALL | 对IPv4和IPv6地址都进行查找(只和AI_V$MAPPED一块使用)。 | |----------------+-----------------------------------------------------------------------| | AI_CANONNAME | 请求一个正式的名称(和别名相对)。 | |----------------+-----------------------------------------------------------------------| | AI_NUMERICHOST | 以数字形式返回主机地址。 | |----------------+-----------------------------------------------------------------------| | AI_NUMERICSERV | 将服务作为端口号码返回。 | |----------------+-----------------------------------------------------------------------| | AI_PASSIVE | 绑定套接字地址以便侦听。 | |----------------+-----------------------------------------------------------------------| | AI_V4MAPPED | 如果没有发现IPv6地址,那么返回以IPv6格式映射的IPv4地址。 | +----------------------------------------------------------------------------------------+
如果getaddrinfo失败,那么我们不能使用perror或者strerror来生成错误消息。相反,我们需要调用gai_strerror来将返回的错误号码转化成错误消息。
#include <netdb.h> const char *gai_strerror(int error);
返回:一个描述错误的指向字符串的指针。
地址映射成主机、服务
getnameinfo函数会将一个地址转化成一个主机和服务名称。
#include <sys/socket.h>
#include <netdb.h>
int getnameinfo(const struct sockaddr *restrict addr, socklen_t alen, char *restrict host,
socklen_t hostlen, char *restrict service, socklen_t servlen, unsigned int flags);
返回:如果成功返回0,如果错误返回非0值。
套接字地址(addr)被转化成一个主机名称和服务名称。如果host非空,那么它指向一个用来存放主机名称的长度为hostlen字节的缓存。类似地,如果service非空,那么它指向一个用来存放服务名称的长度为servlen字节的缓存。
参数flags会给我们指定对这个转换进行的方式。下面的表格列举出来支持的flags。
-
getnameinfo函数的标记
+--------------------------------------------------------------------------------------------------------------+ | Flag | Description | |----------------+---------------------------------------------------------------------------------------------| | NI_DGRAM | 服务基于数据报而不是流。 | |----------------+---------------------------------------------------------------------------------------------| | NI_NAMEREQD | 如果没有找到主机名称,那么将这个作为错误对待。 | |----------------+---------------------------------------------------------------------------------------------| | NI_NOFQDN | 只返回本地主机整个域名称的节点名称部分。 | |----------------+---------------------------------------------------------------------------------------------| | NI_NUMERICHOST | 返回数字形式表示的主机地址而不是名字形式。 | |----------------+---------------------------------------------------------------------------------------------| | NI_NUMERICSERV | 返回数字形式的服务地址(例如端口号码),而不是名字的形式。 | +--------------------------------------------------------------------------------------------------------------+
举例:列举getaddrinfo函数使用的程序
打印主机和服务信息的例子:
#include "apue.h"//本书中一些预先定义好的自定义函数以及需要的头文件都在这里。
#include <netdb.h>
#include <arpa/inet.h>
#if defined(BSD) || defined(MACOS)
#include <sys/socket.h>
#include <netinet/in.h>
#endif
void print_family(struct addrinfo *aip)
{
printf(" family ");
switch (aip->ai_family) {
case AF_INET:
printf("inet");
break;
case AF_INET6:
printf("inet6");
break;
case AF_UNIX:
printf("unix");
break;
case AF_UNSPEC:
printf("unspecified");
break;
default:
printf("unknown");
}
}
void print_type(struct addrinfo *aip)
{
printf(" type ");
switch (aip->ai_socktype) {
case SOCK_STREAM:
printf("stream");
break;
case SOCK_DGRAM:
printf("datagram");
break;
case SOCK_SEQPACKET:
printf("seqpacket");
break;
case SOCK_RAW:
printf("raw");
break;
default:
printf("unknown (%d)", aip->ai_socktype);
}
}
void print_protocol(struct addrinfo *aip)
{
printf(" protocol ");
switch (aip->ai_protocol) {
case 0:
printf("default");
break;
case IPPROTO_TCP:
printf("TCP");
break;
case IPPROTO_UDP:
printf("UDP");
break;
case IPPROTO_RAW:
printf("raw");
break;
default:
printf("unknown (%d)", aip->ai_protocol);
}
}
void print_flags(struct addrinfo *aip)
{
printf("flags");
if (aip->ai_flags == 0) {
printf(" 0");
} else {
if (aip->ai_flags & AI_PASSIVE)
printf(" passive");
if (aip->ai_flags & AI_CANONNAME)
printf(" canon");
if (aip->ai_flags & AI_NUMERICHOST)
printf(" numhost");
#if defined(AI_NUMERICSERV)
if (aip->ai_flags & AI_NUMERICSERV)
printf(" numserv");
#endif
#if defined(AI_V4MAPPED)
if (aip->ai_flags & AI_V4MAPPED)
printf(" v4mapped");
#endif
#if defined(AI_ALL)
if (aip->ai_flags & AI_ALL)
printf(" all");
#endif
}
}
int main(int argc, char *argv[])
{
struct addrinfo *ailist, *aip;
struct addrinfo hint;
struct sockaddr_in *sinp;
const char *addr;
int err;
char abuf[INET_ADDRSTRLEN];
if (argc != 3)
err_quit("usage: %s nodename service", argv[0]);
hint.ai_flags = AI_CANONNAME;
hint.ai_family = 0;
hint.ai_socktype = 0;
hint.ai_protocol = 0;
hint.ai_addrlen = 0;
hint.ai_canonname = NULL;
hint.ai_addr = NULL;
hint.ai_next = NULL;
if ((err = getaddrinfo(argv[1], argv[2], &hint, &ailist)) != 0)
err_quit("getaddrinfo error: %s", gai_strerror(err));
for (aip = ailist; aip != NULL; aip = aip->ai_next) {
print_flags(aip);
print_family(aip);
print_type(aip);
print_protocol(aip);
printf("\n\thost %s", aip->ai_canonname?aip->ai_canonname:"-");
if (aip->ai_family == AF_INET) {
sinp = (struct sockaddr_in *)aip->ai_addr;
addr = inet_ntop(AF_INET, &sinp->sin_addr, abuf,
INET_ADDRSTRLEN);
printf(" address %s", addr?addr:"unknown");
printf(" port %d", ntohs(sinp->sin_port));
}
printf("\n");
}
exit(0);
}
这个程序列举了getaddrinfo函数的使用。如果这个主机上面有多个协议支持给定的协议,那么程序将会打印更多的条目。在这个例子里,我们只打印工作在IPv4下的协议(ai_family等于AF_INET)的地址信息。如果我们想要限制输出为AF_INET协议族,那么我们应当设置hint中的ai_family成员。
当我们在某一个系统下面运行这个程序的时候,我们得到如下类似的输出:
$ ./a.out harry nfs
flags canon family inet type stream protocol TCP
host harry address 192.168.1.105 port 2049
flags canon family inet type datagram protocol UDP
host harry address 192.168.1.105 port 2049
网友评论