因特網無疑是人類有史以來最偉大的設計,它互聯了全球數億臺計算機、通信設備,即使位於地球兩端的用戶也可在頃刻間完成通信。java
能夠說『協議』是支撐這麼一個龐大而複雜的系統有條不紊運做的核心,而所謂『協議』就是通信雙方所必須遵照的規則,在這種規則下,不一樣的數據報可能被解析爲不一樣的響應動做。git
簡而言之,『協議』就是指若是發送和接收方按照這個規則進行數據報文的發送,便可在基本的數據傳輸之上獲得某些特殊的功能或服務,不然你的數據別人是不認識的。例如:遵循 TCP 協議的兩端,能夠在不可靠的網絡傳輸中獲得可靠的數據傳輸能力。github
整個計算機網絡是分層的,有七層模型,也有五層模型,我的以爲五層模型更利於理解。咱們從上至下的介紹這五個層,它們分別是,應用層,運輸層,網絡層,數據鏈路層和物理層。算法
『應用層』算是距離用戶最近的一層了,主機上的一個個的進程就構成了『應用層』。好比你在你的瀏覽器地址欄輸入了 「www.baidu.com」,你的瀏覽器在應用層會作哪些事情呢?瀏覽器
首先瀏覽器會使用 DNS 協議返回域名「www.baidu.com」所對應的 IP 地址,關於 DNS 咱們待會詳細介紹。緩存
接着,應用層決定建立一個『TCP 套接字』,而後將這個請求動做封裝成一個 Http 數據報並推入套接字中。服務器
套接字分爲兩種類型,『TCP 套接字』和『UDP 套接字』,應用層同時可能會有幾十個數據報的發出,而運輸層也會收到全部的響應報文,那麼它該如何區分這些報文究竟是誰的響應報文呢?微信
而套接字就是用於區分各個應用層應用的,每每由端口號和 IP 地址進行標識,運輸層只要查看響應報文的源端口號和 IP 地址就可以知道該將報文推送給哪一個套接字了。網絡
當一個應用層數據報被推進進套接字以後,應用層的全部工做也算是所有完成了,關於後續報文的去向,它已經不用管了。併發
這裏還要說明一點的是,『TCP 套接字』和『UDP 套接字』二者本質上的區別在於,前者保證數據報可靠地到達目的地,可是必然耗時,然後者不保證數據報必定能到達目的地,可是速度快,這也是應用層協議在選擇運輸層協議的時候須要考慮的一點。
關於 TCP 和 UDP,咱們後續還會繼續說,下面咱們看看域名解析協議 DNS 是如何運做的,它是如何將一個域名解析返回它的 IP 地址的。
DNS 原理
首先明確一點的是,DNS 是一個應用層協議,而且它選擇的運輸層協議是 UDP,因此你的域名解析過程通常會很快,但也會常常出現解析失敗的狀況,然而刷新一下又好了。
在 DNS 服務器上,域名和它所對應的 IP 地址存儲爲一條記錄,而全部的記錄都不可能只存儲在一臺服務器上,我相信不管多麼強大的服務器都扛不住全球上億次的併發量吧。
大體來講,有三種類型的 DNS 服務器,根 DNS 服務器,頂級域 DNS 服務器和權威 DNS 服務器。
其中,頂級域 DNS 服務器主要負責諸如 com、org、net、edu、gov 等頂級域名。
根 DNS 服務器存儲了全部頂級域 DNS 服務器的 IP 地址,也就是說你能夠經過根服務器找到頂級域服務器。例如:「www.baidu.com」,根服務器會返回全部維護 com 這個頂級域服務器的 IP 地址。
而後你任意選擇其中一個頂級域服務器,請求該頂級域服務器,該頂級域服務器拿到域名後應當可以作出判斷並給出負責當前域的權威服務器地址,以百度爲例的話,頂級域服務器將返回全部負責 baidu 這個域的權威服務器地址。
因而你能夠任意選擇其中一個權威服務器地址,向它繼續查詢 「www.baidu.com」 的具體 IP 地址,最終權威服務器會返回給你具體的 IP 地址。
至此,咱們簡單描述了一個域名解析的大體過程,還有一些細節之處並未說起,咱們等會會經過一個實例來完整的看一下,下面描述一個很是重要的概念。
整個 DNS 解析過程當中,有一個很是核心的人物咱們一直沒介紹它,它就像主機的『助理』同樣,幫助主機查詢域名的 IP 地址。它叫作『本地 DNS 服務器』。
你們每次經過 DHCP 動態獲取 IP 地址的時候,這一點後文會說。其實路由器不只給你返回了 IP 地址,還會告訴你一個 DNS 服務器地址,這個就是你的本地 DNS 服務器地址,也就是說,你的全部域名解析請求只要告訴它就好了,它會幫你查並返回結果給你的。
除此以外,本地 DNS 服務器每每是具備緩存功能的,一般兩天內的記錄都會被緩存,因此大部分時候你是感受不到域名解析過程的,由於每每就是從緩存裏拿的,很是快。
下面咱們看一個簡單的案例:
網上找的一個圖,本身畫實在太費時間了,但足以說明問題,如今假設請求 「www.xx.com」 。
其實整個 DNS 報文的發送與響應過程都是要走咱們的五層協議的,只是這裏重點在於理解 DNS 協議自己,因此並未說起其餘層的具體細節,這裏的強調是提醒你 DNS 只是一個應用層協議。
運輸層的任務就是將應用層推出套接字的全部數據報收集起來,而且按照應用層指定的運輸層協議,TCP 或 UDP,從新封裝應用層數據報,並推給網絡層等待發送。
TCP 和 UDP 是運輸層的兩個協議,前者是基於鏈接的可靠傳輸協議,後者是無鏈接的不可靠傳輸協議,因此前者更適合於一些對數據完整性要求高的場合,後者則適合於那種能夠容許數據丟失但對傳輸速率要求特別高的場景,例如:語音電話,視頻等,丟一兩個包最多卡頓一下,無傷大雅。
UDP
UDP 不一樣於 TCP 那樣複雜,它既不保證數據可靠的傳輸到目的地,也不保證數據按序到達目的地,僅僅提供了簡單的差錯檢驗。報文格式以下:
其中,數據就是應用層推出來的數據,源端口號用於響應報文的交付,目的端口號用於向目的進程交付數據,校驗和用於檢查傳輸過程當中數據是否受損,若是受損,UDP 將直接丟棄該報文。
TCP
TCP 要稍微複雜些,它是面向鏈接的,而且基於鏈接提供了可靠的數據傳輸服務,它的數據報文格式以下:
單純的解釋報文格式中各個字段的含義並無太過實際的意義,你也很難理解了,在咱們介紹 TCP 是如何『三次握手』,『四次揮手』以及『丟包重傳』等動做時,不間斷的會說明這些動做時如何使用報文中的相關字段的。
首先咱們來看耳熟能詳的『三次握手』,這基本上是 TCP 的代名詞了,不管懂不懂具體原理的人,提到 TCP,基本上都是知道『三次握手』的。
而自己,TCP 的三次握手就是爲了確保通信雙方可以穩定的創建鏈接並完成數據報文的請求與響應動做,至於爲何是三次握手而不是四次五次,這是一個哲學問題,這裏就不作討論了。
第一步:
客戶端向服務端發送一份特殊的 TCP 報文,該報文並不包含應用層的數據,是一份特殊的報文,它的 TCP 首部中 SYN 字段值爲 1 (參見上述報文格式)。
除此以外,客戶端還會隨機生成一個初始序號,填在報文的「序號」字段,表明當前報文的序號是這個,而且我後續的分組會基於這個序號遞增。
而後該報文將會經網絡層、鏈路層、物理層發送到服務端。
第二步:
若是分組丟失了,那麼客戶端會通過某個時間間隔再次嘗試發送。
而若是分組準確的到達服務端了,服務端拆開 TCP 首部會看到,這是一個特殊的 SYN 握手報文,因而爲這次鏈接分配緩存等資源。
接着服務端開始構建響應報文,SYN 是一個用於同步須要的字段,響應報文中依然會被置爲 1,而且服務端也將隨機生成一個初始序號放置的響應報文的序號字段中。
最後,服務端還會爲響應報文中的確認字段賦值,這個值就是客戶端發過來的那個序號值加一。
總體上的意思就是說,「我贊成你的鏈接請求,個人初始序號爲 xxx,你的初始序號我收到了,我等着你的下一個分組到來」
第三步:
客戶端收到服務端的響應報文,因而分配客戶端 TCP 鏈接所必須的緩存等資源,因而鏈接已經創建。
實際上從第三步開始,客戶端就能夠攜帶應用層數據向服務端交換報文了,之後的每份報文中,SYN 都爲 0,由於它只是用於同步初始序號的,這一點須要明確。
總的來講,整個『握手』過程大體以下圖所示:
下面咱們看看拆除一條 TCP 鏈接的『四次揮手』是怎樣的過程。
由於一條 TCP 鏈接會消耗大量的主機資源,不只僅服務端須要分配各類緩存資源,客戶端也一樣須要分配相應資源。由於 TCP 是『全雙工通訊』,服務端和客戶端兩方實際上是同樣的,誰是客戶誰是服務器是相對的。
強調這一點是爲了說明,一條 TCP 鏈接不是隻有客戶端才能斷開,服務端也一樣能夠主動斷開鏈接,這一點須要清楚。
咱們這裏假設客戶端主動發起斷開鏈接的請求爲例:
第一步:
客戶端構建一份特殊的 TCP 報文,該報文首部字段 FIN 被置爲 1,而後發送該報文。
第二步:
服務端收到該特殊的 FIN 報文,因而響應客戶端一個 ACK 報文,告訴客戶端,請求關閉的報文已經收到,我正在處理。
第三步:
服務端發送一個 FIN 報文,告訴客戶端,我將要關閉鏈接了。
第四步:
客戶端返回一個 ACK 響應報文,告訴服務端,我收到你剛纔發的報文了,我已經確認,你能夠關閉鏈接了。
當服務端收到客戶端發送的 ACK 響應報文時,將釋放服務端用於該 TCP 鏈接的全部資源,與此同時,客戶端也會定時等待必定時間後徹底釋放本身用於該鏈接的相關資源。
用一張圖更直觀的描述一下:
結合着圖與相關序號信息,咱們再詳細說說其中的一些細節。
首先,客戶端發送一個特殊分組,該分組的序號爲 u。發送完成以後,客戶端進入 FIN-WAIT-1 這個狀態,這個狀態下,該 TCP 鏈接的客戶端再也不能發送數據報,可是是能夠接受數據報的,它等待着服務端的響應報文。
接着,服務端收到客戶端發送的終止鏈接報文請求,服務端構建響應報文,告訴客戶端「序號 u+1 之前的分組我都收到了」,而且進入 CLOSE-WAIT 狀態,這個狀態持續時間很短。
服務端會緊接着發送它的 FIN 數據報,通知客戶端我服務端即將關閉鏈接,並隨即進入 LAST_ACK 狀態等待客戶端響應報文。
一旦客戶端收到這個 FIN 報文,將返回確認報文並進入 TIME-WAIT 狀態,等待 2MSL 時間間隔後徹底釋放客戶端 TCP 鏈接所佔用資源。
與此同時,當服務端收到客戶端最後的確認報文,就將直接斷開服務端鏈接並釋放相關資源。
至於爲何最後客戶端須要等 2MSL 時間長度再徹底釋放 TCP 相關資源呢?
那是由於 2MSL 是一份報文存在於網絡中最長的時間,超過該時間到達的報文都將被丟棄,而若是客戶端最後的確認報文於網絡中丟失的話,服務端必將發起超時請求,從新發送第三次揮手動做,此時等待中的客戶端就可隨即從新發送一份確認請求。
這是爲何客戶端等待一個最長報文傳輸時間的緣由。有人可能好奇爲何前面的各次請求都沒有作超時等待而只最後一次數據發送作了超時等待?
其實緣由很簡單,相信你也能想到,就是 TCP 自帶計時能力,超過必定時間沒有收到某個報文的確認報文,會自動從新發送,而這裏若是不作等待而直接關閉鏈接,那麼我如何知道服務端到底收到沒個人確認報文呢。
經過等待一個最長週期,若是這個週期內沒有收到服務端的報文請求,那麼咱們的確認報文必然是到達了服務端了的,不然重複發送一次便可。
至此,TCP 的『三次握手』和『四次揮手』咱們已經簡單描述完成了,下面咱們看看 TCP 的一些其餘特性,好比:可靠傳輸,擁塞控制等
首先咱們來看 TCP 是如何實現可靠傳輸的,即如何解決網絡傳輸中丟包的問題。
TCP 使用『回退 N 步』協議實現的可靠傳輸,準確來講,TCP 是在它的基礎上進行了一部分優化。
『回退 N 步』協議也被稱做『滑動窗口』協議,即最多容許發送方有 N 個「已發送但未被確認」的數據報文,如圖所示,p1 到 p3 長度即爲 N,這裏的窗口指的就是 p1 到 p3 這個區間。
只有當發送端收到 p1 的確認報文後,整個窗口才能向前滑動,而實際上在沒有收到 p1 的確認報文前,即使它後面的報文已經被接收,服務端也僅僅會緩存這些『非預期的報文』
直到服務端收到最小預期的那個報文後,從緩存中取出已經到達的後續報文,合併並向上交付,而後向發送端返回一個確認報文。
當發送端窗口從左往右已經連續多個報文被確認後,整個窗口將向前滑動多個單位長度。
下面咱們看一個例子:
這是一個發送方的窗口,灰色表示已經被確認的報文,黃色表示已發送但未被確認的報文,綠色表示下一個待發送的報文,白色表示不可用的報文。
這是咱們假設服務端已經收到 六、7 兩份報文,可是它上一次向上交付給應用層的是 4 號報文,也就是說它在等 5 號報文,因此它暫時會將 六、7 兩個報文緩存起來,等到 5 號報文來了一併交付給應用層。
如今 5 號報文因爲超時被重傳了,終於到達目的地了,如願以償,服務端向上交付 五、六、7 三份報文,並返回一份確認報文,ACK = 8,表示序號 8 之前的全部報文都收到了。
當發送端收到這份確認報文後,五、六、7 變成灰色,窗口向前移動三個單位長度。
此外,我還想強調一個細節,TCP 是沒有否認確認的,因此若是服務端連續響應的多份報文是對同一序號的確認,那頗有可能該序號之後的某個報文丟失。
例如:若是服務端發送多個對分組 5 的 ACK 確認,那說明什麼?說明目前我服務端完整的向上交付的序號是 5 號,後續的報文我沒收到,你最好從新發一下別等待超時了。
這也是『快速重傳』的核心原理。
那麼 TCP 的可靠傳輸咱們也基本介紹完了,下面咱們看看若是網絡擁塞的時候,TCP 是如何控制發送流量的呢?
TCP 認爲:丟包即擁塞,須要下降發送效率,而每一次收到確認數據報即認爲網絡通暢,會增長髮送效率。
TCP 的擁塞控制算法包含三個部分,慢啓動、擁塞避免和快速恢復。
慢啓動的思想是,剛開始緩慢的發送,好比某個時間段內只發送一次數據報,當收到確認報文後,下一次一樣的時間間隔內,將發送兩倍速率的兩份數據報,並以此類推。
因此,短期內,一個 TCP 鏈接的發送方將以指數級增加,但一旦出現丟包,即收到冗餘的 ACK 確認,或者對於一個包的確認 ACK 始終沒收到而不得不啓動一次超時重傳,那麼發送方認爲「網絡是擁塞的」。
因而將速率直接調成一,即一個往返時間段,只發送一個分組,而且設置一個變量 ssthresh 表述一個閾值的概念,這個值是上次丟包時發送方發送速率的一半。
以後的發送方的發送效率同樣會以指數級增加,可是不一樣於第一次,此次一旦達到這個閾值,TCP 將進入『擁塞避免』模式,該模式下的發送效率將再也不指數級增加,會謹慎的增加。
擁塞避免的思想是,每一個往返時間段發送的全部數據報所有獲得確認後,下一次就增長一個分組的發送,這樣緩慢的增加效率是謹慎的。
那麼一旦出現發送端超時丟包,注意這裏是超時,將發送速率置爲一併從新進入慢啓動狀態,閾值就是當前發送效率的一半。
而若是是服務端返回多個冗餘 ACK 以明確你丟包,TCP 認爲這不是嚴重的,對於這種狀況,TCP 減半當前發送效率並進入快速恢復階段。
快速恢復的基本思想是,收到幾個冗餘的 ACK 就增長几個分組的發送效率,就是說,你服務端不是沒收到個人幾個報文嗎,這兩次發送我提高速率迅速發給你。
當這期間出現了由發送端超時致使的丟包,一樣的處理方式,初始化發送速率爲一併減半當前發送效率做爲閾值,進入慢啓動階段。
固然,若是這期間收到了對丟失報文的確認,那麼將適當下降發送效率並進入擁塞避免狀態。
這樣,整個 TCP 最核心的幾個思想都已經介紹完了,整個運輸層基本上也算明瞭了。關於運輸層,你應當有了必定的理解,我再總結一下。
運輸層的任務就是從應用層的各個進程的套接字那取回來全部須要發送的數據,而後選擇 TCP 或者 UDP 將數據封裝並推給下面的網絡層待發送。
未完,待續。。。
文章中的全部代碼、圖片、文件都雲存儲在個人 GitHub 上:
(https://github.com/SingleYam/overview_java)
歡迎關注微信公衆號:撲在代碼上的高爾基,全部文章都將同步在公衆號上。