域名系统

域名系统(Domain Name System,DNS)主要用于主机名字与IP地址之间的映射。

主机名既可以是一个简单得名字,如solaris,也可以是一个全限定域名,如solaris.unpbook.com。

资源记录

DNS中的条目称为资源记录(resource record,RR)

A记录把一个主机名映射成一个32位的IPv4的地址。

AAAA记录把一个主机名映射成一个128位的IPv6地址。

PTR记录把IP地址映射成主机名。

MX记录把一个主机指定作为主机的“邮件交换器(mail exchanger)”。

CNAME代表“cononical name”(规范名字),它的常见用法为常用的服务指派CNAME记录,例如下面名为linux的主机有以下2个CNAME记录

解析器和名字服务器

每个组织机构往往运行一个或多个名字服务器,它们通常是所谓的BIND(Berkeley Internet Name Domain)程序。

本书我们编写的应用程序通过调用成为解析器的函数库中的函数接触DNS服务器,常见的解析器函数是gethostbyname和gethostbyaddr。

下图展示了应用程序、解析器和名字服务器的一个典型关系

gethostbyname函数

查找主机名最基本的函数是gethostbyname。如果调用成功,它就返回一个指向hostent结构的指针。

#inlcude <netdb.h>
struct hostent *gethostbyname(const char *hostname); struct hostent {
char *h_name; /* official (canonical) 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 */
};

gethostbyname执行的是对A记录的查询。它只能返回IPv4地址

下图展示了hostent结构和它包含的信息

下面给出调用gethostbyname的简单例子

 #include    "unp.h"

 int
main(int argc, char **argv)
{
char *ptr, **pptr;
char str[INET_ADDRSTRLEN];
struct hostent *hptr; while (--argc > ) {
ptr = *++argv;
if ( (hptr = gethostbyname(ptr)) == NULL) {
err_msg("gethostbyname error for host: %s: %s",
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:
err_ret("unknown address type");
break;
}
}
exit();
}

运行结果

gethostbyaddr函数

与gethostbyname的行为相反,gethostbyaddr函数试图由一个二进制的IP地址找到相应的主机名

#include <netdb.h>
struct hostent *gethostbyaddr(const char *addr,socklen_t len,int family);
//返回:若成功则为非空指针,若出错则为NULL且设置h_errno

addr参数实际上不是char *类型,而是一个指向存放IPv4地址的某个in_addr结构的指针

gethostbyaddr向一个名字服务器查询PTR记录。我们感兴趣的字段通常是规范主机名的h_name。

getservbyname和getservbyport函数

getservbyname函数用于根据给定名字查找相应服务

名字到端口号的映射关系保存在一个文件中(通常是/etc/services)

#include <netdb.h>
struct servent *getservbyname(const char *servername,const char *protoname);

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 */
};

servent结构中我们关心的主要字段是端口号,下面是本函数的典型调用

struct servent *sptr;

sptr=getservbyname("domain","unp");   /* DNS using UNP */
sptr=getservbyname("ftp","tcp"); /* FTP using TCP*/
sptr=getservbyname("ftp",NULL); /* FTP using TCP*/
sptr=getservbyname("ftp","unp"); /* this call will fail */

getservbyport用于根据给定端口号和可选协议查找相应服务

#include <netdb.h>
struct servent *getservbyport(int port,const char *protoname);

port参数的值必须网络字节序。本函数的典型调用如下

struct servent *sptr;

sptr=getservbyport(htons(),"unp");  /* DNS using UDP */
sptr=getservbyport(htons(),"tcp"); /* FTP using TCP*/
sptr=getservbyport(htons(),NULL); /* FTP using TCP*/
sptr=getservbyport(htons(),"unp"); /* this call will fail */

有些端口号在TCP上用于一种服务,在UDP上却用于完全不同的另一种服务

下面是使用gethostbyname和getservbyname的时间获取客户程序

 #include    "unp.h"

 int
