UNP Chapter 11 - 高级名字与地址转换
11.1. 概述
函数gethostbyname和gethostbyaddr是依赖于协议的,使用前一个函数时,我们必须知道放置结果的套接口地址结构的成员是哪一种(举例来说,IPv4使用sin_addr成员,IPv6使用sin6_addr成员),而调用后一个函数时,必须知道存放二进制地址的是哪一种成员。
getaddrinfo和getnameinfo能为应用程序提供协议独立性。
11.2. getaddrinfo函数
#include <netdb.h>
int getaddrinfo(const char * hostname, const char * service, const struct addrinfo * hints, struct addrinfo * * result); //返回:成功返回0,出错返回非零
这个函数通过result指针返回一个指向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 */
size_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是主机名或地址串,service是服务器或十进制数的端口号字符串,hints是一个空指针或指向一个addrinfo结构的指针,由调用者填写关于它所想返回的信息类型的线索。
11.3. gai_strerror函数
#include <netdb.h>
char * gai_strerror(int error); //返回: 一个指向描述出错信息字符串的指针
11.4. freeaddrinfo函数
由getaddrinfo返回的存储空间,包括addrinfo结构、ai_addr结构、ai_canonname字符串,都是用malloc动态获取的。这些空间可调用freeaddrinfo释放。
#include <netdb.h>
void freeaddrinfo(struct addrinfo * ai);
ai应指向getaddrinfo返回的第一个addrinfo结构。在该链表中的所有结构,以及这些机构所指向的动态存储空间都将被释放。
只复制addrinfo结构,而不复制addrinfo结构所指向的其他结构,叫做浅拷贝或浅复制(shallow copy)。复制addrinfo结构,同时复制addrinfo结构所指向的其他结构,称为深拷贝或深复制(deep copy)。
11.5. getaddrinfo函数:IPv6和UNIX域
11.6. getaddrinfo函数:例子
11.7. host_serv函数
我们给getaddrinfo设计的第一个接口不需要调用者来分配和填写hints结构。这个host_serv函数以地址族和套接口类型作为参数:
#include "unp.h"
struct addrinfo * host_serv(const char * hostname, const char * service ,int family, int socktype); //返回:成功返回指向addrinfo结构的指针,出错返回NULL
函数源代码:
#include "unp"
struct addrinfo * host_serv(const char * host, const char * serv, int family, int socktype)
{
int n;
struct addrinfo hints, *res;
bzero(&hints, sizeof(sturct addrinfo));
hints.ai_flags = AI_CANONNAME; /* always return canonical name */
hints.ai_family = family; /* AF_UNSPEC, AF_INET, AF_INET6, etc. */
hints.ai_socktype = socktype; /* 0, SOCK_STREAM, SOCK_DGRAM, etc. */
if((n=getaddrinfo(host, serv, &hints, &res)) != 0)
return(NULL);
return(res); /* return pointer to first on linked list */
}
11.8. tcp_connect函数
现在我们编写两个使用getaddrinfo的函数,以处理我们编写的TCP客户和服务器程序的大部分情况。第一个函数即tcp_connect执行客户程序的一般步骤:创建一个TCP套接口并与服务器建立连接。
#include "unp.h"
int tcp_connect(const char * hostname, const char * service); //返回: 如成功则返回已连接套接口的描述字,出错则不返回
它的源代码如下:
#include "unp.h"
int tcp_connect(const char * host, const char * serv)
{
int sockfd, n;
struct addrinfo hints, * res, * ressave;
bzero(&hints, sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
if((n = getaddrinfo(host, serv, &hints, &res)) != 0)
err_quit("tcp_connect error for %s, %s: %s", host, serv, gai_strerror(n));
ressave = res;
do{
sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if(sockfd < 0)
continue; /* ignore this one */
if(connect(sockfd, res->ai_addr, res->ai_addrlen) == 0)
break; /* success */
Close(sockfd); /*ignore this one */
} while( (res = res->ai_next) != NULL);
if(res == NULL) /* errno set from final connect() */
err_sys("tcp_connect error for %s, %s", host, serv);
freeaddrinfo(ressave);
return(sockfd);
}
用tcp_connect重新编码的时间日期客户程序
#include "unp.h"
int main(int argc, char * * argv)
{
int sockfd, n;
char recvline[MAXLINE+1];
socklen_t len;
struct sockaddr * sa;
if(argc != 3)
err_quit("usage: daytimectpcli <hostname/IPaddress> <service/port#>");
sockfd = Tcp_connect(argv[1],argv[2]);
sa = Malloc(MAXSOCKADDR);
len = MAXSOCKADDR;
Getpeername(sockfd, sa, &len);
printf("connect to %s \n", Sock_ntop_host(sa,len));
while((n = Read(sockfd, recvline,MAXLINE)) > 0)
{
recvline[n] = 0; /* null terminate */
Fputs(recvline,stdout);
}
exit(0);
}
11.9. tcp_listen函数
下一个函数即tcp_listen执行TCP服务器程序的一般步骤:创建一个TCP套接口,给它捆绑服务器的众所周知端口,并允许接受外来的连接请求。
#include "unp.h"
int tcp_listen(const char * hostname, const char * service, socklen_t * lenptr); //返回: 成功返回已连接套接口描述字,出错则不返回
它的源代码如下:
#include "unp.h"
int top_listen(const char * host, const char * serv, socklen_t * addrlenp)
{
int listenfd, n;
const int on = 1;
struct addrinfo hints, * res, * ressave;
bzero(&hints, sizeof(struct addrinfo));
hints.ai_flags = AI_PASSIVE;
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_STREAM;
if( (n = getaddrinfo(host, serv, &hints, &res)) !=0 )
err_quit("tcp_listen error for %s, %s: %s", host, serv, gai_strerror(n));
ressave = res;
do{
listenfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if(listen<0)
continue; /* error, try next one */
Setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
if(bind(listenfd, res->ai_addr, res->ai_addrlen) == 0)
break; /* success */
Close(listenfd); /* bind error, close and try next one */
} while( (res = res->ai_next) != NULL);
if(res == NULL) /* errno from final socket() or bind() */
err_sys("tcp_listen error for %s, %s", host ,serv);
Listen(listenfd, LISTENQ);
if(addrlenp)
* addrlenp = res->ai_addrlen; /*return size of protocol address */
freeaddrinfo(ressave);
return(listenfd);
}
用tcp_listen重新编码的时间日期服务器程序
#include "unp.h"
#include <time.h>
int main(int argc, char * * argv)
{
int listenfd, connfd;
socklen_t addrlen, len;
char buff[MAXLINE];
time_t ticks;
struct sockaddr * cliaddr;
if(argc != 2)
err_quit("usage: daytimetcpsrv1 <service or port#>");
listenfd = Tcp_listen(NULL, argv[1], &addrlen);
cliaddr = Malloc(addrlen);
for( ; ; )
{
len = addrlen;
connfd = Accept(listenfd, cliaddr, &len);
printf("connection from %s \n", Sock_ntop(cliaddr, len));
ticks = time(NULL);
sprintf(buff, sizeof(buff), "%.24s \r \n", ctime(&ticks));
Write(connfd, buff, strlen(buff));
Close(connfd);
}
}
11.10. udp_client函数
给getaddrinfo提供简单接口的函数在UDP这儿有所改变,因为这里我们提供了一个创建未连接UDP套接口的客户函数,在下一节中则提供另一个创建已连接UDP套接口的函数
#include "unp.h"
int udp_client(const char * hostname, const char * service, void * * saptr, socklen_t * lenp); // 返回: 成功返回未连接套接口的描述字,出错则不返回
这个函数创建一个未连接UDP套接口,返回三项数据。第一,返回值是套接口描述字。第二,saptr是一个指向套接口地址结构(由udp_client动态分配)的指针(由调用者生命)的地址,在这个结构中存放目的IP地址和端口号,用来调用sendto,套接口地址结构的大小在lenp指向的变量中返回。最后一个参数不能是空指针(tcp_listen的最后一个参数是允许的),因为套接口地址结构的长度在调用sendto和recvfrom时都是需要的。
下面是它的源代码:getaddrinfo转换hostname和service参数,socket创建数据报套接口,malloc为一个套接口地址结构分配内存,并由memcpy将对应于创建的套接口的地址结构拷贝到这个内存区域中
#include "unp.h"
int udp_client(const char * host, const char * serv, void * * saptr, socklen_t * lenp)
{
int sockfd, n;
struct addrinfo hints, * res, * ressave;
bzero(&hints, sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
if( (n = getaddrinfo(host, serv, &hints, &res)) != 0)
err_quit("udp_client error for %s, %s: %s", host, serv, gai_strerror(n));
ressave = res;
do{
sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if(sockfd >= 0)
break; /* success */
} while((res = res->ai_next) != NULL);
if(res == NULL) /* errno set from final socket() */
err_sys("udp_client error for %s, %s", host, serv);
* saptr = Malloc(res->ai_addrlen);
memcpy( * saptr, res->ai_addr, res->ai_addrlen);
* lenp = res->ai_addrlen;
freeaddrinfo(ressave);
return(sockfd);
}
使用UDP和udp_client函数重新编写独立于协议的程序的源代码
#include "unp.h"
int main(int argc, char * * argv)
{
int sockfd, n;
char recvline[MAXLINE+1];
socklen_t salen;
struct sockaddr * sa;
if(argc != 3)
err_quit("usage: daytimeudpcli1 <hostname/IPaddress> <service/port>");
sockfd = Udp_client(argv[1], argv[2], (void * *)&sa, &salen);
printf("sending to %s \n", Sock_notp_host(sa, salen));
Sendto(sockfd, "", 1, 0, sa, salen); /* send 1-byte datagram */
n = Recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL);
recvline[n] = 0; /# null terminate */
Fputs(recvline, stdout);
exit(0);
}
11.11. udp_connect函数
#include "unp.h"
int udp_connect(const char * hostname, const char * service); //返回: 如果成功在返回套接口描述字,出错则不返回
它的源代码如下:
#include "unp.h"
int udp_connect(const char * host, const char * serv)
{
int sockfd, n;
struct addrinfo hints, * res, * ressave;
bzero(&hints, sizeof(struct addrinfo));
hints.ai_family = AF_UNSPEC;
hints.ai_socktype = SOCK_DGRAM;
if((n = getaddrinfo(host, serv, &hints, &res)) != 0)
err_quit("udp_connect error for %s, %s: %s", host, serv, gai_strerror(n));
ressave = res;
do
{
sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if(sockfd < 0)
continue; /* ignore this one */
if(connect(sockfd, res->ai_addr, res->ai_addrlen) == 0)
break; /* success */
Close(sockfd); /* ignore this one */
} while((res = res->ai_next) != NULL);
if(res == NULL) /* errno set from final connect() */
err_sys("udp_connect error for %s, %s", host, serv);
freeaddrinfo(ressave);
return(sockfd);
}
11.12. udp_server函数
我们为简化的getaddrinfo的接口开发的最后一个UDP函数是udp_server
#include "unp.h"
int udp_server(const char * hostname, const char * service, socklen_t * lenptr); //返回:成功返回未连接套接口描述字,失败不返回
参数和tcp_listen一样:一个可选的hostname,一个必需的service(已给它捆绑一个端口)以及一个可选的指针,它指向返回套接口地址结构大小的变量。
#include "unp.h"
int udp_server(const char * host, const char * serv, socklen_t * addrlenp)
{
int sockfd, n;
struct addrinfo hints, * res, * ressave;
bzero(&hints, sizeof(struct addrinfo));
hints.ai_flags = AI_PASSIVE;
hints.ai_family = AF_UNSPEC;
hints.ai_stocktype = SOCK_DGRAM;
if((n = getaddrinfo(host, serv, &hints, &res)) != 0)
err_quit("udp_server error for %s, %s: %s", host, serv, gai_strerror(n));
ressave = res;
do{
sockfd = socket(res->ai_family, res->ai_socktype, res->ai_protocol);
if(sockfd < 0)
continue; /* error, try next one */
if(bind(sockfd, res->ai_addr, res->ai_addrlen) == 0)
break; /* success */
Close(sockfd); /* bind error, close and try next one */
} while((res = res->next) != NULL);
if(res == NULL) /* error from final socket() or bind() */
err_sys("udp_server error fro %s, %s", host, serv);
if(addrlenp)
* addrlenp = res->ai_addrlen; /* return size of protocol address */
freeaddrinfo(ressave);
return(sockfd);
}
11.13. getnameinfo函数
这个函数与getaddrinfo互补:它以一个套接口地址为参数,返回一个描述主机的字符串和一个描述服务的字符串。这个函数以一种独立于协议的方式提供这些信息
#include <netdb.h>
int getnameinfo(const struct sockaddr * sockaddr, socklen_t addrlen, char * host, size_t hostlen, char * serv, size_t servlen, int flags); // 返回:成功返回0, 出错返回-1
sockaddr指向包含协议地址的套接口地址结构,它将会被转换成可读的字符串,addrlen是结构的长度。这个结构和长度通常由accept, recvfrom, getsockname,getpeername返回。
sock_ntop和getnameinfo的差别在于,前者不差DNS直接返回可输出的IP地址和端口号,而后者通常试图给主机和服务查到一个名字。
11.14. 可重入函数
线程
11.15. gethostbyname_r和gethostbyaddr_r函数
有两种方法可将像gethostbyname这样的不可重入函数变成可重入函数:
1. 变由函数填写并返回一个静态结果为:由调用者分配结构所需的空间,由可重入函数来填写
2. 可重入函数调用malloc动态分配内存
#include <netdb.h>
struct hostent * gethostbyname_r(const char * hostname, struct hostent * result, char * buf, int buflen, int * h_errnop); // 返回: 成功返回非空指针,出错返回NULL
struct hostent * gethostbyaddr_r(const char * addr, int len, int type, struct hostent * result, char * buf, int buflen, int * h_errnop); // 返回: 成功返回非空指针,出错返回NULL
11.16. getaddrinfo和getnameinfo函数的实现
11.17 小结