DNS(Domain Name System)即域名解析系统,这个东西说对于开发者来说,应该是没有不知道的。说简单点,这个系统的作用就是将域名解析成IP地址。我们的每一次网络请求,如果是使用域名,那么就是进行域名解析。
一个优秀的域名服务应该能够满足两点要求,一个是能够正确的返回IP地址,二就是能够根据网络情况返回所请求的域名最近的服务器IP。
一个DNS查询,会先从本地缓存查找,如果没有或者已经过期,就从DNS服务器查询,如果客户端没有主动设置DNS服务器,一般是从服务商DNS服务器上查找。这就出现了不可控。因为如果使用了IPS的LocalDNS域名服务器,那么基本都会或多或少地无法避免在有中国特色的互联网环境中遭遇到各种域名被缓存、用户跨网访问缓慢等问题。
我们先来看看普通域名服务会有什么问题:
一些小服务商以及小地方的服务商非常喜欢干这个事情。根据腾讯给出的数据,DNS劫持率7%,恶意劫持率2%。网速给的劫持率是10-15%。
智能DNS,就是为了调度用户访问策略,但是这些因素会导致智能DNS策略失效。
目前国内大多数企业对于域名解析这块问题没有进行特殊处理,这导致了上述说的那些问题,其中域名劫持的问题相当普遍。那么有没有一种方法能够避免上述的情况呢?有,当然有。那就是使用HttpDNS。
HttpDNS其实也是对DNS解析的另一种实现方式,只是将域名解析的协议由DNS协议换成了Http协议,并不复杂。使用HTTP协议向D+服务器的80端口进行请求,代替传统的DNS协议向DNS服务器的53端口进行请求,绕开了运营商的Local DNS,从而避免了使用运营商Local DNS造成的劫持和跨网问题。
接入HttpDNS也是很简单的,使用普通DNS时,客户端发送网络请求时,就直接发送出去了,有底层网络框架进行域名解析。当接入HttpDNS时,就需要自己发送域名解析的HTTP请求,当客户端拿到域名对应的IP之后,就向直接往此IP发送业务协议请求。
这样,就再也不用再考虑传统DNS解析会带来的那些问题了,因为是使用HTTP协议,所以不用担心域名劫持问题了;而且,如果选择好的DNS服务器提供商,还保证将用户引导的访问速度最快的IDC节点上。
既然HttpDNS这么好,那么咱们就开始接入吧。不过先慢着,在接入时还需要考虑一个问题:HttpDNS服务器用哪家的呢?
目前,比较出名的HttpDNS服务提供商有两家(腾讯和阿里):
因为第一家有免费版本可供使用,所以我们就使用 DNSPOD 来演示如何接入HttpDNS了。
既然已经选择了 DNSPOD ,那么我们进入其网站的接入页面,能够看到其收费信息和接入指南。一般来说一个服务提供商应该会想所有客户端提供响应的SDK以方便使用,不过这个 DNSPOD 没有,他只提供了一个C语言版本,如果是企业用户,他会给提供一个定制的SDK以供使用,对于免费用户,如果不用这个C语言版本,还有一个选择,那就是开源的第三方SDK:
上面的SDK大同小异用哪个都差不多,此处我们就拿七牛的安卓版本来演示接入。
现在我们已经做好了接入前的大量准备,那么从现在开始就可以进入代码部分了,这也是最重点的部分了。
首先,为了方便演示,我们使用 Android Studio 新建立了一个空的工程;然后,我们需要引入两个库,一个是Okhttp,另一个就是七牛的D+开源SDK了:
compile ‘com.qiniu:happy-dns:0.2.13’
compile ‘com.squareup.okhttp3:okhttp:3.9.0’
第一个是七牛的库,这个库在github上没有说明自己的gradle依赖,但是可以查到,这个库的名字起的还是挺欢乐的。
第二个库就是大名鼎鼎的OkHttp了,至于为什么会用这个库,有两方面原因:一是该库使用人数巨大;二是使用其设置DNS解析服务是相当方便。
在构造OkHttpCient
时,我们可以通过方法OkHttpClient.Builder.dns(httpDns)
来设置该对象使用的DNS解析服务,这样我们就可以自定义HttpDNS域名解析了。
下面需要按照OkHttp的要求自定义DNS解析了,具体的就是需要实现Dns
接口。下面的代码是我封装的一个Dns
实现类:
public class HttpDns implements Dns {
private DnsManager dnsManager;
public HttpDns() {
IResolver[] resolvers = new IResolver[1];
try {
resolvers[0] = new Resolver(getByName("119.29.29.29"));
dnsManager = new DnsManager(NetworkInfo.normal, resolvers);
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
@Override
public List lookup(String hostname) throws UnknownHostException {
if (dnsManager == null) //当构造失败时使用默认解析方式
return Dns.SYSTEM.lookup(hostname);
try {
String[] ips = dnsManager.query(hostname); //获取HttpDNS解析结果
if (ips == null || ips.length == 0) {
return Dns.SYSTEM.lookup(hostname);
}
List result = new ArrayList<>();
for (String ip : ips) { //将ip地址数组转换成所需要的对象列表
result.addAll(Arrays.asList(getAllByName(ip)));
}
return result;
} catch (IOException e) {
e.printStackTrace();
}
//当有异常发生时,使用默认解析
return Dns.SYSTEM.lookup(hostname);
}
}
有了上面的Dns
实现类,下面就是要构造出一个使用这个域名解析方式的OkHttpClient
对象:
OkHttpClient okHttpClient = new OkHttpClient.Builder().dns(new HttpDns()).build();
那么接下来我们使用这个okHttpClient
进行网络请求时,使用的域名解析就会从默认的域名解析转换为HttpDNS域名解析了。是不是很容易,这也是OkHttp的优势之一:便于定制。
这样就完成了接入HttpDNS。
得力于OkHttp的优势和七牛提供的库,我并没有感觉很麻烦。之前我看到有的博客上写的使用拦截器的方式接入HttpDNS,虽然也能用,但是我个人感觉并不是很好。所以拦截器方式的代码就不贴出来了。OkHttp的拦截器很强大,能做很多事,处理DNS当然也不在话下,不过拦截器不是做这个事情的,更何况OkHttp提供了对于DNS的设置。所以还是建议使用这种方式接入。