main(int argc, char **argv)
{
int sockfd, n;
char recvline[MAXLINE + ];
struct sockaddr_in servaddr;
struct in_addr **pptr;
struct in_addr *inetaddrp[];
struct in_addr inetaddr;
struct hostent *hp;
struct servent *sp; if (argc != )
err_quit("usage: daytimetcpcli1 <hostname> <service>"); if ( (hp = gethostbyname(argv[])) == NULL) {
if (inet_aton(argv[], &inetaddr) == ) {
err_quit("hostname error for %s: %s", argv[], hstrerror(h_errno));
} else {
inetaddrp[] = &inetaddr;
inetaddrp[] = NULL;
pptr = inetaddrp;
}
} else {
pptr = (struct in_addr **) hp->h_addr_list;
} if ( (sp = getservbyname(argv[], "tcp")) == NULL)
err_quit("getservbyname error for %s", argv[]); for ( ; *pptr != NULL; pptr++) {
sockfd = Socket(AF_INET, SOCK_STREAM, ); bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = sp->s_port;
memcpy(&servaddr.sin_addr, *pptr, sizeof(struct in_addr));
printf("trying %s\n",
Sock_ntop((SA *) &servaddr, sizeof(servaddr))); if (connect(sockfd, (SA *) &servaddr, sizeof(servaddr)) == )
break; /* success */
err_ret("connect error");
close(sockfd);
}
if (*pptr == NULL)
err_quit("unable to connect"); while ( (n = Read(sockfd, recvline, MAXLINE)) > ) {
recvline[n] = ; /* null terminate */
Fputs(recvline, stdout);
}
exit();
}

getaddrinfo函数

getaddrinfo函数能够处理名字到地址以及服务到端口的这两种转换,而且支持IPv6。

#include <netdb.h>
int getaddrinfo(const char *hostname,
const char *service,
const struct addrinfo *hint,
struct addrinfo **result);

本函数通过result指针参数返回一个指向addrinfo结构链表的指针,该结构定义如下

struct addrinfo {
int ai_flags;
int ai_family;
int ai_socktype;
int ai_protocol;
socklen_t ai_addrlen;
struct sockaddr *ai_addr;
char *ai_canonname;
struct addrinfo *ai_next;
};

hostname参数是一个主机名或地址串(IPv4点分十进制数串或IPv6十六进制数串)

service参数是一个服务名或十进制端口号数串

hints参数可以是一个空指针,也可以是一个指向某个addrinfo结构的指针,调用者在这个结构中填入关于期望返回的信息类型的暗示。

例如,如果指定的服务既支持TCP也支持UDP,那么调用者可以把hints结构中的ai_socktype成员设置成SOCK_DGRAM,使得返回的仅仅是适用于数据报套接字的信息。

hints结构中调用者可以设置的成员有:

1.ai_flags(0个或多个或在一起的AI_xxx值)

2.ai_family(某个AF_xxx值)

3.ai_socktype(某个SOCK_xxx值)

4.ai_protocol

ai_flags成员可用的标志值及含义如下:

AI_PASSIVE             套接字将用于被动打开

AI_CONONNAME       告诉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       按照所在主机的配置选择返回地址类型

如果本函数返回成功,那么result参数指向的变量已被填入一个指针,它指向由其中的ai_next成员串联起来的addrinfo结构链表。可导致返回多个addrinfo结构的情形有以下两个:

1.如果与hostname参数关联的地址有多个,则每个地址都返回一个对应的结构

2.如果service参数指定的服务支持多个套接字类型,那么每个套接字类型都可能返回一个对应的结构

在addrinfo结构中返回的信息可现成用于socket调用,随后现成用于客户的connect或sendto调用,或者社和服务器的bind调用。

如:socket函数的参数就是addrinfo结构中的ai_family、ai_socktype和ad_addr成员。

下图是getaddrinfo返回信息的实例

下面查看这个函数的一些常见的输入:

1.指定hostname和service。这是TCP或UDP客户进程调用getaddrinfo的常规输入。

该调用返回后,TCP客户在一个循环中针对每个返回的IP地址,逐一调用socket和connect,直到有一个连接成功,或者所有地址尝试完毕为止。

2.典型的服务器值指定service而不指定hostname,同时在hints结构中指定AI_PASSIVE标志。返回的套接字地址结构中应含有一个值为INADDR_ANY(对于IPv4)的IP地址

3.服务器可以使用select或poll函数让服务器进程处理多个套接字。

这种情况下,服务器将遍历getaddrinfo返回的整个addrinfo结构链表,并为每个结构创建一个套接字,再使用select或poll。

gai_strerror函数

下图个出可由getaddrinfo返回的非0错误值的名字和含义。gai_strerror以这些值为它的唯一参数,返回对应的出错字符串

#include <netdb.h>
const char *gai_strerror(int error);

freeaddrinfo函数

由getaddrinfo返回的所有存储空间都是动态分配的。这些存储空间通过调用freeaddrinfo释放

#include <netdb.h>
void freeaddrinfo(struct addrinfo *ai);

下面函数是使用getaddrinfo的接口函数

host_serv函数

host_serv函数不要求调用者分配并填写一个hints结构,该结构中我们感兴趣的两个字段(地址族和套接字类型)成为这个接口函数的参数。

#include    "unp.h"
struct addrinfo *host_serv(const char *host, const char *serv, int family, int socktype);
//返回:若成功则为指向addrinfo结构的指针,若出错则为NULL

