一、引言
在浏览器中输入一个地址,点击回车之后发生了什么?这是一个面试中常见的问题 ,这个看似常见简单的操作,其中却隐藏了大量复杂的互联网技术。本篇博客,我们就聊一聊网上冲浪的第一步:DNS解析。
DNS解析是一种服务,其又被称为域名解析。它的作用是将域名解析到具体的网络IP地址,以便进行后续的网络连接操作。DNS解析说起来也简单,从表面上看,就是通过一个查询服务,将域名映射成IP地址,可以往深处推敲,你就会发现其实并没有那么简单,世界上有无数的终端接入互联网,DNS服务是如何从浩如烟海的数据中找到目标数据的,DNS服务是如何保证搜索性能的等等都是值得讨论的话题。
二、分层的网络
在深入了解DNS解析之前,首先需要对我们当下使用的网络系统有大致的了解。关于网络系统的分层,流行的有OSI的7层模型与TCP/IP的五层模型,其中关系如下图所示:
以OSI的7层网络模型为例,其中每一层都负责对应的协议,两台终端在进行交互时,同层之间进行交互。我们从上到下来看:
应用层:顾名思义应用层的主要作用是搭建应用,其负责实现应用层面的协议,例如文件传输FTP协议,还有我们最常用的HTTP协议以及邮箱相关协议等。
表示层:表示层用来对应用层的数据进行封装处理,如压缩与解压。
会话层:会话层位于传输层之上,其用来管理一对会话,即会话的连接开始,同步,中断等等。
传输层:传输层述责数据的分段传输与接收重组,这一层有TCP、UDP等传输协议。
网络层:网路层负责根据IP来将数据传递到指定的目的主机,其会确定传输路由等问题。
数据链路层:将数据进行MAC地址的封装与解封等。
物理层:定义物理媒介的协议,以二进制的方式传输数据,定义数据的传输速率等参数。
当数据真正的通过7层网络模型传输之前,首要确定的是数据要传输到哪里,我们知道通过IP地址确定数据要到达的目的终端,然而IP地址是有一串数字(字母)与点符号构成的,可读性很差且难于记忆,因此采用别名的方式来代替直接使用IP进行地址确定,这个别名就是域名,将域名解析为IP的过程就是域名解析。
三、DNS服务器
既然需要将域名解析成为IP地址,则就需要有一个服务器提供这样的解析功能,这个服务器需要维护这一张域名与IP地址映射的表,在客户提出解析需求时,从表中查询出域名对应的IP地址返回给客户,如下图所示:
然而在实际应用中,上图中的设计结构明显是不切实际的,由一台服务器来维护所有的域名与IP映射关系明显是不现实的。首先,世界上的域名和IP数量非常庞大,并且更新也非常频繁,维护成本很多。另一方面,大量的客户同时进行域名解析请求,也会使解析的效率和速度出现瓶颈。现实中的DNS解析是采用层层递进,多级缓存,递归查询的结构组织而成的,下图很好的描述了这一过程:
从图中可以看到首先当客户端发起DNS解析时,会从本机NDS缓存中进行查找,同样也会查找本机的Hosts文件中是否有指定对应的解析规则,由于本机的Hosts文件具有最高的优先级,因此我们想在本机将某个域名强制指向一个固定的IP,则可以采用修改Hosts文件的方式,在Mac系统中,此Hosts文件在etc文件夹下。
当本机缓存中没有解析出此域名的信息且Hosts文件中也没有指定时,会想本地DNS服务器发起查询,本地DNS服务器也会维护一张缓存表用来提高查询效率,如果本地DNS服务器没有查到,会向根DNS服务器发起请求,根DNS服务器会采用递归迭代的方式进行搜索,全球有13台根DNS服务器,根DNS服务器会根据域名后缀返回对应的顶级域名服务器,顶级域名服务器会再次根据域名分类将指定的主DNS地址返回,如此迭代直到解析到对应的IP地址再一步步返回给客户机。
四、域名服务器类型
根域名服务器
根域名服务器是域名解析系统中最高级别的域名服务器,其复杂返回顶级域名服务器,他们是互联网的基础。目前,全球有13个根域名服务器地址(并非实际的服务器数量),可以在如下网站查找到这些根域名服务器的信息。
顶级域名服务器
顶级域名服务器用于某个顶级域名下的DNS解析,例如有专门负责.com后缀的顶级域名服务器,负责.edu后缀的顶级域名服务器等,顶级域名服务器将查询到的主域名服务器返回。
主域名服务器
主域名服务器负责某个区域的域名解析。同样,主域名服务器会配套辅助域名服务器进行备份与分担负载。
五、手动进行DNS解析
一次完整的HTTP请求首先要做的就是DNS解析(如果是通过域名进行请求)。平时在开发中,我们很少关注是因为发起HTTP的网络请求层帮我们封装好了这一部分逻辑。有时候,为了提高请求的效率或防止DNS劫持,我们也可以自己进行DNS解析。
以iOS中的编程为例,可以直接使用CoreFoundation框架中的接口进行NDS解析:
Boolean result;
CFHostRef hostRef;
CFArrayRef addresses = NULL;
CFArrayRef names = NULL;
NSMutableArray * ipsArr = [[NSMutableArray alloc] init];
CFStringRef hostNameRef = CFStringCreateWithCString(kCFAllocatorDefault, "www.baidu.com", kCFStringEncodingASCII);
hostRef = CFHostCreateWithName(kCFAllocatorDefault, hostNameRef);
CFAbsoluteTime start = CFAbsoluteTimeGetCurrent();
result = CFHostStartInfoResolution(hostRef, kCFHostAddresses, NULL);
if (result == true) {
addresses = CFHostGetAddressing(hostRef, &result);
names = CFHostGetNames(hostRef, &result);
}
if(result)
{
struct sockaddr_in* remoteAddr;
for(int i = 0; i < CFArrayGetCount(addresses); i++)
{
CFDataRef saData = (CFDataRef)CFArrayGetValueAtIndex(addresses, i);
remoteAddr = (struct sockaddr_in*)CFDataGetBytePtr(saData);
if(remoteAddr != NULL)
{
//获取IP地址
char ip[16];
strcpy(ip, inet_ntoa(remoteAddr->sin_addr));
NSString * ipStr = [NSString stringWithCString:ip encoding:NSUTF8StringEncoding];
[ipsArr addObject:ipStr];
}
}
}
CFAbsoluteTime end = CFAbsoluteTimeGetCurrent();
NSLog(@"name:%@", names);
NSLog(@"IP:%@ \n time cost: %0.3fs", ipsArr,end - start);
CFRelease(hostNameRef);
CFRelease(hostRef);
运行上面代码,即可将指定域名的IP地址解析出来,其中CFHostCreateWithName方法根据域名创建一个主机引用对象,CFHostStartInfoResolution方法用来进行主机信息的解析,如果解析成功,CFHostGetAddressing方法用来获取具体的IP地址数据。