您好,登錄后才能下訂單哦!
這篇文章主要介紹web協議中DNS和WebSocket有什么用,文中介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們一定要看完!
我們首先通過 Linux 下的dig命令來了解一下 DNS 是怎么做域名解析的。我們首先輸入命令:
dig www.baidu.com
看下標注的紅框,從左到右依次代表:
域名的名稱 也就是服務器名稱
網絡類型,DNS 協議在設計的時候考慮到了其他網絡類型,但是目前位置這個值還是寫死的IN 你就理解成是互聯網就可以了。這個值一般不變
標識域名對應何種類型的地址,A 就代表ip的地址。
這里可能有人會問了,這個域名的后面為啥還有個“.”?我們輸入的明明是 www.baidu.com 不是 www.baidu.com. 啊 。
這里要提一下:
末尾的. 代表的就是根域名,每個域名都有根域名,所以通常我們會省略它。
根域名的下一級叫頂級域名,比如我們熟知的.com與.net。
再下一級就是次級域名了,比如例子中的.baidu。這個次級域名只要你有錢是可以隨便注冊的。
最后這個www,這個代表三級域名。一般是用戶在自己的域里面為服務器分配的名稱。用戶可以隨便分他。
所以可以看出來這里的域名是分級別的。能弄明白這點就能搞清楚為什么DNS的查詢過程是分級查詢了。
我們可以利用dig+trace命令來完整的還原一次分級查詢的過程:
你看通過命令的方式就能一目了然的理解DNS查詢的過程了。這遠比你在網上搜一些什么DNS是遞歸查詢啊之類的要來的直觀。這里有眼尖的小伙伴可能會問,這個CNAME是用來干嘛的?大家只要理解CNAME主要用來做CDN加速的即可。詳細的可以去維基百科查詢,那里說的很清楚,本文受限于篇幅就不在這個知識點上展開了。
這里注意因為 Wireshark的捕獲過濾器無法設置DNS協議,又因為DNS是基于 UDP協議的,所以這里捕獲過濾器我們就設置為 UDP就好。
然后就可以在一堆 UDP報文中找到我們想看的DNS報文了,我們在瀏覽器中輸入www.airbnb.com:
這里要注意左邊有兩個箭頭,向右的箭頭代表“請求”,向左的箭頭就代表該“請求”的回復了。
這些DNS報文經過 Wireshark的解析以后,格式已經幫我們分析好了,所以看起來很清晰。也很簡單。這里我們不再詳細分析DNS的二進制報文格式,有興趣的可以自行查找相關資料。在我們上述展示的DNS報文抓包截圖的時候,細心的同學已經發現了,我們DNS報文的查詢地址是172.22.3.102。一般而言,大部分公司內部網絡都會提供一個統一的DNS服務器,這個地址就是內部的DNS服務器地址了,有圖為證:
我們當然也可以使用其他DNS查詢,比如使用著名的谷歌DNS
經過上述的分析看起來DNS的查詢過程好像比較簡單,但實際上DNS帶來的性能或者安全問題很多很多。我們首先來還原一下完整的DNS查詢過程(假設我們想訪問csdn的網站):
瀏覽器輸入一個域名地址,如果操作系統的DNS緩存中有這個域名的Ip地址 那么直接返回,沒有的話 就去第二步。
操作系統會向 本系統設置的tcp/ip 參數中的DNS服務器地址 發出DNS查詢報文。注意這個服務器我們通常叫他 本地DNS服務器。也就是上述我們截圖中的172.22.3.102
如果本地DNS服務器的緩存中有這個域名對應的ip地址,那就直接返回,如果沒有,繼續下一步。
首先看DNS服務器的架構圖:
也就是說當我們的本地DNS服務器緩存中沒有該域名的ip地址的時候,本地DNS服務器就會直接向 根DNS(全世界只有13臺)服務器去查詢,然后根DNS服務器就會分析這個域名,告訴我們的本地DNS服務器 你應該去.net 這個DNS服務器去查詢。然后.net這個DNS服務器又告訴本地DNS服務器 你應該去csdn.net 這個DNS服務器 去查詢DNS地址。然后最終csdn的 DNS服務器就將正確的ip地址返回給我們的本地DNS,本地DNS再將這個值返回給我們的瀏覽器(這個過程其實你用前面的dig+trace命令可以更直觀的體會到)。
通過上述的一次完整的DNS交互過程,我們可以至少得出三個結論:
DNS服務器是可以做負載均衡的。當然前提條件是你這個域名得自己建一個DNS服務器。一般大廠都會自建。
DNS的查詢是一個遞歸的過程,弱網的情況,這個時間會變的很漫長。且DNS使用的是 UDP傳輸協議,弱網有直接查詢失敗的可能。
DNS的查詢過程不可控,比如說本地DNS服務器完全可以返回一個錯誤的ip地址。比如你訪問了一個京東的鏈接,然后返回給你的ip地址是拼多多的。
這還只是表面上看出來的傳統DNS查詢的缺點,實際上現在我們每天大部分的流量都來自于移動網絡。移動網絡中,傳統DNS服務暴露出來的問題更多:
前文我們說過本地DNS服務器會緩存域名的ip,但這個緩存時效我們控制不了,全靠運營商的操守。有可能發生我們ip地址已經變化,但是本地DNS服務器返回的還是老ip的情況。
有些運營商為了節省運營商和運營商之間的流量計算成本,會偷偷的將一些靜態頁面緩存。當用戶訪問這些頁面的時候 往往訪問的是這些靜態頁面的緩存服務器的地址。此時不管我們的頁面更新了多少內容,對于用戶來說都是老的頁面。
運營商在某些場景,例如人口集中的地鐵站,演唱會,足球場附近等等,一旦發現自己的用戶太多,本地DNS服務器壓力巨大的時候,就會手動設置將本地DNS服務器向根域名服務器
查詢 然后遞歸查詢 DNS的過程 修改成:直接向另外一個運營商(假設這個運營商名字為B)的DNS服務器地址進行查詢,B的DNS服務器就會返回一個B的地址,此時運營商A的用戶訪問的ip地址就是運營商B的ip了,這種跨運營商訪問的場景速度會非常慢。
某些寬帶提供方的NAT服務非常不穩定,大家都知道我們在家上網的時候 本機地址其實就是一個內網地址,我們之所以能訪問外部的網絡是因為這些寬帶提供方提供了一層網關來負責NAT,這個NAT會將我們的內網地址轉換成一個外網的地址,NAT之后的ip,某些權威DNS服務器就無法判斷這個ip到底屬于哪個運營商。也會帶來跨運營商訪問的問題。
除了自己的DNS服務器,其他公共DNS服務器的緩存時效都不可控,這對雙機房部署,異地多活,多域名等策略都會有影響。
弱網環境下,因為DNS使用的傳輸協議是不可靠的 UDP,又因為DNS查詢的過程是一個遞歸的過程,所以DNS查詢在弱網環境下是有概率失敗的。
基于上述缺點,越來越多的大廠使用了HTTPDNS的這種技術(據騰訊的公開資料顯示,15年騰訊每天的localDNS失敗次數就達到了80w次,接入HTTPDNS以后,用戶平均訪問延遲下降超過10%,訪問失敗率下降了超過五分之一,用戶訪問體驗的效果提升非常顯著):
這種技術的原理其實挺簡單的,無非就是讓我們的手機App 發起一個HTTP請求(這個請求地址多數使用ip直連,如果使用域名那么依然針對此請求依然有傳統DNS的問題),這個請求可以攜帶用戶所在的運營商,地理位置,精確到省市,然后服務器根據這些信息 返回一個最佳的ip地址給App,然后App將這個域名-ip的映射關系設置到我們的okhttp中。這樣手機中的大部分請求就會直接使用我們HTTP服務器返回的ip地址而不是運營商的地址了。
注意這里我說的是大部分請求而不是全部請求的原因是,對于Android系統來說,webview的DNS查詢過程代碼全部在c層,且版本和版本之間有一定差異,這部分的hook過程極為艱難,截止到這篇文章編寫的時候,筆者依舊沒有查詢到公開的能夠hook webview DNS的源碼,而iOS這點做的顯然就比Android好一些,對于iOS來說webview的HTTP就是一個正常的HTTP request 與原生的代碼并沒有任何區別。對于Android客戶端來說,接入HTTPDNS也不是一件特別容易的事。即使現在擁有了okhttp。
方案一:
通過okhttp的攔截器,在發出請求之前將我們的url中的域名直接替換成ip,再手動往header中添加host頭部信息。缺點:如果url是https的,ip直連會出現證書校驗的問題。另外因為請求的時候 我們直接用的ip 但是 服務端返回的set cookie頭部信息卻帶上的域名,這里也要額外處理。優點:因為是攔截器的實現機制,所以很容易做開關進行降級處理。
方案二:
通過okhttp的DNS直接接管。
public class HttpDNS implements DNS { private static final DNS SYSTEM = DNS.SYSTEM; @Override public List<InetAddress> lookup(String hostname) throws UnknownHostException { //假設這個DNShelper可以返回我們httpDNS查詢的結果 String ip = DNSHelper.getIpByHost(hostname); if (ip != null && !ip.equals("")) { List<InetAddress> inetAddresses = Arrays.asList(InetAddress.getAllByName(ip)); return inetAddresses; } return SYSTEM.lookup(hostname); } } //然后讓okhttp使用我們的DNS實現就好 OkHttpClient client = new OkHttpClient.Builder() .DNS(new HttpDNS()) .build();
這種方案就不存在攔截器哪種缺點,因為本質上這種方案和系統的DNS查詢方案并無二致,無非系統的是 UDP去localDNS找,我們的是用HTTP去 HTTP服務器上找。這種方案可以解決方案一的所有缺點,但是有一個問題就是一旦這個HTTPDNS返回的結果有問題,那么很難降級。且okhttp的DNS查詢也是有一層緩存的,一旦我們的HTTP DNS服務器返回的地址有誤,那么在一定時間范圍內后續針對這個域名的訪問都會有問題。
前面我們說過Android自身webview的機制導致HTTPDNS很難在webview中起到作用,但是仍舊有一些方法可以盡量規避掉webview中loacalDNS速度慢的缺點。例如我們可以在html中設置預加載靜態資源的DNS請求,而不用等到真正請求這些資源的時候才會查找DNS。
<!--域名預解析--> <meta http-equiv="x-DNS-prefetch-control" content="on" > <link rel="DNS-prefetch" href="//vivo.com.cn" >
考慮到實際上webview和App自身代碼使用的DNS緩存都是操作系統中的同一塊存儲區域,我們也可以統計出我們常用web頁面中頻繁請求的url的域名,在App一啟動的時機,就提前訪問這些域名,這樣等到熱點web頁面在加載的時候,如果操作系統DNS緩存已經有了對應的ip,則可以省略一次DNS的查詢。
其實DNS協議真的不是完全基于UDP協議的,DNS的協議里面其實有主DNS服務器和輔DNS服務器的概念,輔DNS服務器在啟動的時候會主動去主DNS服務器上拉取最新的該區域DNS信息。這個拉取的過程采用的就是TCP協議,而不是UDP協議。也就是協議文檔中說的zone transfer。
這里有人可能會想到,為什么不用UDP協議來完成這個過程,因為UDP協議最大只能傳送512個byte的數據,而輔DNS要拉取該區域的DNS信息很容易就超過這個最大報文數量的限制,所以這里采用的就是TCP協議來完成拉取數據的操作。
很多人不明白為什么一定要用 WebSocket,明明我輪詢HTTP請求一樣可以完成需求。這句話本身并不錯,可以用 WebSocket 的地方確實全部都可以用輪詢HTTP請求來替代。但是其背后的效率卻天差地別。
我們可以把 WebSocket 看成是 HTTP 協議為了支持長連接所打的一個大補丁,它和 HTTP 有一些共性,是為了解決 HTTP 本身無法解決的某些問題而做出的一個改良設計。在以前 HTTP 協議中所謂的 keep-alive 長連接是指在一次 TCP 連接中完成多個 HTTP 請求,但是對每個請求仍然要單獨發 header;所謂的輪詢是指從客戶端不斷主動的向服務器發 HTTP 請求查詢是否有新數據。這種模式有三個缺點:
除了真正的數據部分外,服務器和客戶端還要大量交換 HTTP header,信息交換效率很低。
因為HTTP是無狀態的,每次請求服務端都要通過客戶端傳遞來的參數來查詢這個請求到底是誰的,例如每次都要查詢一下這個userId下面有多少存款,買過幾部手機等等,對服務器的寶貴的計算資源是一種浪費。
輪詢的時間間隔不好設置,設置高了,用戶的界面響應不及時,設置的太低,又怕流量消耗大,服務器扛不住。
當然輪詢也有優點就是實現成本極低,幾乎不需要客戶端和服務端有額外的開發成本。WebSocket在首次使用的時候還是需要做一些基礎設施改造的(例如nginx相應的配置)。WebSocket的實現成本:雖然說現代服務器編程中默認都提供了WebSocket的實現,但是我們知道考慮到擴展性等因素,我們通常都不會直接和源服務器打交道,而是和代理服務器打交道。對WebSocket來說同樣如此,所以這里對于首次實現WebSocket的團隊是有一定技術成本。
上圖是一個簡單的服務器架構圖,客戶端發出去的請求經過一臺專門做負載均衡的代理服務器以后將這些請求逐一轉發到對應的源服務器上。而對于WebSocket來說 情況則變的稍微有點復雜:
相比純 HTTP 來說,WebSocket通常會增加一層專門的消息分發系統提高消息的處理效率。通常是Kafka或者是RabbitMQ。
首先 來看一下WebSocket的幀格式。我們首先設置一下 Wireshark的捕獲器:
可以看出來這里我們操作步驟一共是 connect,然后發消息,服務器返回我們發送的消息,最后我們主動斷開連接。
WebSocket是一個基于幀的協議,所以這里我們著重分析一下WebSocket的幀格式,每個幀頭部的 第4-第7個 bit位,這4個bit 代表的就是Opcode,比較重要的就是幾個值:
2:代表這是二進制幀,
1:代表這是一個文本幀,
8:代表關閉幀。
這里有人就要問了,既然WebSocket是能保證長連接(tcp)的,那么這條長連接是由誰發起的?看下圖:
此外我們還需要注意Sec-WebSocket-Accept,和Sec-WebSocket-Key 這2個頭部信息。
客戶端生成一個隨機數以后用base64加密以后放到Sec-WebSocket-Key頭部信息中,然后服務器接受到這個信息,用這個值與rfc中規定的一個魔法字符串:“258EAFA5-E914-47DA-95CA-C5AB0DC85B11”拼起來,然后再使用sha-1加密 再經過base64 以后計算出來的值 放到Sec-WebSocket-Accept頭部中返回給客戶端。
這么做的原因是帶來一些基礎的保障,前面我們說過WebSocket連接的建立是依托HTTP消息的,為了防止這個WebSocket連接的建立是調用者無心誤觸發或者其他異常情況,所以這里有一次額外的數據校驗的過程。
看完連接,我們再看一下斷開連接,與WebSocket的連接不同,WebSocket的斷開連接是有明確步驟的,需要先斷開WebSocket的連接,然后才是tcp的斷開連接。
圖中可以看出來這個心跳包大概是30s發送一次,而且并沒有使用rfc中約定好的0x9或者0xA的所謂ping pong的心跳幀,而是就用了最簡單的文本幀來表示。
上圖所示,左邊的就是WebSocket 服務端發起的心跳包,opcode的值還是text文本幀的意思,只不過文本的內容很特殊。右邊就是WebSocket客戶端回復的心跳包。
這里要注意的是 Wireshark抓包的時候,最右邊有一個masked的標識,這通常代表這一個WebSocket的幀是由客戶端發送給服務端的,這是一個掩碼的標識。在WebSocket協議中只要是客戶端發起的消息,都必須經過這個隨機的masking-key的掩碼計算之后才能傳輸。這是為了解決代理緩存污染的問題。
注意這里問題的核心是實現不當的代理服務器,所謂實現不當的代理服務器就是指沒有完整實現好WebSocket協議的代理服務器。而不是真正意義上惡意的代理服務器,惡意的代理服務器,用mask幀的技術是無法避免的。
所謂mask掩碼技術就是指瀏覽器在發送WebSocket幀的時候必須生成一個隨機的mask-key,在幀的二進制中將傳輸的內容與這個mask-key做異或操作。得出來的值才可以在網絡中傳輸。
當我們的服務器接收到這個WebSocket幀以后就可以用這個mask-key來反異或,從而就可以得出真正的內容了,這是最低成本實現檢測WebSocket幀是否遭到篡改的方案。例如:我們用WebSocket 傳輸一個 文本幀,內容為字符串vivo,vivo的ascii碼的16進制為:76 69 76 6f。而這個消息,這次瀏覽器生成的mask-key 為 23 68 c0 a3。
我們將這2個值進行異或操作:
可以得到值為55 01 b6 cc。然后看一下抓包的幀內容里面是不是這個值:
以上是“web協議中DNS和WebSocket有什么用”這篇文章的所有內容,感謝各位的閱讀!希望分享的內容對大家有幫助,更多相關知識,歡迎關注億速云行業資訊頻道!
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。