1 域名系统
域名系统(Domain Name System,DNS)主要用于主机名字与IP地址之间的映射。主机名既可以是一个简单名字(simple name),例如mimiasd,也可以是一个全限定域名(Fully Qualified Domain Name,FQDN),例如mimiasd.unpbook.com。
2 gethostbyname函数
查找主机名最基本的函数是gethostbyname。如果成功,它就返回一个指向hostent结构的指针,该结构中含有所查找主机的所有IPv4地址。这个函数只能处理IPv4的地址。gethostbyname2函数通过设置AF值,可查看IPv6的地址。
#inlcude <netdb.h>
/* Return entry from host data base for host with NAME.
This function is a possible cancellation point and therefore not
marked with __THROW. */
extern struct hostent *gethostbyname (const char *__name);
#ifdef __USE_MISC
/* Return entry from host data base for host with NAME. AF must be
set to the address type which is `AF_INET' for IPv4 or `AF_INET6'
for IPv6.
This function is not part of POSIX and therefore no official
cancellation point. But due to similarity with an POSIX interface
or due to the implementation it is a cancellation point and
therefore not marked with __THROW. */
extern struct hostent *gethostbyname2 (const char *__name, int __af);
本函数返回的非空指针指向如下的hostent结构。
/* Description of data base entry for a single host. */
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. */
#if defined __USE_MISC || defined __USE_GNU
# define h_addr h_addr_list[0] /* Address, for backward compatibility.*/
#endif
};
图1-1 hostent结构和它所包含的信息
gethostbyname与我们介绍的其他套接字函数的不同之处在于:当错误发生时,它不设置errno变量,而是将全局整数变量h_errno设置为在头文件<netdb.h>中定义的下列常值之一:
/* Possible values left in `h_errno'. */
# define HOST_NOT_FOUND 1 /* Authoritative Answer Host not found. */
# define TRY_AGAIN 2 /* Non-Authoritative Host not found,
or SERVERFAIL. */
# define NO_RECOVERY 3 /* Non recoverable errors, FORMERR, REFUSED,
NOTIMP. */
# define NO_DATA 4 /* Valid name, no data record of requested
type. */
例子:
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
int main( int argc, char **argv )
{
char *ptr, **pptr;
char str[ INET_ADDRSTRLEN ];
struct hostent *hptr;
// 给每个命令行参数调用gethostname
while( --argc > 0 )
{
// 输出规范主机名
ptr = *++argv;
if( ( hptr = gethostbyname( ptr ) ) == NULL )
{
printf( " 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 );
// pptr指向一个指针数组,其中每个指针指向一个地址。对于每一个地址,我们调用inet_ntop并输出返回的字符串
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( " unknown address type " );
break;
}
}
exit( 0 );
}
3 gethostbyaddr函数
gethostbyaddr函数试图由一个二进制的IP地址找到相应的主机名,与gethostbyname的行为正好相反。
#inlcude <netdb.h>
/* Return entry from host data base which address match ADDR with
length LEN and type TYPE.
This function is a possible cancellation point and therefore not
marked with __THROW. */
extern struct hostent *gethostbyaddr (const void *__addr, __socklen_t __len,
int __type);
4 getservbyname函数和getservbyport函数
像主机一样,服务也通常靠名字来认识。如果沃恩在程序代码中通过其名字而不是其端口号来指代一个服务,而且从名字到端口号的映射关系保存在一个文件中(通常是/etc/services),那么即使端口号发生变动,我们需要修改的仅仅是/etc/services文件中的某一行,而不是重新编译应用程序。getservbyname函数用于根据给定查找应用服务。
#inlcude <netdb.h>
/* Return entry from network data base for network with NAME and
protocol PROTO.
This function is a possible cancellation point and therefore not
marked with __THROW. */
extern struct servent *getservbyname (const char *__name, const char *__proto);
本函数返回的非空指针指向如下的servent结构。
/* Description of data base entry for a single service. */
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 *sptr;
sptr = getservbyname( " domain ", " udp " ); // DNS using UDP
sptr = getservbyname( " ftp ", " tcp " ); // FTP using TCP
sptr = getservbyname( " ftp ", NULL ); // FTP using TCP
sptr = getservbyname( " ftp ", " udp " ); // this call will fail
下一个函数getservbyport用于根据给定端口号和可选协议查找相应服务。
#inlcude <netdb.h>
/* Return entry from service data base which matches port PORT and
protocol PROTO.
This function is a possible cancellation point and therefore not
marked with __THROW. */
extern struct servent *getservbyport (int __port, const char *__proto);
port函数的值必须为网络字节序。本函数的典型调用如下;
struct servent *sptr;
sptr = getservbyport( hton( 53 ), " udp " ); // DNS using UDP
sptr = getservbyport( hton( 21 ), " tcp " ); // FTP using TCP
sptr = getservbyport( hton( 21 ), NULL ); // FTP using TCP
sptr = getservbyport( hton( 21 ), " udp " ); // this call will fail
必须清楚的是,有些端口号在TCP上用于一种服务,在UDP上却用于完全不同的另一种服务。
例子:使用gethostbyname和getservbyname
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <error.h>
#include <string.h>
#include <arpa/inet.h>
#include <sys/un.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define MAX_MESG_SIZE 1024
char * sock_ntop(const struct sockaddr *sa, socklen_t salen);
char * Sock_ntop(const struct sockaddr *sa, socklen_t salen);
int main( int argc, char **argv )
{
int sockfd, n;
char recvline[ MAX_MESG_SIZE + 1 ];
struct sockaddr_in servaddr;
struct in_addr **pptr;
struct in_addr *inetaddrp[ 2 ];
struct in_addr inetaddr;
struct hostent *hp;
struct servent *sp;
// 第一个命令行参数是主机名,我们把它作为参数传递给gethostbyname,第二个命令行参数是服务名,我们把它作为
// 参数传递给getservbyname。假设我们的代码使用TCP,我们把它作为getservbyname的第二个参数。如果
// gethostbyname名字查找失败,我们就尝试使用inet_aton函数,确定其参数是否已是ASCII格式的地址,若是则构造
// 一个由相应的地址构成的单元素列表。
if( argc != 3 )
{
printf( " usage:daytimecpcli1 <hostname> <service> \n" );
exit( 1 );
}
if( ( hp = gethostbyname( argv[ 1 ] ) ) == NULL )
{
if( inet_aton( argv[ 1 ], &inetaddr ) == 0 )
{
printf( " hostname error for %s: %s \n", argv[ 1 ], hstrerror( h_errno ) );
exit( 1 );
}
else
{
inetaddrp[ 0 ] = &inetaddr;
inetaddrp[ 1 ] = NULL;
pptr = inetaddrp;
}
}
else
{
pptr = ( struct in_addr ** ) hp->h_addr_list;
}
if( ( sp = getservbyname( argv[ 2 ], "tcp" ) ) == NULL )
{
printf( " getservbyname error for %s \n", argv[ 2 ] );
exit( 1 );
}
// 我们把对socket和connect的调用放在一个循环中,该循环为服务器主机的每个地址执行一次,直到
// connect成功或IP地址列表试完为止。调用socket以后,我们以服务器主机的IP地址和端口填装网际网套接字
// 地址结构。尽管我们可以把对bzero的调用和它后面的两个赋值语句置于循环体之外以提高执行效率,不过下面代码要
// 简介些。与服务器建立连接几乎不会称为网络客户的性能瓶颈。
for( ; *pptr != NULL; pptr++ )
{
sockfd = socket( AF_INET, SOCK_STREAM, 0 );
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( ( struct sockaddr* ) &servaddr, sizeof( servaddr ) ) );
//接着调用connect。如果调用成功,那就使用break语句终止循环,否则输出一个出错消息并关闭套接字。我们直到
// connect调用失败的描述符必须关闭,不能再用。
if( connect( sockfd, ( struct sockaddr* ) &servaddr, sizeof( servaddr ) ) == 0 )
break; // success
else
{
printf( " connect error \n" );
close( sockfd );
}
}
if( *pptr == NULL )
{
printf( " unable to connect \n" );
exit( 1 );
}
while( ( n = read( sockfd, recvline, MAX_MESG_SIZE ) ) > 0 )
{
recvline[ n ] = 0; // null terminate
fputs( recvline,stdout );
}
exit( 0 );
}
5 getaddrinfo函数
gethostbyname和gethostbyaddr这两个函数仅仅支持IPv4。getadddrinfo函数能够处理名字到地址以及服务到端口这两种转换,返回的是一个sockaddr结构而不是一个地址列表。
#include <netdb.h>
/* Translate name of a service location and/or a service name to set of
socket addresses.
This function is a possible cancellation point and therefore not
marked with __THROW. */
extern int getaddrinfo (const char *__restrict __name,
const char *__restrict __service,
const struct addrinfo *__restrict __req,
struct addrinfo **__restrict __pai);
本函数通过__pai指针参数返回一个指向addrinfo结构链表的指针,而adddrinfo结构定义在头文件<netdb.h>中。
/* Structure to contain information about address of a service provider. */
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. */
};
在addrinfo结构中返回的信息可现在用于socket调用,随后现成用于适合客户的connect或senfto调用,或者是适合服务器的bind调用。
图1-2 getaddrinfo返回信息的实例
端口53用于domain服务。这个端口在套接字地址结构中按照网络字节序存放。返回的ai_protocol值或为IPPROTO_TCP,或为IPPROTO_UDP。
图1-3 为每个IP地址返回的addrinfo结构的数目
6 gai_strerror函数
图1-4给出了可由getaddrinfo返回的非0错误值的名字和含义。gai_strerror以这些值为它的唯一参数,返回一个指向对应的出错信息串指针。
/* Convert error return from getaddrinfo() to a string. */
extern const char *gai_strerror (int __ecode) __THROW;
图1-4 getaddrinfo返回的非0错误常值
7 freeaddrinfo函数
由getaddrinfo返回的所有存储空间都使动态获取的(譬如来自malloc调用),包括addrinfo结构、ai_addr结构和ai_cannonname字符串。这些诶储存空间通过调用freeaddrinfo返还给系统。
#include <netdb.h>
/* Free `addrinfo' structure AI including associated storage. */
extern void freeaddrinfo (struct addrinfo *__ai) __THROW;
__ai参数指向由getaddrinfo返回的第一个adddinfo结构。这个链表中的所有结构以及由它们指向的任何动态存储空间都被释放掉。