前言
在套接字编程中,我们经常使用数字的 IP 地址和端口号进程编程,但是我们平常所熟悉的是一些便于记忆的字符名字,要使这种名字能够为套接字操作函数识别,所以这两者之间必须存在着某种转换关系。本节介绍的是 【主机名 与
地址】 和 【服务名 与 端口号】 之间的转换。在 Unix 系统中,可以使用函数 gethostbyname、gethostbyaddr 实现【主机名 与 地址】之间的转换;可以使用函数 getservbyname、getservbyport 实现 【服务名 与 端口号】
之间的转换。但是前面这些函数只适合在 IPv4 域里面,若要在 IPv4 和 IPv6 实现这些功能,则可以使用 getaddrinfo 函数。
主机名 与 地址 之间的转换
gethostbyname 与 gethostbyaddr 函数
/* 主机名与地址之间转换 */ /* * 函数功能:主机名与地址之间转换; * 返回值:若成功则返回主机结构指针,若出错则返回NULL; * 函数原型: */ #include <netdb.h> struct hostent *gethostbyname(const char *hostname);//将主机名转换为数字地址; struct hostent *gethostaddr(const char *addr, size_t len, int family);//将数字地址转换为主机名; /* 函数功能:获取主机信息; * 函数原型: */ struct hostent *gethostent(void);/* 获取主机信息,并返回hostent结构指针 */ void sethostent(int stayopen);/* 设置主机信息 */ void endhostent(void); /* * 说明: * 若主机数据文件没有打开,gethostent会打开它,该函数返回文件的下一条目; * 函数sethostent会打开文件,若文件已打开,那么将其回绕; * 函数endhostent将关闭文件; * 其中hostent结构至少包含如下成员数据: */ struct hostent { char *h_name; /* official name of host */ char **h_aliases; /* pointer to alternate host name array */ int h_addrtype; /* address type: AF_INET */ int h_length; /* length in bytes of address: 4 */ char **h_addr_list; /* pointer to array of IPv4 address */ };
上面的函数若成功调用,则会返回一个指向 hostent 结构的指针,若出错则返回 NULL,且设置全局变量 h_error 为相应值。一般的 socket 系统调用都将错误信息存储在全局变量 error 中,但是和主机 host 有关的系统调用,则将错误信息存储在 h_error 中,它的取值如下:
- HOST_NOT_FOUND:找不到主机;
- TRY_AGAIN:重试;
- NO_RECOVERY:不可修复性错误;
- NO_DATA:指定的名字有效,但是没有记录;
其中,hostent 结构信息之间关系如下图所示:
服务名 与 端口号 之间的转换
getservbyname 与 getservbyport 函数
/* 服务名与端口号之间的转换 */ /* * 函数功能:服务名与端口号之间的转换; * 返回值:若成功则返回指针,若出错则返回NULL; * 函数原型: */ #include <netdb.h> struct servent *getservbyname(const char *servname, const char *protoname); struct servent *getservbyport(int port, const char *protoname); struct servent *getservent(void); void setservent(int stayopen); void endservent(void); /* * protoname参数若为空,则返回取决与实现,若为非空,则指定协议名称; * * 其中servent 结构至少包含以下成员: */ struct servent { char *s_name; /* official service name */ char **s_aliases; /* pointer to alternate service name array */ int s_port; /* port number */ char *s_proto; /* name of protocol */ };
以下是采用上面函数编写的客户端程序:
#include "unp.h" int main(int argc, char **argv) { int sockfd, n; char recvline[MAXLINE + 1]; struct sockaddr_in servaddr; struct in_addr **pptr = NULL; struct in_addr *inetaddrp[2]; struct in_addr inetaddr; struct hostent *hp; struct servent *sp; if (argc != 3) err_quit("usage: %s <hostname> <service>", argv[0]); /* 将主机名作为gethostbyname的参数,并获取hostent结构信息 */ if ( (hp = gethostbyname(argv[1])) == NULL) { /* 若gethostbyname获取失败,则使用inet_aton构造地址单元素列表 */ if (inet_aton(argv[1], &inetaddr) == 0) { err_quit("hostname error for %s: %s", argv[1], hstrerror(h_errno)); } else { inetaddrp[0] = &inetaddr; inetaddrp[1] = NULL; pptr = inetaddrp; } } else {/* 若gethostbyname成功,则pptr指向地址链表 */ pptr = (struct in_addr **) hp->h_addr_list; } /* 将服务名作为getservbyname的参数,获取servent结构信息 */ if ( (sp = getservbyname(argv[2], "tcp")) == NULL) /* 若失败则退出 */ err_quit("getservbyname error for %s", argv[2]); /* 遍历地址结构列表中的每一个地址 */ for ( ; *pptr != NULL; pptr++) { /* 创建基于TCP套接字 */ if( (sockfd = socket(AF_INET, SOCK_STREAM, 0))) err_sys("socket error"); /* 初始化服务器地址信息 */ bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = sp->s_port;/* 端口号由getservbyname获取 */ /* 复制服务器地址信息,该地址信息由gethostbyname获取 */ 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)) == 0) break; /* success */ /* 若地址连接失败,则关闭与该地址相关的套接字 */ err_ret("connect error"); close(sockfd); } /* 检查是否所有服务器地址都连接失败 */ if (*pptr == NULL) err_quit("unable to connect"); /* 处理函数,读取服务器应答信息,并显示到标准输出 */ while ( (n = Read(sockfd, recvline, MAXLINE)) > 0) { recvline[n] = 0; /* null terminate */ Fputs(recvline, stdout); } exit(0); }
【地址 与 主机名】 和 【服务名 与 端口号】之间的转换
getaddrinfo 函数
/* IPv6、IPv4 都可使用 */ /* * 函数功能:将 服务名与端口号 和 主机名与地址 之间转换; * 返回值:若成功则返回0,若出错则返回非0错误编码; * 函数原型: */ #include <netdb.h> #include <sys/socket.h> int getaddrinfo(const char *hostname, const char *service, const struct addrinfo *hints, struct addrinfo **result); void freeaddrinfo(struct addrinfo *ai);/* 把从getaddrinfo函数动态分配的成员结构内存返回给系统,参数ai是由函数getaddrinfo返回的第一个addrinfo结构 */ const char *gai_strerror(int error);//若getaddrinfo出错时,错误消息只能由该函数输出; /* * 说明: * 该函数需要提供主机名或服务名,若只提供其中一个,则另一个必须指定为NULL; * addrinfo是一个结构链表,其定义如下: */ struct addrinfo { int ai_flags; /* customize behavior */ int ai_family; /* address family */ int ai_socktype; /* socket type */ int ai_protocol; /* protocol */ socklen_t ai_addrlen; /* length in bytes of address */ struct sockaddr *ai_addr; /* address */ char *ai_canonname; /* canonical name of host */ struct addrinfo *ai_next; /* next in list */ }; /* * 函数功能:将地址转换成服务名或主机名; * 返回值:若成功则返回0,若出错则返回非0值; * 函数原型: */ #include <netdb.h> #include <sys/socket.h> int getnameinfo(const struct sockadd *addr, socklen_t alen, char * host, socklen_t hostlen, char * service, socklen_t servlen, unsigned int flags); /* * 说明: * addrinfo结构成员: * ai_flags 取值如下: * (1)AI_PASSIVE 套接字将用于被动打开; * (2)AI_CANONNAME 告知getaddrinfo函数返回主机的规范名字; * (3)AI_NUMERICHOST 防止任何类型的名字到地址映射,hostname必须是一个地址串; * (4)AI_NUMERICSERV 防止任何类型的名字到服务映射,service必须是一个十进制端口号数串; * (5)AI_V4MAPPED 若同时指定ai_family值为AF_INET6,若没有可用的AAAA记录,则返回与A记录对应的IPv4映射的IPv6地址; * (6)AI_ALL 若同时指定AI_V4MAPPED标志,除了返回与AAAA记录对应的IPv6地址外,还返回与A记录对应的IPv4映射的IPv6地址; * (7)AI_ADDRCONFIG 按照所在主机的配置选择返回地址类型; */
参考资料:
《Unix 网络编程》
时间: 2024-10-05 06:25:51