名字与地址转换
域名系统
gethostbyname()/gethostbyaddr()
通过主机名查找IPV4地址
struct hostent *gethostbyname(const char *name);
//同过一个二进制的IP地址找到一个相应的主机名
//这两个不可重入函数,因为返回指向同一个静态变量结构的指针
struct hostent *gethostbyaddr(const void *addr,socklen_t len, int type);
struct hostent {
char h_name; / official name of host */
char *h_aliases; / alias list */
int h_addrtype; /* host address type : AF_INET */
int h_length; /* length of address */
char *h_addr_list; / list of addresses */
}
#include <stdio.h>
#include <stdlib.h>
#include <netdb.h>
#include <arpa/inet.h>
#define INET_ADDRSTRLEN 16
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)
{
fprintf(stderr,"gethostbyname() error for host :%s: %s",\
ptr,hstrerror(h_errno));
continue;
}
printf("主机域名为 :%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;
}
}
}
return 0;
}
//执行结果
./main www.sina.com
主机域名为 :polaris.sina.com.cn
alias:www.sina.com
alias:us.sina.com.cn
alias:news.sina.com.cn
alias:jupiter.sina.com.cn
address:202.108.33.107
getserverbyname()/getserverbyport()
服务也通常有靠名字来认知,如果我们程序中通过名字而不是其端口牢指代一个服务器,而且从名字到端口的映射关系保存在一个文件中(/etc/services),那么即使端口号发生变动,我们只需修改/etc/services ,而不必重新编译应用程序
struct servent *getservbyname(const char *name, const char *proto);
struct servent *getservbyport(int port, const char *proto);struct servent { char *s_name; /* official service name */ char **s_aliases; /* alias list */ int s_port; /* port number,network byte odeer */ char *s_proto; /* protocol to use */
grep -e ^ssh /etc/services
ssh 22/tcp # SSH Remote Login Protocol
ssh 22/udpstruct servent *sptr
sptr = getservbyname(“ssh”,”tcp”) /SSH using TCP/
sptr = getservbyname(“ssh”,”udp”)/ssh using udp/sptr = getservbyport(htons(22),”tcp”);
#include <stdio.h>
#include <stdlib.h>
#include <netdb.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>
#define MAXLINE 4096
int main(int argc, char *argv[])
{
int sockfd,n;
char recvline[MAXLINE+1];
struct sockaddr_in servaddr;
struct in_addr **pptr;
struct in_addr *inetaddrp[2];
struct in_addr inetaddr;
struct hostent *hp;
struct servent *sp;
if(argc !=3)
{
fprintf(stderr,"usage: daytimetcpcli1<hostname> <server>");
exit(1);
}
if((hp = gethostbyname(argv[1]))== NULL) /*通过域名获取IP地址*/
{
/*将一个字符串IP地址转换为一个32位的网络序列IP地址*/
if(inet_aton(argv[1],&inetaddr)==0)
{
fprintf(stderr,"hostname error for %s: %s\n",argv[1],\
hstrerror(h_errno));
}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)
{
fprintf(stderr,"getservbyname() error for %s\n",argv[2]);
exit(1);
}
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",inet_ntoa(servaddr.sin_addr));
if(connect(sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr))==0)
break;
fprintf(stderr,"connect error\n");
close(sockfd);
}
if(*pptr == NULL)
{
fprintf(stderr,"unable to connect");
exit(1);
}
while((n = read(sockfd,recvline,MAXLINE))>0)
{
recvline[n] = 0;
fputs(recvline,stdout);
}
return 0;
}
//执行结果
./main os ssh
trying 127.0.1.1
SSH-2.0-OpenSSH_7.2p2 Ubuntu-4ubuntu2.1
getaddrinfo()
gethostbyname()/gethostbyaddr这俩个函数只支持IPv4
getaddrinfo()函数能够处理名字到地址以及服务到端口的这两种转换,返回的是一个sockaddr结构,这些sockaddr结构随后可由套接字函数直接使用int getaddrinfo(const char *node, const char *service,const struct addrinfo *hints,struct addrinfo **res);
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; };
node:一个主机名或者地址串(IPv4的点分十进制串或者IPv6的16进制串)
service:服务名可以是十进制的端口号,也可以是已定义的服务名称,如ftp、http等
hints:可以是一个空指针,也可以是一个指向某个addrinfo结构体的指针,调用者在这个结构中填入关于期望返回的信息类型的暗示。举例来说:指定的服务既可支持TCP也可支持UDP,所以调用者可以把hints结构中的ai_socktype成员设置成SOCK_DGRAM使得返回的仅仅是适用于数据报套接口的信息。res:本函数通过result指针参数返回一个指向addrinfo结构体链表的指针。
返回值:0——成功,非0——出错ai_flags标志极其含义如下:
AI_PASSIVE 套接字永远被动打开
AI_CANONNAME 告知getaddrinfo函数返回主机的规范名字
AI_NUMERICHOST 防止任何类型的名字到地址的映射,node参数必须是一个字符串
AI_V4MAPPED 如果指定ai_family 成员的值为AF_INET6,那么如果没有可用的AAAA记录,就返回与A记录相对应的IPv4地址的IPV6映射
A_ALL 如果同时指定AI_V4MAPPED标志,那么除了返回AAAA记录想对应的ipv6地址外,还返回与A记录映射的ipv6地址getnameinfo()他把套接字地址结构转换成主机名和服务器名
gai_strerror()
处理getadrinfo()的出错信息,返回:指向错误描述消息字符串的指针
const char *gai_strerror(int errcode);
freeaddrinfo()
void freeaddrinfo(struct addrinfo *res);
由于getaddrinfo()返回的所有存储空间都是动态获取的,这些存储空间通过freeaddrinfo还给系统
#include <stdio.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <time.h>
#include <netdb.h>
#include <string.h>
#include <unistd.h>
#define MAXLINE 4096
/*getaddrinfo的接口函数 */
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)
{
fprintf(stderr,"tcp_connect error for %s,%s,\%s",\
host,serv,gai_strerror(n));
exit(1);
}
ressave = res;
do{ /*创建套接字描述符*/
sockfd = socket(res->ai_family,res->ai_socktype,res->ai_protocol);
if(sockfd<0)
{
continue;
}
if(connect(sockfd,res->ai_addr,res->ai_addrlen)==0)
{
break; /*连接成功*/
}
close(sockfd);/*连接失败 关闭描述符号*/
}while((res = res->ai_next)!=NULL);
if(res == NULL)
{
fprintf(stderr,"tcp_connect error for %s ,%s",host,serv);
}
freeaddrinfo(ressave); /*释放内存*/
return(sockfd);
}
int main(int argc, char *argv[])
{
int sockfd,n;
char recvline[MAXLINE+1];
socklen_t len;
struct sockaddr_storage ss;
if(argc != 3)
{
fprintf(stderr,"main <hostname/IPaddress> <service/port#>");
exit(1);
}
sockfd = tcp_connect(argv[1],argv[2]);
len = sizeof(ss);
getpeername(sockfd,(struct sockaddr*)&ss,&len); /*获取socket的对方地址*/
char str[128];/*unix domian is largest*/
char portstr[8];
switch(ss.ss_family)
{
case AF_INET:
{
struct sockaddr_in *sin = (struct sockaddr_in*)&ss;
if(inet_ntop(AF_INET,&sin->sin_addr.s_addr,str,sizeof(str))==NULL)
break;
if(ntohs(sin->sin_port)!=0)
{
snprintf(portstr,sizeof(portstr),":%d",ntohs(sin->sin_port));
strcat(str,portstr);
}
break;
}
case AF_INET6:
{
struct sockaddr_in6 *sin6 =(struct sockaddr_in6*)&ss;
if(inet_ntop(AF_INET6,&sin6->sin6_addr,str,sizeof(str))==NULL)
{
break;
}
if(ntohs(sin6->sin6_port)!=0)
{
snprintf(portstr,sizeof(portstr),"%d",ntohs(sin6->sin6_port));
strcat(str,portstr);
}
break;
}
}
printf("connect to : %s\n",str);
while((n = read(sockfd,recvline,MAXLINE))>0)
{
recvline[n] = 0;
fputs(recvline,stdout);
}
printf("Hello World!\n");
return 0;
}
getnameinfo()
getnameinfo()是getaddrinfo()的互补函数 ,它以一个套接字地址为参数,返回其中的主机的一个字符和描述其中服务器的另一个字符串
int getnameinfo(const struct sockaddr *sa, socklen_t salen,char *host, socklen_t hostlen,char *serv, socklen_t servlen, int flags);
sockaddr指向一个套接字地址结构,其中包含待转换成直观可读的字符串的协议地址,salen是这个结构的长度,
host,hostlen指定主机字符串
serv,servlen指定服务器字符串,
如果把hostlen=0,servlen=0;就不返回主机字符串