1、一個http請求的流程
在分布式架構中,有一個很重要的環節,就是分布式網絡中的計算機節點彼此之間需要通信。這個通信的過程一定會涉及到通信協議相關的知識點,
我們每天都在用瀏覽器訪問各種網站,作為用戶來說,只需要需要輸入一個網址并且正確跳轉就行。但是作為程序員,看到的可能就是這個響應背后的整體流程。所以我們通過一個 http請求的整個流程來進行說明遠程通信的原理
域名解析服務DNS
首先,用戶訪問一個域名,會經過 DNS 解析DNS(DomAIn Name System),它和 HTTP 協議一樣是位于應用層的協議,主要提供域名到IP 的解析服務。我們其實不用域名也可以訪問目標主機的服務,但是 IP 本身不是那么容易記,所以使用域名進行替換使得用戶更容易記住。
靜態內容分發CDN
在很多大型網站,會引入 CDN 來加速靜態內容的訪問,這里簡單給大家解釋一下什么是 CDN(Content Delivery.NETwork),表示的是內容分發網絡。CDN 其實就是一種網絡緩存技術,能夠把一些相對穩定的資源放到距離最終用戶較近的地方,一方面可以節省整個廣域網的帶寬消耗,另外一方面可以提升用戶的訪問速度,改進用戶體驗。我們一般會把靜態的文件(圖片、腳本、靜態頁面)放到 CDN 中。
http協議通信原理
域名被成功解析以后,客戶端和服務端之間,是怎么建立連接并且如何通信的呢?
說到通信,大家一定聽過 tcp 和 udp 這兩種通信協議,以及建立連接的握手過程。而 http 協議的通信是基于 tcp/ip 協議之上的一個應用層協議,應用層協議除了 http 還有哪些呢(FTP、DNS、SMTP、Telnet 等)。
涉及到網絡協議,我們一定需要知道 OSI 七層網絡模型和 TCP/IP 四層概念模型,OSI 七層網絡模型包含(應用層、表示層、會話層、傳輸層、網絡層、數據鏈路層、物理層)、TCP/IP 五層概念模型包含(應用層、傳輸層、網絡層、數據鏈路層、物理層)。
請求發起過程
當應用程序用 T C P 傳送數據時,數據被送入協議棧中,然后逐個通過每一層直到被當作一串bit流送入網絡。其中每一層對收到的數據都要增加一些首部信息(有時還要增加尾部信息)
客戶端如何找到目標服務
在客戶端發起請求的時候,我們會在數據鏈路層去組裝目標機器的 mac 地址,目標機器的mac 地址怎么得到呢?
這里就涉及到一個 ARP 協議,這個協議簡單來說就是已知目標機器的 ip,需要獲得目標機器的 mac 地址。(發送一個廣播消息,這個 ip 是誰的,請來認領。認領 ip 的機器會發送一個 mac 地址的響應,為了避免每次都用 ARP 請求,機器本地也會進行 ARP 緩存。當然機器會不斷地上線下線,IP 也可能會變,所以 ARP 的 MAC 地址緩存過一段時間就會過期。)
有了這個目標 MAC 地址,數據包在鏈路上廣播,MAC 的網卡才能發現,這個包是給它的。MAC 的網卡把包收進來,然后打開 IP 包,發現 IP 地址也是自己的,再打開 TCP 包,發現端口是自己,也就是 80 端口,而這個時候這臺機器上有一個 Nginx 是監聽 80 端口。于是將請求提交給 nginx,nginx 返回一個網頁。然后將網頁需要發回請求的機器。然后層層封裝,最后到 MAC 層。因為來的時候有源 MAC 地址,返回的時候,源 MAC 就變成了目標 MAC,再返給請求的機器。
接收端收到數據包以后的處理過程
當目的主機收到一個以太網數據幀時,數據就開始從協議棧中由底向上升,同時去掉各層協議加上的報文首部。每層協議都要去檢查報文首部中的協議標識,以確定接收數據的上層協議。
為什么有了 MAC 層還要走 IP 層呢?
之前我們提到,mac 地址是唯一的,那理論上,在任何兩個設備之間,我應該都可以通過mac 地址發送數據,為什么還需要 ip 地址?
mac 地址就好像個人的身份證號,人的身份證號和人戶口所在的城市,出生的日期有關,但是和人所在的位置沒有關系,人是會移動的,知道一個人的身份證號,并不能找到它這個人,mac 地址類似,它是和設備的生產者,批次,日期之類的關聯起來,知道一個設備的mac,并不能在網絡中將數據發送給它,除非它和發送方的在同一個網絡內。
所以要實現機器之間的通信,我們還需要有 ip 地址的概念,ip 地址表達的是當前機器在網絡中的位置,類似于城市名+道路號+門牌號的概念。通過 ip 層的尋址,我們能知道按何種路徑在全世界任意兩臺 Internet 上的的機器間傳輸數據
TCP/IP 的分層管理
TCP/IP 協議按照層次分為 5 層:應用層、傳輸層、網絡層、數據鏈路層、物理層,復雜的程序都需要分層,這個是軟件設計的要求,每一層專注于當前領域的事情。如果某些地方需要修改,我們只需要把變動的層替換掉就行,一方面改動影響較少,另一方面整個架構的靈活性也更高。最后,在分層之后,整個架構的設計也變得相對簡單了。
分層負載
了解了分層的概念以后,我們再去理解所謂的二層負載、三層負載、四層負載、七層負載就容易多了。
一次 http 請求過來,一定會從應用層到傳輸層,完成整個交互。只要是在網絡上跑的數據包,都是完整的。可以有下層沒上層,絕對不可能有上層沒下層。
二層負載
二層負載是針對 MAC,負載均衡服務器對外依然提供一個 VIP(虛 IP),集群中不同的機器采用相同 IP 地址,但是機器的 MAC 地址不一樣。當負載均衡服務器接受到請求之后,通過改寫報文的目標 MAC 地址的方式將請求轉發到目標機器實現負載均衡
二層負載均衡會通過一個虛擬 MAC 地址接收請求,然后再分配到真實的 MAC 地址
三層負載均衡
三層負載是針對 IP,和二層負載均衡類似,負載均衡服務器對外依然提供一個 VIP(虛 IP),但是集群中不同的機器采用不同的 IP 地址。當負載均衡服務器接受到請求之后,根據不同的負載均衡算法,通過 IP 將請求轉發至不同的真實服務器
三層負載均衡會通過一個虛擬 IP 地址接收請求,然后再分配到真實的 IP 地址
四層負載均衡
四層負載均衡工作在 OSI 模型的傳輸層,由于在傳輸層,只有 TCP/UDP 協議,這兩種協議中除了包含源 IP、目標 IP 以外,還包含源端口號及目的端口號。四層負載均衡服務器在接受到客戶端請求后,以后通過修改數據包的地址信息(IP+端口號)將流量轉發到應用服務器。
四層通過虛擬 IP + 端口接收請求,然后再分配到真實的服務器
七層負載均衡
七層負載均衡工作在 OSI 模型的應用層,應用層協議較多,常用 http、radius、dns 等。七層負載就可以基于這些協議來負載。這些應用層協議中會包含很多有意義的內容。比如同一個Web 服務器的負載均衡,除了根據 IP 加端口進行負載外,還可根據七層的 URL、瀏覽器類別來決定是否要進行負載均衡
七層通過虛擬的 URL 或主機名接收請求,然后再分配到真實的服務器。
2、tcp/ip協議深入分析
通過上面基本清楚了網絡的通信流程,在 http 協議中,底層用到了 tcp 的通信協議,接下來看看 tcp 的通信協議原理
tcp握手協議
所以 TCP 消息的可靠性首先來自于有效的連接建立,所以在數據進行傳輸前,需要通過三次握手建立一個連接,所謂的三次握手,就是在建立 TCP 鏈接時,需要客戶端和服務端總共發送 3 個包來確認連接的建立,在 socket 編程中,這個過程由客戶端執行 connect 來觸發
- 第 一 次 握 手(SYN=1, seq=x)客 戶 端 發 送 一 個TCP 的 SYN 標志位置 1 的包,指明客戶端打算連接的服務器的端口,以及初始序號 X,保存在 包 頭 的 序 列 號(SequenceNumber)字段里。發送完畢后,客戶端 進 入SYN_SEND 狀態。
- 第 二 次 握 手(SYN=1, ACK=1,seq=y,ACKnum=x+1):服務器發回確認包(ACK) 應 答 。即SYN 標志位和ACK 標 志 位 均 為1。服務器端選擇自己 ISN 序列號,放到 Seq 域里,同時將 確 認 序 號(Acknowledgement Number)設置為客戶的 ISN 加 1,即 X+1。發送完畢后,服務器 端 進 入SYN_RCVD 狀態。
- 第 三 次 握 手(ACK=1 ,ACKnum=y+1)客戶端再次發送確認包(ACK),SYN 標志位為 0,ACK 標志位為 1,并且把服務器發來 ACK 的序號字段+1,放在確定字段中發送給對方,并且在數據段放寫 ISN 發完畢后 , 客 戶 端 進 入ESTABLISHED 狀態,當服務器端接收到這個包時,也進 入ESTABLISHED 狀態,TCP 握手結束。
SYN 攻擊
在三次握手過程中,Server 發送 SYN-ACK 之后,收到 Client 的 ACK 之前的 TCP 連接稱為半連接(half-open connect),此時 Server 處于 SYN_RCVD 狀態,當收到 ACK 后,Server轉入 ESTABLISHED 狀態。SYN 攻擊就是 Client 在短時間內偽造大量不存在的 IP 地址,并向Server 不斷地發送 SYN 包,Server 回復確認包,并等待 Client 的確認,由于源地址是不存在的,因此,Server 需要不斷重發直至超時,這些偽造的 SYN 包將產時間占用未連接隊列,導致正常的 SYN 請求因為隊列滿而被丟棄,從而引起網絡堵塞甚至系統癱瘓。SYN 攻擊時一種典型的 DDoS 攻擊,檢測 SYN 攻擊的方式非常簡單,即當 Server 上有大量半連接狀態且源 IP 地址是隨機的,則可以斷定遭到 SYN 攻擊了
TCP 四次揮手協議
四次揮手表示 TCP 斷開連接的時候,需要客戶端和服務端總共發送 4 個包以確認連接的斷開;客戶端或服務器均可主動發起揮手動作(因為 TCP 是一個全雙工協議),在 socket 編程中,任何一方執行 close() 操作即可產生揮手操作
- 第一次揮手(FIN=1,seq=x):假設客戶端想要關閉連接,客戶端發送一個 FIN 標志位置為 1 的包,表示自己已經沒有數據可以發送了,但是仍然可以接受數據。發送完畢后,客戶端進入 FIN_WAIT_1 狀態。
- 第二次揮手(ACK=1,ACKnum=x+1):服務器端確認客戶端的 FIN 包,發送一個確認包,表明自己接受到了客戶端關閉連接的請求,但還沒有準備好關閉連接。發送完畢后,服務器端進入 CLOSE_WAIT 狀態,客戶端接收到這個確認包之后,進入 FIN_WAIT_2 狀態,等待服務器端關閉連接。
- 第三次揮手(FIN=1,seq=w):服務器端準備好關閉連接時,向客戶端發送結束連接請求,FIN 置為 1。發送完畢后,服務器端進入 LAST_ACK 狀態,等待來自客戶端的最后一個 ACK。
- 第四次揮手(ACK=1,ACKnum=w+1):客戶端接收到來自服務器端的關閉請求,發送一個確認包,并進入 TIME_WAIT 狀態,等待可能出現的要求重傳的 ACK 包。服務器端接收到這個確認包之后,關閉連接,進入 CLOSED 狀態。
客戶端等待了某個固定時間(兩個最大段生命周期,2MSL,2 Maximum Segment Lifetime)之后,沒有收到服務器端的 ACK,認為服務器端已經正常關閉連接,于是自己也關閉連接,進入 CLOSED 狀態
問題
【問題 1】為什么連接的時候是三次握手,關閉的時候卻是四次握手?
答:三次握手是因為因為當 Server 端收到 Client 端的 SYN 連接請求報文后,可以直接發送SYN+ACK 報文。其中 ACK 報文是用來應答的,SYN 報文是用來同步的。但是關閉連接時,當 Server 端收到 FIN 報文時,很可能并不會立即關閉 SOCKET(因為可能還有消息沒處理完),所以只能先回復一個 ACK 報文,告訴 Client 端,"你發的 FIN 報文我收到了"。只有等到我 Server 端所有的報文都發送完了,我才能發送 FIN 報文,因此不能一起發送。故需要四步握手。
【問題 2】為什么 TIME_WAIT 狀態需要經過 2MSL(最大報文段生存時間)才能返回到 CLOSE狀態?
答:雖然按道理,四個報文都發送完畢,我們可以直接進入 CLOSE 狀態了,但是我們必須假象網絡是不可靠的,有可以最后一個 ACK 丟失。所以 TIME_WAIT 狀態就是用來重發可能丟失的 ACK 報文。