- 浏览: 140259 次
文章分类
最新评论
在网络编程中,尽管大部分情况下操作的都是 IP 地址,但现实中需要记忆的却通常是主机名,因此有必要提供将主机名转换为 IP 地址的函数。
不过在介绍这些函数之前,有必要先对主机名作一些说明。
主机名既可以是一个简单名字,如 solaris,也可以是一个全限定域名(Fully Qualified Domain Name, FODN,也称绝对名字),如 solaris.unpbook.com(严格说来,FQDN 必须要以一个点号结尾,以告知 DNS 解析器该名字是全限定的,从而不必搜索解析器自己维护的可能域名列表,不过很多用户往往都省略了结尾的点号)。为实现主机名与 IP 地址之间的映射,就需要用到域名系统(Domain Name System,DNS)。
DNS 中的条目称为资源记录(resource record,RR)。常用的 RR 类型主要有以下几种:
1、A:A 记录会把一个主机名映射成一个 IPv4 地址。比如下面是 unpbook.com 域中关于主机 freebsd 的 4 个 DNS 记录,其中第一个就是一个 A 记录:
freebsd IN A 12.106.32.254
IN AAAA 3ffe:b80:1f8d:1:a00:20ff:fea7:686b
IN MX 5 freebsd.unpbook,com.
IN MX 10 mailhost.unpbook.com.
2、AAAA:“四 A”记录会把一个主机名映射成一个 128 位的 IPv6 地址,如上所示。
3、MX:MX 记录表示把一个主机指定作为给定主机的“邮件交换器”(mail exchanger)。上例中的主机 freebsd 有 2 个 MX 记录:第一个的优先级值为 5,第二个的优先级为 10。当存在多个 MX 记录时,按照优先级顺序使用,值越小优先级越高。
4、PTR:“指针记录”(pointer record)PTR 会把 IP 地址映射成主机名。在做 PTR 查询时,会先把 IPv4/IPv6 地址反转顺序,每个字节都转换成各自的十进制/十六进制,然后在末尾添加上 in-addr.arpa/ip6.arpa。比如上例中 freebsd 的两个 PTR 记录分别是 254.32.106.12.in-addr.arpa 和 b.6.8.6.7.a.e.f.f.f.0.2.0.0.a.0.1.0.0.0.d.8.f.1.0.8.b.0.e.f.f.3.ip6.arpa。
5、CNAME:CNAME 代表“规范名字”(canonical name)。它经常为常用的服务(如 ftp 和 www)指派 CNAME 记录,这样用户就只需要使用这些服务名而无需知道真实的主机名。比如下面是一个名为 linux 的主机的 2 个 CNAME 记录:
ftp IN CNAME linux.unpbook.com.
www IN CNAME linux.unpbook.com.
很多组织机构往往运行有一个或多个名字服务器(name server),即通常所谓的 BIND(Berkeley Internet Name Domain)程序。而大部分的网络应用程序都是通过调用称为解析器(resolver)的函数库中的函数(如下文的 gethostbyname 等)来接触 DNS 解析器。下图展示了应用进程、解析器和名字服务器之间的一个典型关系。
应用程序使用通常的函数调用来执行解析器中的代码,解析器代码通过读取其系统相关配置文件来确定本组织机构的各个名字服务器的位置(通常使用文件 /etc/resolv.conf 包含本地名字服务器主机的 IP 地址)。解析器先向本地名字服务器发出 UDP 请求进行查询,如果它也不知道,通常也会使用 UDP 向其他名字服务器查询。如果答案太长,超出了 UDP 消息的承载能力,本地名字服务器和解析器就会自动切换到 TCP。
除了使用 DNS 获取名字和地址信息,常用的替代方法还有静态主机文件(通常是 /etc/hosts 文件)、网络信息系统(Network Information System,NIS)以及轻权目录访问协议(Lightweight Directory Access Protocol, LDAP)等。对于开发人员来说,调用诸如 gethostbyname 和 gethostbyaddr 这样的解析器函数即可。
利用主机名查找 IP 最基本的函数是 gethostbyname。它会在调用成功时返回一个指向 hostent 结构的指针,其中就包含了给定主机的所有 IPv4 地址。只能返回 IPv4 地址正是该函数的局限所在,使用后面要介绍的 getaddrinfo 函数则能同时处理 IPv4 和 IPv6 地址,因而为便于兼容,推荐使用后者。而 gethostbyaddr 函数的行为与 gethostbyname 刚好相反,它是试图通过一个二进制的 IP 地址找到相应的主机名。
由此可见,按照 DNS 的说法,gethostbyname 执行的是对 A 记录的查询,因为它返回的是 IPv4 地址,而 gethostbyaddr 则是在 in_addr.arpa 域中向一个名字服务器查询 PTR 记录。
另外要注意的是,这两个函数在发生错误时与其他很多套接字函数不同,它们不设置 errno 变量,而是将全局整数变量 h_errno 设置为在头文件 <netdb.h> 中定义的下列常值之一:
* HOST_NOT_FOUND;
* TRY_AGAIN;
* NO_RECOVERY;
* NO_DATA(等同于 NO_ADDRESS)。
这里的 NO_DATA 表示指定的名字有效,但是没有 A 记录。只有 MX 记录的主机名就是这样的一个例子。现在多数解析器都提供了一个名为 hstrerror 的函数,它接收一个 h_errno 值作为参数,返回对应的错误说明。
此外还需注意,gethostbyaddr 函数的 addr 参数实际上并不是“char *”类型,而是一个指向存放了 IPv4 地址的某个 in_addr 结构的指针,len 参数就是该结构的大小(对于 IPv4 地址为 4)。
下面这个简单例子演示了 gethostbyname 函数的用法。
类似于主机,服务也可以通过名字来认知。通常使用文件 /etc/services 来保存服务名字到端口号的映射关系。函数 getservbyname 可根据给定的名字和可选的协议参数来查找相应的服务,getservbyport 函数则是根据给定的端口号和可选的协议参数来查找相应的服务。
注意,如果指定了 protoname 参数,那么对应的服务必须要有匹配的协议。由于有些网络服务既用 TCP 也用 UDP(如 DNS),有些服务又仅仅支持单个协议(如 FTP 服务就只使用 TCP),所以如果 protoname 未指定而 servname 服务支持多个协议,则返回哪个端口号取决于实现。尽管多数情况下支持多个协议的服务往往使用相同的端口号,但也不能保证一定如此。此外,有些端口号在不同的协议下所代表的服务是不一样的,比如 512~514 范围内的端口。
下面这个程序演示了 gethostbyname 和 getservbyname 函数的用法,它接收 2 个命令行参数:主机名和服务名。它会尝试连接到多宿服务器主机的每个 IP 地址,直到有一个连接成功或所有地址尝试完毕为止。
不过在介绍这些函数之前,有必要先对主机名作一些说明。
主机名既可以是一个简单名字,如 solaris,也可以是一个全限定域名(Fully Qualified Domain Name, FODN,也称绝对名字),如 solaris.unpbook.com(严格说来,FQDN 必须要以一个点号结尾,以告知 DNS 解析器该名字是全限定的,从而不必搜索解析器自己维护的可能域名列表,不过很多用户往往都省略了结尾的点号)。为实现主机名与 IP 地址之间的映射,就需要用到域名系统(Domain Name System,DNS)。
DNS 中的条目称为资源记录(resource record,RR)。常用的 RR 类型主要有以下几种:
1、A:A 记录会把一个主机名映射成一个 IPv4 地址。比如下面是 unpbook.com 域中关于主机 freebsd 的 4 个 DNS 记录,其中第一个就是一个 A 记录:
freebsd IN A 12.106.32.254
IN AAAA 3ffe:b80:1f8d:1:a00:20ff:fea7:686b
IN MX 5 freebsd.unpbook,com.
IN MX 10 mailhost.unpbook.com.
2、AAAA:“四 A”记录会把一个主机名映射成一个 128 位的 IPv6 地址,如上所示。
3、MX:MX 记录表示把一个主机指定作为给定主机的“邮件交换器”(mail exchanger)。上例中的主机 freebsd 有 2 个 MX 记录:第一个的优先级值为 5,第二个的优先级为 10。当存在多个 MX 记录时,按照优先级顺序使用,值越小优先级越高。
4、PTR:“指针记录”(pointer record)PTR 会把 IP 地址映射成主机名。在做 PTR 查询时,会先把 IPv4/IPv6 地址反转顺序,每个字节都转换成各自的十进制/十六进制,然后在末尾添加上 in-addr.arpa/ip6.arpa。比如上例中 freebsd 的两个 PTR 记录分别是 254.32.106.12.in-addr.arpa 和 b.6.8.6.7.a.e.f.f.f.0.2.0.0.a.0.1.0.0.0.d.8.f.1.0.8.b.0.e.f.f.3.ip6.arpa。
5、CNAME:CNAME 代表“规范名字”(canonical name)。它经常为常用的服务(如 ftp 和 www)指派 CNAME 记录,这样用户就只需要使用这些服务名而无需知道真实的主机名。比如下面是一个名为 linux 的主机的 2 个 CNAME 记录:
ftp IN CNAME linux.unpbook.com.
www IN CNAME linux.unpbook.com.
很多组织机构往往运行有一个或多个名字服务器(name server),即通常所谓的 BIND(Berkeley Internet Name Domain)程序。而大部分的网络应用程序都是通过调用称为解析器(resolver)的函数库中的函数(如下文的 gethostbyname 等)来接触 DNS 解析器。下图展示了应用进程、解析器和名字服务器之间的一个典型关系。
应用程序使用通常的函数调用来执行解析器中的代码,解析器代码通过读取其系统相关配置文件来确定本组织机构的各个名字服务器的位置(通常使用文件 /etc/resolv.conf 包含本地名字服务器主机的 IP 地址)。解析器先向本地名字服务器发出 UDP 请求进行查询,如果它也不知道,通常也会使用 UDP 向其他名字服务器查询。如果答案太长,超出了 UDP 消息的承载能力,本地名字服务器和解析器就会自动切换到 TCP。
除了使用 DNS 获取名字和地址信息,常用的替代方法还有静态主机文件(通常是 /etc/hosts 文件)、网络信息系统(Network Information System,NIS)以及轻权目录访问协议(Lightweight Directory Access Protocol, LDAP)等。对于开发人员来说,调用诸如 gethostbyname 和 gethostbyaddr 这样的解析器函数即可。
利用主机名查找 IP 最基本的函数是 gethostbyname。它会在调用成功时返回一个指向 hostent 结构的指针,其中就包含了给定主机的所有 IPv4 地址。只能返回 IPv4 地址正是该函数的局限所在,使用后面要介绍的 getaddrinfo 函数则能同时处理 IPv4 和 IPv6 地址,因而为便于兼容,推荐使用后者。而 gethostbyaddr 函数的行为与 gethostbyname 刚好相反,它是试图通过一个二进制的 IP 地址找到相应的主机名。
#include <netdb.h> struct hostent *gethostbyname(const char *hostname); struct hostent *gethostbyaddr(const char *addr, socklen_t len, int family); /* 两个函数的返回值:若成功则为非空指针;否则为 NULL 且设置 h_errno */ struct hostent{ char *h_name; // official (canonical) name of host char **h_aliases; // pointer to array of pointers to alias names int h_addrtype; // host address type: AF_INET int h_length; // length of address: 4 char **h_addr_list; // ptr to array of ptrs with IPv4 addrs };
由此可见,按照 DNS 的说法,gethostbyname 执行的是对 A 记录的查询,因为它返回的是 IPv4 地址,而 gethostbyaddr 则是在 in_addr.arpa 域中向一个名字服务器查询 PTR 记录。
另外要注意的是,这两个函数在发生错误时与其他很多套接字函数不同,它们不设置 errno 变量,而是将全局整数变量 h_errno 设置为在头文件 <netdb.h> 中定义的下列常值之一:
* HOST_NOT_FOUND;
* TRY_AGAIN;
* NO_RECOVERY;
* NO_DATA(等同于 NO_ADDRESS)。
这里的 NO_DATA 表示指定的名字有效,但是没有 A 记录。只有 MX 记录的主机名就是这样的一个例子。现在多数解析器都提供了一个名为 hstrerror 的函数,它接收一个 h_errno 值作为参数,返回对应的错误说明。
此外还需注意,gethostbyaddr 函数的 addr 参数实际上并不是“char *”类型,而是一个指向存放了 IPv4 地址的某个 in_addr 结构的指针,len 参数就是该结构的大小(对于 IPv4 地址为 4)。
下面这个简单例子演示了 gethostbyname 函数的用法。
#include <stdio.h> #include <stdlib.h> #include <netdb.h> #include <arpa/inet.h> #include <netinet/in.h> 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){ printf("gethostbyname error for host: %s: %s\n", 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: for(pptr=hptr->h_addr_list; *pptr!=NULL; pptr++) printf("\taddress: %s\n", inet_ntop(AF_INET, *pptr, str, sizeof(str))); break; default: printf("Error: unknown address type!"); } } exit(0); }
类似于主机,服务也可以通过名字来认知。通常使用文件 /etc/services 来保存服务名字到端口号的映射关系。函数 getservbyname 可根据给定的名字和可选的协议参数来查找相应的服务,getservbyport 函数则是根据给定的端口号和可选的协议参数来查找相应的服务。
#include <netdb.h> struct servent *getservbyname(const char *servname, const char *protoname); struct servent *getservbyport(int port, const char *protoname); /* 两个函数的返回值:若成功则为非空指针;否则为 NULL */ struct servent{ char *s_name; // official service name char **s_aliases; // alias list int s_port; // port number, network byte order char *s_proto; // protocal to use };
注意,如果指定了 protoname 参数,那么对应的服务必须要有匹配的协议。由于有些网络服务既用 TCP 也用 UDP(如 DNS),有些服务又仅仅支持单个协议(如 FTP 服务就只使用 TCP),所以如果 protoname 未指定而 servname 服务支持多个协议,则返回哪个端口号取决于实现。尽管多数情况下支持多个协议的服务往往使用相同的端口号,但也不能保证一定如此。此外,有些端口号在不同的协议下所代表的服务是不一样的,比如 512~514 范围内的端口。
下面这个程序演示了 gethostbyname 和 getservbyname 函数的用法,它接收 2 个命令行参数:主机名和服务名。它会尝试连接到多宿服务器主机的每个 IP 地址,直到有一个连接成功或所有地址尝试完毕为止。
#include <stdio.h> #include <stdlib.h> #include <netdb.h> #include <string.h> #include <strings.h> #include <arpa/inet.h> #include <netinet/in.h> #include <sys/socket.h> typedef struct sockaddr SA; #define MAXLINE 1024 int main(int argc, char *argv[]){ if(argc != 3){ printf("Usage: %s <hostname> <service>\n", argv[0]); exit(2); } struct hostent *p_hEnt = gethostbyname(argv[1]); struct in_addr **pptr = NULL; struct in_addr *inetaddrp[2]; struct in_addr inetaddr; if(p_hEnt == NULL){ if(inet_aton(argv[1], &inetaddr) == 0){ printf("hostname error: %s, %s\n", argv[1], hstrerror(h_errno)); exit(2); } inetaddrp[0] = &inetaddr; inetaddrp[1] = NULL; pptr = inetaddrp; }else{ pptr = (struct in_addr **)p_hEnt->h_addr_list; } struct servent *p_sEnt = getservbyname(argv[2], "tcp"); if(p_sEnt == NULL){ printf("wrong servename: %s\n", argv[2]); exit(2); } struct sockaddr_in servaddr; bzero(&servaddr, sizeof(servaddr)); servaddr.sin_family = AF_INET; servaddr.sin_port = p_sEnt->s_port; int sockfd; for(; *pptr != NULL; pptr++){ printf("trying %s\n", inet_ntoa(**pptr)); //servaddr.sin_addr = **pptr; // 直接利用结构体赋值 memcpy(&servaddr.sin_addr, *pptr, sizeof(struct in_addr)); sockfd = socket(AF_INET, SOCK_STREAM, 0); if(connect(sockfd, (SA *)&servaddr, sizeof(servaddr)) == 0) break; // success close(sockfd); // connect 失败的套接字不能再继续使用 printf("\t...connect error!\n"); } if(*pptr == NULL) printf("cannot connect to %s\n", argv[1]); char line[MAXLINE+1]; int n; while((n=read(sockfd, line, MAXLINE)) > 0){ line[n] = 0; // null terminate fputs(line, stdout); } exit(0); }
发表评论
-
Unix 域套接字与描述符的传递
2019-03-27 23:59 709在Unix 域套接字概述一节中介绍了什么是 U ... -
Unix 域套接字概述
2019-03-12 22:48 975Unix 域协议并不是一个实际的协议族,而是在 ... -
kqueue 接口
2019-03-06 00:47 683kqueue 接口是 ... -
辅助数据
2019-02-28 00:40 684辅助数据(a ... -
recv/send 和 recvmsg/sendmsg 函数
2019-01-22 00:40 1535recv 和 send ... -
inetd 守护进程介绍
2019-01-09 21:51 1044在 4.3 BSD 系统之前,很多网络服务都是 ... -
主机名与 IP 地址的转换(续)
2018-12-25 00:37 953在主机名与 IP 地址的转换一节中提到的 ge ... -
SCTP 事件通知
2018-02-08 03:49 973SCTP 提供了多种可用的通知,用户可经由这些通知追踪 ... -
SCTP 套接字选项
2018-02-04 09:35 1682在获取和设置套接 ... -
SCTP 套接字编程基础函数
2018-02-04 10:08 1048SCTP 服务器可以使 ... -
SCTP 套接字编程基础概念
2018-01-18 00:10 558SCTP 套接字分为一到一套接字和一到多套接字。提供一 ... -
UDP套接字编程基础
2018-01-14 10:37 570下图显示了使用 UDP 套接字编写客户/服务器程序时的 ... -
通用套接字选项
2018-01-02 00:46 544在获取和设置套接 ... -
获取和设置套接字选项
2017-12-29 08:21 434下面几种方法可用 ... -
I/O 复用之 poll 函数
2017-12-27 00:20 421poll 函数提供的功能与 select 类似,不过在 ... -
I/O 复用之select 函数
2017-12-12 00:32 514select 函数允许进 ... -
Unix 5 种 IO 模型概述
2017-11-19 01:44 304Unix 下有 5 种可用 ... -
套接字创建、连接和关闭函数
2017-08-13 17:16 661下图是一对 TCP 客户与服务器进程之间发生的一些典型 ... -
字节转换和填充函数
2017-08-06 01:09 426网络编程中,为保证发送协议栈和接收协议栈就如 32 位 ... -
IPv4 和 IPv6 的套接字地址结构
2017-08-01 21:03 735大多数套接字函数 ...
相关推荐
在编程领域,网络通信是不可或缺的一部分,而主机名与IP地址之间的转换是网络通信中的基础操作。易语言作为一款中国本土开发的、面向初学者的编程语言,也提供了相应的功能来实现这一转换。本篇文章将深入讲解如何...
// 使用gethostbyaddr将IP转换为主机名 struct in_addr addr; inet_aton("192.168.1.1", &addr); host_entry = gethostbyaddr((const void *)&addr, sizeof(addr), AF_INET); if (host_entry == NULL) { ...
10. **项目结构**:一个简单的IP地址与主机名转换程序可能包括一个主函数,调用`Dns.GetHostEntry`或`Dns.Resolve`方法,然后显示或存储转换结果。用户界面可能是命令行形式或者图形用户界面(GUI),如Windows ...
这段代码首先获取主机名,然后使用`getaddrinfo`函数查找与之相关的IP地址。`getaddrinfo`返回一个链表,包含了不同类型的IP地址。遍历这个链表,使用`inet_ntop`函数将二进制的IP地址转换为人类可读的形式。 需要...
`AddressList`属性是一个`IPAddress`对象数组,包含了与主机名关联的所有IP地址,包括IPv4和IPv6。 在实际应用中,我们可能只需要IPv4或IPv6地址,可以进行筛选: ```csharp IPAddress ipv4Address = null; ...
1. **DNS解析**:DNS(Domain Name System)是将主机名转换为IP地址的服务。在程序中,我们可以使用DNS查询来获取IP地址。在Python中,可以使用`socket.gethostbyname()`函数实现,如`ip_address = socket....
获取本地主机名和IP地址的MFC实现 在MFC应用程序中,获取本地主机名和IP地址是一项基本的网络编程任务。下面,我们将介绍如何使用MFC框架来实现这个功能。 首先,我们需要在StdAfx.h文件中包含afxsock.h头文件,...
3. **进行DNS解析**:对于每一行IP,使用编程语言提供的DNS库或函数(如Python的`socket.gethostbyaddr()`)进行反向解析,将IP转换为主机名。这个函数会返回一个包含主机名、域名和IP地址列表的元组,通常我们只...
在IT行业中,网络通信是不可或缺的一部分,而网络通信的基础之一就是IP地址与主机名的相互转换。本篇文章将深入探讨如何在Delphi 2009环境下编写一个小程序,实现通过IP地址获取主机名的功能。 首先,让我们了解IP...
在Windows系统中,如果你需要通过编程方式获取本机的主机名和IP地址,可以使用Winsock库中的函数。Winsock是Windows Socket接口的简称,它为应用程序提供了标准的网络通信功能。下面我们将详细讨论如何利用Winsock库...
如果需要将主机名转换为IP地址,可以使用`gethostbyname`函数。这个函数接收主机名作为参数,返回一个`hostent`结构体,其中包含了IP地址等信息。结构体中的`h_addr_list`成员包含了IP地址列表。注意,对于IPv4,...
在IT领域,网络通信是不可或缺的一部分,而主机名与IP地址之间的转换是网络通信中的基本操作。本篇文章将深入探讨如何在Unix/Linux环境下,利用C语言通过IP地址获取主机名的相关知识点。 首先,我们需要理解IP地址...
在这个主题中,“MFC获取主机名,IP地址,MAC地址”指的是如何在MFC应用中获取计算机的相关网络信息。 首先,获取主机名是通过Windows API函数`GetComputerName`来完成的。这个函数允许程序获取当前系统的计算机名...
在IT领域,获取本机主机名和IP地址是常见的网络编程任务,这涉及到操作系统与网络接口的交互。本文将详细讲解这两个概念以及如何在不同环境下实现获取。 首先,主机名是一个标识符,用于唯一地识别网络上的计算机。...
标题 "转换IP地址为主机名的控件" 描述了这样一个功能:将数字形式的IP地址转换成人类可读的主机名。在计算机网络中,IP地址是标识网络上设备的唯一数字序列,而主机名则是一种便于记忆的字符串形式。这个控件可能是...
- Delphi提供了一系列的网络编程组件和函数,如 Indy (Internet Direct) 库,TIdDNSResolver 等,它们可以帮助开发者处理IP与主机名的相互转换。 - Indy 是一个强大的开源网络库,包含多种协议的支持,如TCP/IP、...
在本文中,我们将深入探讨这些知识点,包括网络基础、TCP/IP协议、主机名与IP地址的关系,以及如何通过编程来获取这些信息。 首先,我们要理解主机名和IP地址的基本概念。主机名是人类可读的标识符,例如...
在DNS(Domain Name System)系统中,IP地址和主机名称可以相互转换。 在C#中,我们可以使用`System.Net.Dns`命名空间下的类和方法来实现这个功能。具体来说,`Dns`类提供了一个静态方法`GetHostEntry`,它可以接受...