第八章域名及地址名
现在可能无人不知DNS,多多少少都听过,这到底是是个啥东西呢,与我们之前学的有什么关系呢?
8.1 域名系统
DNS是对IP地址和域名进行相互转换的系统,其核心是DNS服务器。
8.1.1 域名是啥
提供网络服务的服务器端也有自己的IP地址,但是IP地址很难记住,而类似www.xxx.com的域名却是很容易记住的,
我们在客户端需要与服务器端进行连接,通常用域名取代IP地址。
8.1.2 DNS服务器
在浏览器中输入Naver网站的IP地址222.122.195.5就能浏览这个网站。如果用www.naver.com访问网站,也能够得到相同的效果。
从结果上来看,这两种方式是没有区别的,但是接入的过程不同。
域名是赋给服务器的虚拟地址,并非真实,因此需要将虚拟地址转化为实际地址。如何把域名变为IP地址呢?
这就需要DNS服务器了,本质上说DNS服务器是一个分布式数据库,里面存放着各种IP地址与域名对应的关系。
其过程是这样的:
计算机中记录着默认DNS服务器的地址,通过这个more你的DNS服务器得到相应域名的IP地址信息。在浏览器中输入域名后,浏览区通过默认DNS服务器获得该域名对应的IP地址信息,之后再接入网站。
注意:IP服务器的IP地址会相对频繁的改变,而域名一般不会轻易改变,因此域名是非常重要的,这类似于一个标识。
使用ping
命令可以得到某个域名的IP地址信息,在控制台中运行即可
ping www.baidu.com
使用 nslookup
可以得到自己计算机中注册的 默认DNS服务器地址
默认DNS服务器是啥?
计算机内置的默认DNS服务器并不知道网络上所有域名的IP地址信息,它只知道部分,一丢丢的信息。
如果这个服务器无法解析域名(没有你输入域名的IP地址信息),它会询问其他DNS服务器,并提供给用户。过程如下图所示。
上图是默认DNS服务器无法解析主机询问的域名IP地址时 的应答过程。
它会向上级DNS服务器询问,通过这种方式逐级向上传递信息,到达顶级DNS服务器——DNS根服务器。 这时它至少该向哪个DNS服务器询问,向下级DNS传递解析请求,得到IP地址后原路返回,最后将解析的IP地址传递到发起请求的主机,DNS就是这样层次化管理的一种分布式数据库系统。
8.2 IP地址和域名之间的转换
8.2.1 转换的必要性
假设我们现在需要运营一个www.xxx.com域名的公司,需要开发客户端使用公司提供的服务,这个客户端需要接入如下的服务器地址:IP:211.102.112.11 PORT:9999
首先,我们不能让用户每次使用都自己输入服务器的地址来连接吧~所以我们要在客户端里保存一个能够连接到服务器的地址信息。
由于IP地址会经常变更,相对而言域名要稳定许多,所以使用域名来编写程序是更优的选择。这样每次运行程序时,根据域名获取IP地址,再接入服务器,这样就不会依赖于IP地址的固定性了
因此我们需要一个能够在程序中将 域名 转换为 IP地址 的转换函数。
8.2.2 利用域名获得IP地址
下面函数可以通过传递字符串格式的域名获得IP地址。
#include <netdb.h>
struct hostent* gethostbyname(const char* hostname);
->成功时返回hostent结构体地址,失败时返回NULL指针。
使用时只需要传入一个字符串格式的域名,就能得到域名对应的IP地址
但是在返回时,地址信息转入了hostent结构体中。下面介绍一下这个结构体。
struct hostent
{
char* h_name; //official name
char** h_aliases; //alias list
int h_haddrtype; // host address type
int h_length; // address length
char** h_addr_list; //address list
}
h_name: 变量中存放官方域名(Official domain name)。官方域名代表某一个主页,实际上一些公司的域名并未用官方域名注册。
haliases: 通过多个域名可以访问同一个主页,同一个IP也可以绑定多个域名。除了官方域名之外的其他域名存储在这个变量中。
h_addrtype: gethostbyname函数不尽之处IPV4还支持IPV6.可以通过此变量获得保存h_addr_list的**IP地址的地址族**信息。如果是IPV4 则为 AF_INET
h_length: 保存IP地址的长度,如果是IPV4地址,4个字节。IPV6 为16个字节。
h_addr_list:最重要的成员。以整数形式保存域名对应的IP地址。 另外,用户多的网站可能分配多个IP给同一个域名,利用多服务器进行负载均衡。同样可以通过此变量获取IP地址信息。
调用gethostbyname函数后返回的hostent结构体的变量结构如下图所示!
下面用一个示例演示gethostbyname函数的使用,并说明hostent结构体变量的特性。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
void error_handling(char* message);
int main(int argc,char* argv[])
{
int i;
struct hostent* host;
if(argc!= 2){
printf("Usage:%s <addr>\n",argv[0]);
exit(1);
}
host = gethostbyname(argv[1]);
if(!host){
error_handling("gethost...error");
}
// 官方名
printf("Officail name: %s\n",host->name);
// 别名
for(int i = 0;host->h_aliases[i]){
printf("第%d个别名(aliase)为: %s\n",i+1,host->h_aliases[i]);
}
// 地址类型
printf("Address type:%s\n",(host->h_addrtype == AF_INET)?"AF_INET":"AF_INET6");
// IP地址列表
for (int i = 0; host->h_addr_list[i]; ++i)
{
/* code */
printf("IP addr %d:%s\n",i+1,inet_addr(*(struct in_addr*)host->h_addr_list[i]));
}
return 0;
}
void error_handling(char* message)
{
fputs(message,stderr);
fputc('\n',stderr);
exit(1);
}
先不看运行结果,大家心里都有数,先看一下程序中打印IP地址列表的部分。
printf(“IP addr %d:%s\n”,i+1,inet_addr((struct in_addr)host->h_addr_list[i]));
这里的类型转换是怎么回事?
在hostent类型的结构体中,其中一个元素是 char** h_addr_list;
h_addr_list[i] 的类型是 char* 为什么要强转成 in_addr*类型的指针呢?
因为这里char* 指针(这个字符指针数组)指向了(实际保存的是)in_addr结构体变量地址值而非字符串。
其结构如下图所示:
这里为什么要用char*?
因为通用性呀,如果用 in_addr* 只能在IPV4地址使用了。IPV6没法用了。
为什么不用void*?
这样当然更好,但是这个东西标准化出来的时候,规定的使用char* ,要是现在可能就用void* 了
这里是测试结果:
8.2.3 利用IP地址获得域名
#include <netdb.h>
struct hostent* gethostbyaddr(const char* addr,socklen_t len, int family);
-> 成功时返回hostent结构体变量地址值,失败时返回NULL指针。
addr: 含有IP地址的 in_addr 结构体指针。为了同时传递IPv4地址之外的其他信息,声明为char*
len: 向第一个参数传递的地址信息的字节数。 IPv4为4,IPv6为16
family:传递地址族信息,IPv4为AF_INET,IPv6为AF_INET6
注意一下:下面的代码中,我们是通过IP get 域名和其他信息,输入参数是const char* addr
,其指向in_addr 结构体变量,因此我们要初始化 要传入对应的 sin_addr的地址,同时要进行类型转换。
#include <netdb.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
void error_handling(char* message);
int main(int argc,char* argv[])
{
// int i;
struct hostent* host;
struct sockaddr_in addr;
if(argc!= 2){
printf("Usage:%s <IP>\n",argv[0]);
exit(1);
}
// 因为是IP 得到域名,输入参数中是结构体变量,因此先初始化变量。
memset(&addr,0,sizeof(addr));
addr.sin_addr.s_addr = inet_addr(argv[1]);
host = gethostbyaddr((char*)&addr.sin_addr,4,AF_INET);
if(!host){
error_handling("gethost...error");
}
// 官方名
printf("Officail name: %s\n",host->h_name);
// 别名
for(int i = 0;host->h_aliases[i];i++){
printf("第%d个别名(aliase)为: %s\n",i+1,host->h_aliases[i]);
}
// 地址类型
printf("Address type:%s\n",(host->h_addrtype == AF_INET)?"AF_INET":"AF_INET6");
// IP地址列表
for (int i = 0; host->h_addr_list[i]; ++i)
{
/* code */
printf("IP addr %d:%s\n",i+1,inet_ntoa(*(struct in_addr*)host->h_addr_list[i]));
}
return 0;
}
void error_handling(char* message)
{
fputs(message,stderr);
fputc('\n',stderr);
exit(1);
}
下面是测试结果: