UNP笔记-名字与地址转换函数
作者:互联网
之前都是使用数值地址来表示主机(比如:127.0.0.1),用数值端口号来标识服务器(比如:6379)。
但是有时候最好使用名字而不是数值:名字比较容易记住,数值地址容易变动,而名字地址保持不变;随着IPv6上转移,数值地址变得很长,手工键入数值容易出错。之后将有一系列函数用于名字、数值、端口之间的转换。
gethostbyname & gethostbyaddr 函数
gethostbyname函数
查找主机名字最基本的函数时gethostbyname。如果调用成功,就返回一个指向hostent结构体的指针,该结构体中含有查找主机的所有IPv4地址。这个函数局限就是只能返回IPv4的地址,之后提到的getaddrinfo函数将能同时处理IPv4和IPv6地址,POSIX预警在将来的某个版本将撤销gethostbyname函数,然而IPv6能被广泛使用还在遥远的将来,那么在新的程序中应该使用getaddrinfo函数。
gethostbyname函数定义:
#include <netdb.h>
struct hostent* gethostbyname(const char* hostname);
本函数返回的空指针指向如下的hostent结构:
struct hostent
{
char *h_name; /* Official name of host. */
char **h_aliases; /* Alias list. */
int h_addrtype; /* Host address type. */
int h_length; /* Length of address. */
char **h_addr_list; /* List of addresses from name server. */
};
gethostbyname执行的是对A记录的查询,只能返回IPv4地址,其结构信息如下如所示:
geohostbyname和其他套接字函数不同:发生错误时,不设置errno变量,而是将全局整数变量h_errno设置为在头文件<netdb.h>中定义的下列常值之一:
HOST_NOT_FOUND、TRY_AGAIN、NO_RECOVERY、NO_DATA(等同于NO_ADDRESS)。
多数解析器提供名为hstrerror
的函数,它以某个h_errno值作为唯一的参数,返回的是一个const char*指针,指向相应的错误说明。
如下的例子调用gethostbyname,显示所有信息:
#include <unistd.h>
#include <netdb.h>
#include <stdio.h>
#include <arpa/inet.h>
int main(int argc, char **argv)
{
char *ptr, **pptr;
char str[INET_ADDRSTRLEN];
struct hostent *hptr;
while (--argc > 0)
{
ptr = *++argv;
if ((hptr = gethostbyname(ptr)) == NULL)
{
printf("gethostbyname error for host: %s:%s.\n", ptr, hstrerror(h_errno));
continue;
}
printf("official hostname: %s\n", hptr->h_name);
for (pptr = hptr->h_aliases; *pptr != NULL; pptr++)
{
printf("\talias: %s\n", *pptr);
}
switch (hptr->h_addrtype)
{
case AF_INET:
pptr = hptr->h_addr_list;
for (; *pptr != NULL; pptr++)
{
printf("\taddress: %s\n", inet_ntop(hptr->h_addrtype, *pptr, str, sizeof(str)));
}
break;
default:
printf("unknow address type.");
break;
}
}
return 0;
}
运行上面的程序时,后面的参数跟上主机名,就能打印出上图hostent结构体的信息。
gethostbyaddr函数
该函数试图由一个二进制的IP地址找到对应的主机名,与gethostbyname的行为刚好相反。
#include <netdb.h>
struct hostent *gethostbyaddr(const char* addr, socklen_t len, int family);
参数:
addr:实际上不是char*类型,是一个指向存放IPv4地址的某个in_addr结构的指针。
len:是这个结构的大小。
falily:对于IPv4来说,是AF_INET。
返回值:
指向与之前描述同样的hostent结构的指针,在上面的gethostbyhost已经描述过。通常感兴趣的字段是存放规范主机名的h_name。
注意:由于历史原因这两个函数是不可重入的,因为因为它们返回指向同一个静态结构的指针。之后的版本实现了多线程版本,它们的名字都是该函数名加_r结尾的。
getservbyname & getservbyport 函数
getservbyname函数
和主机一样,服务器也通常靠名字来认知。如果我们在代码中通过其名字而不是端口号来指代一个服务,而且从名字到端口号的映射关系保存在一个文件中(通常是/etc/services),那么即使端口号发生变动,我们仅需就改的是/etc/service文件中的某一行,而不用重新编译程序。getservbyname函数用于根据名字查找相应服务。
#include <netdb.h>
struct servent* getservbyname(const char* servname, const char* protoname);
参数:
servname:服务器名字。
protoname:协议。如果指定了proto参数,那么指定服务必须有匹配的协议。游戏因特网服务即用TCP也用UDP,有的则仅支持单个协议。如果protoname未指定而servname指定服务支持多个协议,那么返回哪个端口号取决于实现。
返回值:
成功返回指向struct servent结构体的指针。出错返回NULL。
本函数返回的非空指针指向如下的servent结构:
struct servent
{
char *s_name; /* Official service name. */
char **s_aliases; /* Alias list. */
int s_port; /* Port number. */
char *s_proto; /* Protocol to use. */
};
struct servent结构体中我们关心的主要字段是端口号。既然端口号是以网路字节序返回的,把它存放到套接字地质结构时绝不能调用htons。
本函数典型调用如下:
struct servaddr* sptr;
sptr = getservbyname("domain", "udp");
sptr = getservbyname("ftp", "tcp"); //FTP仅使用支持TCP
sptr = getservbyname("ftp", NULL); //和上面的等价
sptr = getservbyname("ftp", "udp"); //调用失败
getservbyport函数
用于根据给定的端口号和可选协议查找响应服务。
#include <netdb.h>
struct servent* getservbyport(int port, const char* protoname);
参数:
prot:端口。值必须是网络字节序
protoname:协议。和上面getservbyport描述的相同。
返回值:
成功返回指向struct servent结构体的指针,出错返回NULL。
本函数典型调用如下:
struct servent* sptr;
sptr = getservbyport(htons(53), "udp"); //DNS
sptr = getservbyport(htons(21), "tcp"); //TCP
sptr = getservbyport(htons(21), "NULL");//TCP, 和上面相等
sptr = getservbyport(htons(21), "udp"); //调用失败,UPD没有服务使用21端口
getaddrinfo & getnameinfo 函数
getaddrinfo 函数
由于gethostbyname和gethostbyaddr函数仅支持IPv4,又想要解析IPv6就需要getaddrinfo函数。getaddrinfo函数能够处理名字到地址,以及服务到端口的两种转换,返回的是一个sockaddr结构体而不是一个地址列表。
这些sockaddr结构体随后可以由套接字函数直接使用。该函数是协议无关的。定义如下:
#include <netdb.h>
int getaddrinfo(const char* hostname, const char* service, const struct addrinfo* hists, struct addrinfo** result);
参数:
result:该函数通过result指针参数返回一个指向addrinfo结构链表的指针,addrinfo的结构定义如下:
struct addrinfo
{
int ai_flags; /* Input flags. */
int ai_family; /* Protocol family for socket. */
int ai_socktype; /* Socket type. */
int ai_protocol; /* Protocol for socket. */
socklen_t ai_addrlen; /* Length of socket address. */
struct sockaddr *ai_addr; /* Socket address for socket. */
char *ai_canonname; /* Canonical name for service location. */
struct addrinfo *ai_next; /* Pointer to next in list. */
};
hostname:主机名或IPv4或IPv6数串。
service:是一个服务名或十进制端口号数串。
hints:可以是空指针,也可以指向某个addrinfo结构体的指针,调用者在这个结构中填入关于期望返回的信息类型的暗示。如某个服务即支持TCP又支持UDP,如果将hints结构体中ai_socktype成员设置成为SOCK_DGRAM,使得返回的仅仅是适用于数据报套接字的信息。
hints
结构体中调用者可以设置的成员有:
- ai_flags(零个或多个或在一起的AI_xxx值)
- ai_family(某个AF_xxx值)
- ai_socktype(某个sock_xxx值)
- ai_protocol
其中aiflags
成员可用的标志值及其含义如下:
-
AI_PASSIVE套接字将用于被动打开。
-
AI_CANONNAME 告知getaddrinfo函数返回主机的规范名字。
-
AI_NUMERICHOST 防止任何类型的名字到地址映射,hostmame参数必须是一个地址串。
-
AI_NUMERICSERV 防止任何类型的名字到服务映射,service参数必须是一个十进制端口号数串。
-
AI_V4MAPPED 如果同时指定ai_family成员的值为AF_INET6,那么如果没有可用的AAAA记录,就返回与A记录对应的IPv4映射的IPv6地址。
-
AI_ALL 如果同时指定AI_V4MAPPED标志,那么除了返回与AAAA记录对应的IPv6地址外,还返回与A记录对应的IPv4映射的IPv6地址。
-
AL_ADDRCONEIG 按照所在主机的配置选择返回地址类型,也就是只查找与所在主机问馈接口以外的网络接口配置的IP地址版本一致的地址。
返回值:
出错返回非0。成功返回0,由result参数指向的变量已被填入一个指针,它指向的是由其中的ai_next成员串起来的addrinfo结构体链表。导致返回多个addrinfo结构情况如下:
- 如果与hostname参数关联的地址有多个,适用于所请求地址族的每个地址都返回一个对应的结构。
- 如果sevice参数指定的服务支持多个套接字类型,那么每个套接字类型都可能返回一个对应的结构体,取决于hints结构中的ai_socktype成员。
这些结构的返回顺序没有保证。如果hints结构体中设置了AI_CANONNAME标志,那么本函数返回的第一个addrinfo结构的ai_canonname成员指向所查找主机的规范名字。
执行下列程序片,假设主机freebsd4的规范名字是freebsd4.unpbook.com
,并且它在DNS中有两个IPv4地址:
struct addrinfo hints, *res;
bzero(&hints, sizeof(hints));
hints.ai_flags = AI_CANONNAME;
hints.ai_family = AF_INET;
getaddrinfo("freebsd4", "domain", &hints, &res);
返回的信息如下:
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);
参数:
sockaddr:指向一个套接字地址结构。
addrlen:这个地址结构的长度。该结构的长度通常由accept、recvfrom、getsockname或getpeername返货。
host:主机字符串。(调用者预先分配存储空间)
hostlen:主机字符串长度。(如果不想返回字符串,指定为0)
serv:服务字符串。(调用者预先分配存储空间)
servlen:服务字符串长度。(如果不想返回字符串,指定为0)
flats:可指定标志,用于改变getnameinfo的操作(如:NI_DGRAM数据报服务)
返回值:
成功返回0,出错为非0。
该函数与sock_ntop的差别在于,sock_ntop不涉及DNS,只返回IP地址和端口号的一个可显示版本,而该函数通常尝试获取主机的服务的名字。
gai_strerror 函数
该函数用于返回getaddrinfo函数的非0错误所指向的对应的出错信息字符串的指针。
#include <netdb.h>
const char* gai_strerror(int errno);
getaddrinfo返回的非0错误如下:
常值 | 说明 |
---|---|
EAI_AGAIN | 名字解析中临时失败 |
EAI_BADFLAGS | ai_flags的值无效 |
EAI_FAIL | 名字解析中不可恢复地失败 |
EAL_FAMTLY | 不支持ai_family |
EAI_MEMORY | 内存分配失败 |
EAI_NONAME | hostname或service未提供,或者不可知 |
EAL_OVERFLOW | 用户参数缓冲区溢出(仅限getnameinfo()函数) |
EAI_SERVICE | 不支持ai_socktype类型的service |
EAI_SOCKTYPE | 不支持ai_socktype |
EAI_SYSTEM | 在errno变量中有系统错误返回 |
freeaddrinfo函数
由getaddrinfo返回的所有存储空间都是动态获取的,包括addrinfo结构、ai_addr结构和ai_canonname字符串。这些空间需要调用freeaddrinfo函数返还给操作系统。
#include <netdb.h>
void freeaddrinfo(struct addrinfo* ai);
参数:
ai:指向由getaddrinfo返回的第一个addrinfo结构。这个链表中所有的结构以及由它们指向的任何动态存储空间都被释放。
可重入函数讨论
一般在设计多线程时,会讨论可重入函数问题。
由于历史原因,gethostbyname、gethostbyaddr、getservbyname和getservbyport这4个函数是不可重入的因为它们都返回指向同一个静态结构的指针。新版本系统支持多线程的实现了这4个函数的可重入版本,是以它们的名字以_r结尾。inet_pton和inet_ntop总是可重入的。
因历史原因,inet_ntoa是不可重入的,不过支持线程的一些实现提供了使用线程特定数据的可重入版本。
getaddrinfo可重入的前提是由它调用的函数都可重入,这就是说,它应该调用可重入版本的gethostbyname(以解析主机名)和getservbyname(以解析服务名)。本函数返回的结果全部存放在动态分配内存空间的原因之一就是允许它可重入。
getnameinfo可重入的前提是由它调用的函数都可重入,这就是说,它应该调用可重入版本的gethostbyaddr(以反向解析主机名)和getservbyport(以反向解析服务名)。它的2个结果字符串(分别为主机名和服务名)由调用者分配存储空间,从而允许它可重入。
errno变量存在类似的问题,这个整形变量每个进程一个副本。但是一个进程中,有多个线程,一个线程中发生了错误并色湖之了errno的值,从系统调用返回时把错误码存入errno到稍后由程序显示errno的值之间存在一个小的时间窗口,如果这时候另一个线程改变了errno的值,就会影响其他线程。
所以在可重入的函数中不调用任何不可重入的函数,后面章节介绍多线程如何处理errno变量问题。
gethostbyname_r & gethostbyaddr_r 函数
有两种方法可以把诸如gethostbyname之类的不可重入的函数改为可重入。
-
把由不可重入函数填写并返回静态结构的做法改为由调用这分配再由可重入函数填写结构。这就要新增三个参数:指向hostent结构体的指针,指向存放多有其他信息所用缓冲区的指针和其大小,还有存放错误码的变量,因为不能再用全局h_errno。
这种方法gatename和inte_ntop也使用这种方法。
-
由可重入函数调用malloc动态分配空间。这是getaddrinfo使用的技巧。问题是调用该函数的应用进程必须手动释放分配的内存空间,否则内存泄漏。
可重入版本的函数定义如下:
#include <netdb.h>
struct hostent *gethostbyname_r(const char *hostname, struct hostent *result, char *buf, int buflen, int *h_errnop);
struct hostent *gethostbyaddr_r(const char *addr, int len, int type, struct hostent *result, char *buf, int buflen, int *h_errnop);
每个函数都需要4个额外的参数。其中result参数指向由调用者分配并由被调用函数填写的hostent结构。成功返回时本指针同时作为函数的返回值。
buf参数指向由调用者分配且大小为buflen的缓冲区。该缓冲区用于存放规范主机名、别名指针数组、各个别名字符串、地址指针数组以及各个实际地址。由result指向的hostent结构中的所有指针都指向该缓冲区内部。那这个缓冲区要有多大才行呢?不幸的是,就该缓冲区的大小而言,大多数手册页面只是含糊地说“该缓冲区必须大得足以存放与hostent结构关联的所有数据”。gethostbyname当前的实现最多能够返回35个别名指针和35个地址指针,并内部使用一个8192字节的缓冲区存放这些别名和地址。因此大小为8192字节的缓冲区应该足够了。
如果出错,错误码就通过h_errnop指针而不是全局变量h_errno返回。
标签:UNP,返回,函数,ai,重入,笔记,char,地址,struct 来源: https://www.cnblogs.com/crazyang/p/14187114.html