DNS(Domain Name System)即域名解析系統(tǒng),這個(gè)東西說對(duì)于開發(fā)者來說,應(yīng)該是沒有不知道的。說簡單點(diǎn),這個(gè)系統(tǒng)的作用就是將域名解析成IP地址。我們的每一次網(wǎng)絡(luò)請(qǐng)求,如果是使用域名,那么就是進(jìn)行域名解析。
一個(gè)優(yōu)秀的域名服務(wù)應(yīng)該能夠滿足兩點(diǎn)要求,一個(gè)是能夠正確的返回IP地址,二就是能夠根據(jù)網(wǎng)絡(luò)情況返回所請(qǐng)求的域名最近的服務(wù)器IP。

LocalDNS
一個(gè)DNS查詢,會(huì)先從本地緩存查找,如果沒有或者已經(jīng)過期,就從DNS服務(wù)器查詢,如果客戶端沒有主動(dòng)設(shè)置DNS服務(wù)器,一般是從服務(wù)商DNS服務(wù)器上查找。這就出現(xiàn)了不可控。因?yàn)槿绻褂昧薎PS的LocalDNS域名服務(wù)器,那么基本都會(huì)或多或少地?zé)o法避免在有中國特色的互聯(lián)網(wǎng)環(huán)境中遭遇到各種域名被緩存、用戶跨網(wǎng)訪問緩慢等問題。
我們先來看看普通域名服務(wù)會(huì)有什么問題:
1. 域名劫持:
一些小服務(wù)商以及小地方的服務(wù)商非常喜歡干這個(gè)事情。根據(jù)騰訊給出的數(shù)據(jù),DNS劫持率7%,惡意劫持率2%。網(wǎng)速給的劫持率是10-15%。
- 把你的域名解析到競(jìng)爭(zhēng)對(duì)手那里,然后哭死都不知道,為什么流量下降了。
- 在你的代碼當(dāng)中,插入廣告或者追蹤代碼。這就是為什么在淘寶或者百度搜索一下東西,很快就有人聯(lián)系你。
- 下載APK文件的時(shí)候,替換你的文件,下載一個(gè)其他應(yīng)用或者山寨應(yīng)用。
- 打開一個(gè)頁面,先跳轉(zhuǎn)到廣告聯(lián)盟,然后跳轉(zhuǎn)到這個(gè)頁面。無緣無故多花廣告錢,以及對(duì)運(yùn)營的誤導(dǎo)。
2.智能DNS策略失效
智能DNS,就是為了調(diào)度用戶訪問策略,但是這些因素會(huì)導(dǎo)致智能DNS策略失效。
- 小運(yùn)營商,沒有DNS服務(wù)器,直接調(diào)用別的服務(wù)商,導(dǎo)致服務(wù)商識(shí)別錯(cuò)誤,直接跨網(wǎng)傳輸,速度大大下降。
- 服務(wù)商多長NAT,實(shí)際IP,獲得不了,結(jié)果沒有就近訪問。
- 一些運(yùn)營商將IP設(shè)置到開卡地,即使漫游到其他地方,結(jié)果也是沒有就近訪問。
目前國內(nèi)大多數(shù)企業(yè)對(duì)于域名解析這塊問題沒有進(jìn)行特殊處理,這導(dǎo)致了上述說的那些問題,其中域名劫持的問題相當(dāng)普遍。那么有沒有一種方法能夠避免上述的情況呢?有,當(dāng)然有。那就是使用HttpDNS。
HttpDNS
HttpDNS其實(shí)也是對(duì)DNS解析的另一種實(shí)現(xiàn)方式,只是將域名解析的協(xié)議由DNS協(xié)議換成了Http協(xié)議,并不復(fù)雜。使用HTTP協(xié)議向D+服務(wù)器的80端口進(jìn)行請(qǐng)求,代替?zhèn)鹘y(tǒng)的DNS協(xié)議向DNS服務(wù)器的53端口進(jìn)行請(qǐng)求,繞開了運(yùn)營商的Local DNS,從而避免了使用運(yùn)營商Local DNS造成的劫持和跨網(wǎng)問題。
接入HttpDNS也是很簡單的,使用普通DNS時(shí),客戶端發(fā)送網(wǎng)絡(luò)請(qǐng)求時(shí),就直接發(fā)送出去了,有底層網(wǎng)絡(luò)框架進(jìn)行域名解析。當(dāng)接入HttpDNS時(shí),就需要自己發(fā)送域名解析的HTTP請(qǐng)求,當(dāng)客戶端拿到域名對(duì)應(yīng)的IP之后,就向直接往此IP發(fā)送業(yè)務(wù)協(xié)議請(qǐng)求。
這樣,就再也不用再考慮傳統(tǒng)DNS解析會(huì)帶來的那些問題了,因?yàn)槭鞘褂肏TTP協(xié)議,所以不用擔(dān)心域名劫持問題了;而且,如果選擇好的DNS服務(wù)器提供商,還保證將用戶引導(dǎo)的訪問速度最快的IDC節(jié)點(diǎn)上。
接入HttpDNS之前
既然HttpDNS這么好,那么咱們就開始接入吧。不過先慢著,在接入時(shí)還需要考慮一個(gè)問題:HttpDNS服務(wù)器用哪家的呢?
選擇服務(wù)商
目前,比較出名的HttpDNS服務(wù)提供商有兩家(騰訊和阿里):
因?yàn)榈谝患矣忻赓M(fèi)版本可供使用,所以我們就使用 DNSPOD 來演示如何接入HttpDNS了。
選擇接入SDK
既然已經(jīng)選擇了 DNSPOD ,那么我們進(jìn)入其網(wǎng)站的接入頁面,能夠看到其收費(fèi)信息和接入指南。一般來說一個(gè)服務(wù)提供商應(yīng)該會(huì)想所有客戶端提供響應(yīng)的SDK以方便使用,不過這個(gè) DNSPOD 沒有,他只提供了一個(gè)C語言版本,如果是企業(yè)用戶,他會(huì)給提供一個(gè)定制的SDK以供使用,對(duì)于免費(fèi)用戶,如果不用這個(gè)C語言版本,還有一個(gè)選擇,那就是開源的第三方SDK:
上面的SDK大同小異用哪個(gè)都差不多,此處我們就拿七牛的安卓版本來演示接入。
接入HttpDNS
現(xiàn)在我們已經(jīng)做好了接入前的大量準(zhǔn)備,那么從現(xiàn)在開始就可以進(jìn)入代碼部分了,這也是最重點(diǎn)的部分了。
首先,為了方便演示,我們使用 Android Studio 新建立了一個(gè)空的工程;然后,我們需要引入兩個(gè)庫,一個(gè)是Okhttp,另一個(gè)就是七牛的D+開源SDK了:
compile 'com.qiniu:happy-dns:0.2.13'
compile 'com.squareup.okhttp3:okhttp:3.9.0'
第一個(gè)是七牛的庫,這個(gè)庫在github上沒有說明自己的gradle依賴,但是可以查到,這個(gè)庫的名字起的還是挺歡樂的。
第二個(gè)庫就是大名鼎鼎的OkHttp了,至于為什么會(huì)用這個(gè)庫,有兩方面原因:一是該庫使用人數(shù)巨大;二是使用其設(shè)置DNS解析服務(wù)是相當(dāng)方便。
在構(gòu)造OkHttpCient時(shí),我們可以通過方法OkHttpClient.Builder.dns(httpDns)來設(shè)置該對(duì)象使用的DNS解析服務(wù),這樣我們就可以自定義HttpDNS域名解析了。
下面需要按照OkHttp的要求自定義DNS解析了,具體的就是需要實(shí)現(xiàn)Dns接口。下面的代碼是我封裝的一個(gè)Dns實(shí)現(xiàn)類:
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<InetAddress> lookup(String hostname) throws UnknownHostException {
if (dnsManager == null) //當(dāng)構(gòu)造失敗時(shí)使用默認(rèn)解析方式
return Dns.SYSTEM.lookup(hostname);
try {
String[] ips = dnsManager.query(hostname); //獲取HttpDNS解析結(jié)果
if (ips == null || ips.length == 0) {
return Dns.SYSTEM.lookup(hostname);
}
List<InetAddress> result = new ArrayList<>();
for (String ip : ips) { //將ip地址數(shù)組轉(zhuǎn)換成所需要的對(duì)象列表
result.addAll(Arrays.asList(getAllByName(ip)));
}
return result;
} catch (IOException e) {
e.printStackTrace();
}
//當(dāng)有異常發(fā)生時(shí),使用默認(rèn)解析
return Dns.SYSTEM.lookup(hostname);
}
}
有了上面的Dns實(shí)現(xiàn)類,下面就是要構(gòu)造出一個(gè)使用這個(gè)域名解析方式的OkHttpClient對(duì)象:
OkHttpClient okHttpClient = new OkHttpClient.Builder().dns(new HttpDns()).build();
那么接下來我們使用這個(gè)okHttpClient進(jìn)行網(wǎng)絡(luò)請(qǐng)求時(shí),使用的域名解析就會(huì)從默認(rèn)的域名解析轉(zhuǎn)換為HttpDNS域名解析了。是不是很容易,這也是OkHttp的優(yōu)勢(shì)之一:便于定制。
這樣就完成了接入HttpDNS。
得力于OkHttp的優(yōu)勢(shì)和七牛提供的庫,我并沒有感覺很麻煩。之前我看到有的博客上寫的使用攔截器的方式接入HttpDNS,雖然也能用,但是我個(gè)人感覺并不是很好。所以攔截器方式的代碼就不貼出來了。OkHttp的攔截器很強(qiáng)大,能做很多事,處理DNS當(dāng)然也不在話下,不過攔截器不是做這個(gè)事情的,更何況OkHttp提供了對(duì)于DNS的設(shè)置。所以還是建議使用這種方式接入。