下面是该函数的源代码

 struct addrinfo *
host_serv(const char *host, const char *serv, int family, int socktype)
{
int n;
struct addrinfo hints, *res; bzero(&hints, sizeof(struct 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)) != )
return(NULL); return(res); /* return pointer to first on linked list */
}

tcp_connect函数

我们使用getaddrinfo编写tcp_connect函数:创建一个TCP套接字并连接到一个服务器。

#include    "unp.h"
int tcp_connect(const char *host, const char *serv);

下面是该函数的源代码

 #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)) != )
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 < )
continue; /* ignore this one */ if (connect(sockfd, res->ai_addr, res->ai_addrlen) == )
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 + ];
socklen_t len;
struct sockaddr_storage ss; if (argc != )
err_quit("usage: daytimetcpcli <hostname/IPaddress> <service/port#>"); sockfd = Tcp_connect(argv[], argv[]); len = sizeof(ss);
Getpeername(sockfd, (SA *)&ss, &len);
printf("connected to %s\n", Sock_ntop_host((SA *)&ss, len)); while ( (n = Read(sockfd, recvline, MAXLINE)) > ) {
recvline[n] = ; /* null terminate */
Fputs(recvline, stdout);
}
exit();
}

tcp_listen函数

tcp_listen执行TCP服务器的通常步骤:创建一个TCP套接字,给它捆绑服务器的总所周知的端口,并允许接收外来的连接请求。

#include    "unp.h"
int tcp_listen(const char *host, const char *serv, socklen_t *addrlenp);
//返回:若成功则为连接套接字描述符,若出错则不返回

下面是该函数的源代码

 #include    "unp.h"

 int
tcp_listen(const char *host, const char *serv, socklen_t *addrlenp)
{
int listenfd, n;
const int on = ;
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)) != )
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 (listenfd < )
continue; /* error, try next one */ Setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on));
if (bind(listenfd, res->ai_addr, res->ai_addrlen) == )
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 len;
char buff[MAXLINE];
time_t ticks;
struct sockaddr_storage cliaddr; if (argc != )
err_quit("usage: daytimetcpsrv1 <service or port#>"); listenfd = Tcp_listen(NULL, argv[], NULL); for ( ; ; ) {
len = sizeof(cliaddr);
connfd = Accept(listenfd, (SA *)&cliaddr, &len);
printf("connection from %s\n", Sock_ntop((SA *)&cliaddr, len)); ticks = time(NULL);
snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks));
Write(connfd, buff, strlen(buff)); Close(connfd);
}
}

下面是tcp_listen的协议无关时间获取服务器程序

 #include    "unp.h"
#include <time.h> int
main(int argc, char **argv)
{
int listenfd, connfd;
socklen_t len, addrlen;
char buff[MAXLINE];
time_t ticks;
struct sockaddr_storage cliaddr; if (argc == )
listenfd = Tcp_listen(NULL, argv[], &addrlen);
else if (argc == )
listenfd = Tcp_listen(argv[], argv[], &addrlen);
else
err_quit("usage: daytimetcpsrv2 [ <host> ] <service or port>"); for ( ; ; ) {
len = sizeof(cliaddr);
connfd = Accept(listenfd, (SA *)&cliaddr, &len);
printf("connection from %s\n", Sock_ntop((SA *)&cliaddr, len)); ticks = time(NULL);
snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks));
Write(connfd, buff, strlen(buff)); Close(connfd);
}
}

udp_client函数

使用getaddrinfo来编写udp_client函数。

本函数创建一个为连接的UDP套接字,并返回3项数据。

1.返回值是该套接字的描述符

2.把目的IP地址和端口存放在saptr指向的套接字地址结构中,用于稍后调用sendto

3.这个套接字地址结构的大小在lenp指向的变量中返回

#include    "unp.h"
int udp_client(const char *host, const char *serv, SA **saptr, socklen_t *lenp);
//返回:若成功则为未连接套接字描述符,若出错则不返回

下面是该函数的源代码

 #include    "unp.h"

 int
udp_client(const char *host, const char *serv, SA **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)) != )
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 >= )
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);
}

协议无关时间获取客户程序

 #include    "unp.h"

 int
main(int argc, char **argv)
{
int sockfd, n;
char recvline[MAXLINE + ];
socklen_t salen;
struct sockaddr *sa; if (argc != )
err_quit("usage: daytimeudpcli1 <hostname/IPaddress> <service/port#>"); sockfd = Udp_client(argv[], argv[], (void **) &sa, &salen); printf("sending to %s\n", Sock_ntop_host(sa, salen)); Sendto(sockfd, "", , , sa, salen); /* send 1-byte datagram */ n = Recvfrom(sockfd, recvline, MAXLINE, , NULL, NULL);
recvline[n] = '\0'; /* null terminate */
Fputs(recvline, stdout); exit();
}

