Chrome是如何实现DNS的解析

DNS解析是一种非常常见的解析方式,来看一下百度百科的说明:

DNS(Domain Name System,域名系统),因特网上作为域名和IP地址相互映射的一个分布式数据库,能够使用户更方便的访问互联网,而不用去记住能够被机器直接读取的IP数串。通过主机名,最终得到该主机名对应的IP地址的过程叫做域名解析(或主机名解析)。DNS协议运行在UDP协议之上,使用端口号53。在RFC文档中RFC 2181对DNS有规范说明,RFC 2136对DNS的动态更新进行说明,RFC 2308对DNS查询的反向缓存进行说明。

简单点说就是我们想登陆某一个网页,只需要记住有主观意义的域名,比如说www.bytedance.com, www.baidu.com, www.sina.cn 等等,甚至可以简单地把dns理解为一个key-value的分布式数据库,其中key值就是域名,而value就是域名对应的ip地址,这里并不是一一对应的关系,一个域名可以对应多个ip地址,这样做是为了防止当一个ip地址不可用时,可以切换到另一个ip地址,从而不会影响用户的业务。

关于dns的原理我们就简单介绍到这里,今天我们要讲的是chromium是如何实现dns解析的。大家可能对chromiun有点陌生,但我相信很多人一定都用过chrome浏览器,实际上chromiun就是chrome的项目源码名称,可不要小瞧chrome浏览器,其复杂程度不亚于一个操作系统,甚至你可以直接把chrome浏览器看成一个web操作系统,关于这个项目有很多值得学习的地方,比如说它的多进程调度,渲染器的使用,浏览器加速算法等等,在这里我们只关注它对于网络部分的实现.

Chromium中关于网络方面的实现均在net库中,实现了包括dns、http、udp、ftp、quic等等大家熟知的协议,大家感兴趣的话可以去网上下载chromium的源代码。

dns的代码路径是 src/net/dns, 通过下图可以看到文件有很多个,如果一个个点进去看,既浪费时间又很难真正理解dns的精髓。

这里写图片描述

下面我就带大家看一看chrome对于dns解析的实现:

第一步首先关注核心函数,这里的核心函数是指dns整个模块对外的一个接口,简单点说就是当用户在浏览器中输入网址时,主进程便会调用dns模块中的这个文件来获取相应的ip地址,而这里的核心函数就位于 host_resolver_impl.cc 中,其核心函数为:
int HostResolverImpl::Resolve(const RequestInfo& info,
RequestPriority priority,
AddressList* addresses,
const CompletionCallback& callback,
RequestHandle* out_req,
const BoundNetLog& source_net_log) ;

Key key = GetEffectiveKeyForRequest(info, source_net_log);
这里的key值时根据info得出的一组可以唯一标志dns中一种主机解析job,关于job的概念等会就会提到。

int rv = ResolveHelper(key, info, addresses, source_net_log);
可以看到在这里又新引入了一个ResolveHelper函数,这个函数的作用时根据计算得到的key值以及info值,依次判断是否为ip,能否从cache中得到,能否从host中得到,这个函数可以理解为一个辅助的解析函数。

JobMap::iterator jobit = jobs_.find(key);
Job* job;

加入辅助解析函数并未找到需要的ip地址,并且之前未启动相同的job(这里通过find函数来查找指定key值的job是否存在),则需要启动一个job来进行相应的dns解析,关于dns解析,这里有两种方式:

一种是要提到一个函数getaddrinfo(), 这个函数可以根据域名得到相应的ip地址,并且既可以用在ipv4上,也可以用在ipv6上,但由于其是一个系统级别的调用,因此是一个阻塞调用,chrome将这种方式称之为ProcTask方式:

而另外一种调用方式则是chromium按照dns协议规范,通过异步非阻塞的调用方式,实现了getaddrinfo()函数,这种方式被称之为DnsTask。

那究竟如何选择使用哪种方式来进行dns解析呢?还是要看代码,下面这些代码时job类中start()方法的实现:
void Start() override {
DCHECK_LE(num_occupied_job_slots_, 1u);

// Caution: Job::Start must not complete synchronously.
if (!system_only && had_dns_config_ &&
!ResemblesMulticastDNSName(key_.hostname)) {
StartDnsTask(); //满足上述条件后才使用DnsTask
} else {
StartProcTask(); //其余情况均使用ProcTask
}
}
当满足系统未指定解析方式、dns已经配置完成主机名满足一定条件才可以使用DnsTask,其余情况均使用ProcTask。

先来说一下ProcTask,这种方式较为简单,其最终回调用到函数SystemHostResolverCall() 中去, 而这个函数只是对 getaddrinfo() 做了一层封装而已,逻辑相对简单一些,感兴趣的同学可以去看一下源码,位置是在host_resolver.cc这个文件里。

DnsTask相对来说就复杂一些,简单点说就是通过调用StartA(),紧接着再调用
dns_query.cc、dns_transaction.cc,同时调用udp协议获取fd,然后将这个fd注册到网络库的io线程事件循环中,从而使得dns解析流程统一写到了异步回调的编程框架中。

好了,关于chromium中dns的解析暂时先说到这里,后续如果有更进一步的解析,我会及时更新并分享出来的。

你可能感兴趣的:(chrome浏览器)