Linux提供了一些强大的函数(称为getaddrinfo和getnameinfo)实现二进制套接字地址结构和主机名、主机地址、服务名和端口号的字符串表示之间的相互转化。当和套接字接口一起使用时,这些函数能使我们编写独立于任何特定版本的协议的网络程序。
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
int getaddrinfo(const char *host, const char *service,
const struct addrinfo *hints,
struct addrinfo **result);
//返回:如果成功返回0;如果错误返回非0的错误代码。
void freeaddrinfo(struct addrinfo *result);
//释放链表,无返回。
const char *gai_strerror(int errcode);
//返回:错误消息。
下面就来分析getaddrinfo()函数的用法。
参数说明:
-
const char *host:一个域名或一个IP地址(IPv4或IPv6都行)。
-
const char *service:服务名(http,ftp等)或端口号。
-
const struct addrinfo *hints:参数hints可以为空(NULL),也可以是一个指向addrinfo结构体的指针。addrinfo结构体成员如下所示:
struct addrinfo {
int ai_flags;
int ai_family;
int ai_socktype;
int ai_protocol;
char *ai_canonname;
size_t ai_addrlen;
struct sockaddr *ai_addr;
struct addrinfo *ai_next;
};
学过数据结构都知道addrinfo就是一个链表,ai_next指向下一个节点。 如果要传递hints参数,只能设置下列字段:ai_family、ai_socktype、ai_protocol和ai_flags字段。其他字段必须设置为NULL。实际中,我们用memset将整个结构清零,然后有选择地设置一些字段。
下面对addrinfo的每个字段详细分析:
-
ai_family:指定返回地址的地址族。
常量名 | 取值 | 含义 |
AF_INET | 2 | IPv4 |
AF_INET6 | 23 | IPv6 |
AF_UNSPEC | 0 | 协议无关 |
-
ai_socktype:指定套接字的类型
常量名 | 取值 | 含义 |
SOCK_STREAM | 1 | 流 |
SOCK_DGRAM | 2 | 数据报 |
-
ai_protocol:指定socket的协议
常量名 | 取值 | 含义 |
IPPROTO_IP | 0 | 协议无关 |
IPPROTO_IPV4 | 4 | IPv4 |
IPPROTO_IPV6 | 41 | IPv6 |
IPPROTO_UDP | 17 | UDP |
-
ai_flags:一个位掩码
A1_ADDRCONFIG:它要求只有当本地主机被配置为IPv4时,getaddrinfo返回IPv4地址。对IPv6也是类似。
AI_CANONNAME:ai_canonname字段默认为NUlL。如果设置了该标志,就是告诉getaddrinfo将列表中第一个addrinfo结构的ai_canonname字段指向host的权威(官方)名字。AI_NUMERICSERV:参数service默认可以是服务名或端口号。这个标志强制参数service为端口号。
AL_PASSIVE:getaddrinfo默认返回套接字地址,客户端可以在调用connect时用作主动套接字。这个标志告诉该函数,返回的套接字地址可能被服务器用作监听套接字。在这种情况中,参数host应该为NULL。得到的套接字地址结构中的地址字段会是通配符地址,告诉内核这个服务器会接受发送到该主机所有IP地址的请求。这是所有示例服务器所期望的行为。
如果将hints指定为 NULL相当于将 ai_socktype 和 ai_protocol 设置为 0;将ai_family设置为AF_UNSPEC;将ai_flags 设置为(AI_V4MAPPED | AI_ADDRCONFIG)。
4.struct addrinfo **result:result一个指向addrinfo结构的链表,其中每个结构指向一个对应于host和service的套接字地址结构。
第二个重点函数就是getnameinfo()。getnameinfo函数和getaddrinfo是相反的,将一个套接字地址结构转换成相应的主机和服务名字符串。它是已弃用的gethostbyaddr和getservbyport函数的新的替代品,和以前的那些函数不同,它是可重入和与协议无关的。
#include <sys/socket.h>
#include <netdb.h>
int getnameinfo(const struct sockaddr *sa,socklen_t salen,
char *host,size_t hostlen,
char *service,size_t servlen,int flags);
//返回:如果成功则为0,如果错误则为非零的错误代码。
参数sa指向大小为salen字节的套接字地址结构,host指向大小为hostlen字节的缓冲区,service指向大小为servlen字节的缓冲区。getnameinfo函数将套接字地址结构sa转换成对应的主机和服务名字符串,并将它们复制到host和servcice缓冲区。如果getnameinfo返回非零的错误代码,应用程序可以调用gai_strerror把它转化成字符串。
如果不想要主机名,可以把host设置为NULL,hostlen设置为0。对服务字段来说也是一样。不过,两者必须设置其中之一。
参数flags是一个位掩码,能够修改默认的行为。可以把各种值用OR组合起来得到该掩码。下面是两个有用的值:
-
NI_NUMERICHOST:getnameinfo默认试图返回host中的域名。设置该标志会使该函数返回一个数字地址字符串。
-
NI_NUMERICSERV:getnameinfo默认会检查/etc/services,如果可能,会返回服务名而不是端口号。设置该标志会使该函数跳过查找,简单地返回端口号。
下面是getaddrinfo()和getnameinfo()函数的应用,一个域名解析程序能更好的理解这两个函数。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netdb.h>
#define MAXLINE 8192
int main(int argc ,char **argv)
{
struct addrinfo *p,*listp,hints;
char buf[MAXLINE];
int rc,flags;
if(argc!=2)
{
fprintf(stderr,"usag:%s<domain name>\n",argv[0]);
exit(0);
}
memset(&hints,0,sizeof(struct addrinfo));
hints.ai_family=AF_INET;
hints.ai_socktype=SOCK_STREAM;
if((rc=getaddrinfo(argv[1],NULL,&hints,&listp))!=0)
{
fprintf(stderr,"getaddrinfo error :%s",gai_strerror(rc));
exit(1);
}
flags=NI_NUMERICHOST;
for(p=listp;p;p=p->ai_next)
{
getnameinfo(p->ai_addr,p->ai_addrlen,buf,MAXLINE,NULL,0,flags);
printf("%s\n",buf);
}
freeaddrinfo(listp);
exit(0);
}
这段代码比较简单,没有难懂偏僻的语法,逻辑也很清晰,所以就不解释这段代码了。
程序名为hostinfo.c,通过输入gcc -o hostinfo hostinfo.c 指令就能得到hostinfo可执行目标文件。在Linux操作系统环境的运行结果如下:
通过ip地址查询网站得到的结果如下:
运行结果和ip地址查询结果也基本一致。