udp_connect函数

udp_connect函数创建一个已连接UDP套接字

#include    "unp.h"
int udp_connect(const char *host, const char *serv);
//若成功则为已连接套接字描述符,若出错则不返回

下面是该函数的源代码

 #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)) != )
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 < )
continue; /* ignore this one */ if (connect(sockfd, res->ai_addr, res->ai_addrlen) == )
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);
}

udp_server函数

本函数的参数与tcp_listen一样,有一个可选的hostname和一个必须的service(从而可以绑定其端口号),以及一个可选的指向某个变量的指针,用于返回套接字地址结构的大小

#include    "unp.h"
int udp_server(const char *host, const char *serv, socklen_t *addrlenp);
//返回:若成功则为未连接套接字描述符,若出错则不返回

下面是该函数的源代码

 #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_socktype = SOCK_DGRAM; if ( (n = getaddrinfo(host, serv, &hints, &res)) != )
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 < )
continue; /* error - try next one */ if (bind(sockfd, res->ai_addr, res->ai_addrlen) == )
break; /* success */ Close(sockfd); /* bind error - close and try next one */
} while ( (res = res->ai_next) != NULL); if (res == NULL) /* errno from final socket() or bind() */
err_sys("udp_server error for %s, %s", host, serv); if (addrlenp)
*addrlenp = res->ai_addrlen; /* return size of protocol address */ freeaddrinfo(ressave); return(sockfd);
}

协议无关时间获取服务器程序

 #include    "unp.h"
#include <time.h> int
main(int argc, char **argv)
{
int sockfd;
ssize_t n;
char buff[MAXLINE];
time_t ticks;
socklen_t len;
struct sockaddr_storage cliaddr; if (argc == )
sockfd = Udp_server(NULL, argv[], NULL);
else if (argc == )
sockfd = Udp_server(argv[], argv[], NULL);
else
err_quit("usage: daytimeudpsrv [ <host> ] <service or port>"); for ( ; ; ) {
len = sizeof(cliaddr);
n = Recvfrom(sockfd, buff, MAXLINE, , (SA *)&cliaddr, &len);
printf("datagram from %s\n", Sock_ntop((SA *)&cliaddr, len)); ticks = time(NULL);
snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks));
Sendto(sockfd, buff, strlen(buff), , (SA *)&cliaddr, len);
}
}

getnameinfo函数

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 flag);

flags参数是下面6个可指定的标志

可重入函数

gethostbyname函数不是一个可重入的函数。

关于可重入函数可以查看 http://www.cnblogs.com/runnyu/p/4643764.html

gethostbyname_r和gethostbyaddr_r函数提供了对原来两个函数的可重入版本

最新文章

  1. Linux中的用户和用户组
  2. 七种机器内部排序的原理与C语言实现,并计算它们的比较次数与移动次数。
  3. javascript学习总结(二):DOM常用方法。
  4. [java] java解析txt文件
  5. Android mtk单路录音问题
  6. Java 多线程——基础知识
  7. Linux基础--文件与目录管理
  8. 445port入侵具体解释
  9. WPF之Behavior
  10. HDU 4714 Tree2cycle
  11. 无限大整数相加算法的C语言源代码
  12. log4j级别输出
  13. java菜鸟篇&lt;一&gt; 对JsonObject 和JsonArray知识点理解
  14. 怎样给你的Android 安装文件(APK)减肥
  15. nexus REST API /artifact/maven/[resolve|redirect] returns unexpected for v=LATEST
  16. Django Template(模板)
  17. Flask 之东方不败一
  18. AngularJS学习之旅—AngularJS 过滤器(七)
  19. CSS高度塌陷问题解决方案
  20. 检查浏览器是否已经启用Java支持功能

热门文章

  1. nginx通过spawn-fcgi调用C++写的cgi程序
  2. 文件处理.Windows.Fastcopy.3.50.x64.文件复制简体中文破解版(验证版)
  3. 妹子(girls)
  4. 原生js实现tooltip提示框的效果
  5. 一个javascript继承和使用的例子
  6. 【转】Nodejs学习笔记(二)--- 模块
  7. pat 甲级 1010. Radix (25)
  8. Android Timer 的 schedule()方法定时循环切换图片
  9. ios 地图,系统升级为12后,进入地图,大头针全部默认展开问题,以及在选择不同距离的情况下,如何刷新地图的区域范围
  10. (5)ASP.NET HttpResponse 类