1.常規的網絡交互過程是從客戶端發起網絡請求,用戶態的應用程序(瀏覽器)會生成 HTTP 請求報文、并通過 DNS 協議查找到對應的遠端 IP 地址。
2.在套接字生成之后進入內核態,瀏覽器會委托操作系統內核協議棧中的上半部分,也就是 TCP/UDP 協議發起連接請求。
3.然后經由協議棧下半部分的 IP 協議進行封裝,使數據包具有遠程定位能力。
4.經過 mac 層處理,找到接收方的目標 MAC 地址。
5.最終數據包在經過網卡轉化成電信號經過交換機、路由器發送到服務端,服務端經過處理拿到數據,再通過各種網絡協議把數據響應給客戶端。
6.客戶端拿到數據進行渲染
7.客戶端和服務端之間反復交換數據,客戶端的頁面數據就會發生變化。剛才的過程中,提到了多個層級和網絡協議,那么網絡為什么要分層呢?網絡協議又是什么?
前置知識:網絡分層和網絡協議
在計算機網絡時代初期,各大廠商推出了不同的網絡架構和標準,為統一標準,國際標準化組織 ISO 推出了統一的 OSI 參考模型。
當前網絡主要遵循的 IEEE 802.3 標準,就是基于 OSI 模型提出的,主要定義的是物理層和數據鏈路層有線物理數據流傳輸的標準。
那網絡為什么要分層?
通過分層處理簡化問題難度,降低復雜度,由于分層后的各層之間相互獨立,我們可以把大問題分割成小問題。同樣,分層也保證了網絡的松耦合和相對的靈活,分層拆分后易于各層的實現和維護,也方便了各層的后續擴展。網絡分層解決了網絡復雜的問題,在網絡中傳輸數據中,我們對不同設備之間的傳輸數據的格式,需要定義一個數據標準,所以就有了網絡協議。網絡協議是雙方通信的一種約定,以便雙方都可以理解對方的信息。接下來我們就用 OSI 協議體系中廣泛應用的 TCP/IP 層的體系結構來分析整個過程,需要重點關注數據處理的過程和網絡協議
ISO_OSI通信流轉示意圖:
發起請求階段(應用層)
先來看下網絡應用層的工作流程,依然以瀏覽器中輸入URL開始。
用戶輸入:在瀏覽器中輸入URL
在瀏覽器中輸入URL,瀏覽器會根據輸入內容,先匹配對應的URL以及關鍵字,給出輸入建議,同時校驗URL的合法性,并且會在URL前后補全URL
以輸入 cosmos.com 為例,首先瀏覽器會判斷出這是一個合法的 URL,并且會補全為 http://www.cosmos.com。
其中 http 為協議,cosmos.com 為網絡地址,每個網絡欄的地址都符合通用 URI 的語法。URI 一般語法由五個分層序列組成。后面的第一行內容我給你列了 URL 的格式,第二行做了行為說明。
URI = scheme:[//authority]path[?query][#fragment]
URI = 方案:[//授權]路徑[?查詢][#片段ID]
接著,瀏覽器從 URL 中會提取出網絡的地址,也叫做主機名(host),一般主機名可以為域名或 IP 地址,此處使用域名。
對 URL 進行解析之后,瀏覽器確定了服務器的主機名和請求路徑,接下來就是根據這些信息來生成 HTTP 請求消息了,但此時并未將HTTP請求發送出去
網絡請求前:查看瀏覽器緩存
瀏覽器在 HTTP 報文生成完成后,它并不是馬上就開始網絡請求的。
在請求發出之前,瀏覽器首先會檢查保存在本地計算機中的緩存,如果訪問過當前的 URL,會先進入緩存中查詢是否有要請求的文件。此時存在的緩存有路由器緩存、DNS 緩存、瀏覽器緩存、Service Worker、Memory Cache、Disk Cache、Push Cache、系統緩存等。
如果在瀏覽器緩存里沒有命中緩存,瀏覽器會做一個系統調用獲得系統緩存中的記錄,就是我們的 gethostbyname 方法,它的作用是通過域名獲取 IP 地址。這個方法會返回如下結構。
struct hostent
{
char *h_name;// 主機的別名.www.cosmos.com就是google他自己的別名
char **h_aliases;// 主機ip地址的類型,到底是ipv4(AF_.NET),還是pv6(AF_INET6)
int h_addrtype;// 主機ip地址的長度
int h_length;// 主機ip地址的長度
char **h_addr_list; // 主機的ip地址,注意,這個是以網絡字節序存儲的
#define h_addr h_addr_list[0] 這個函數,是將類型為af的網絡地址結構src,轉換成主機序的字符串形式,存放在長度為cnt的字符串中。返回指向dst的一個指針。如果函數調用錯誤,返回值是NULL
};
如果沒有訪問過當前的 URL,就會跳過緩存這一步,這時我們就會進入網絡操作了。
域名解析:DNS
接著上一小節在瀏覽器確認了輸入的 URL 之前沒有訪問,瀏覽器就會生成對應的 HTTP 請求,這時瀏覽器需要委托操作系統將 HTTP 報文發送到對應的服務端。在發送消息之前,還有一個工作需要做,就是查找服務端的 IP 地址,因為操作系統在發送消息時,必須知道對方的 IP 地址才可以發送。
但是由于 IP 地址由一串數字組成,不夠語義化,為方便你記憶,我們將 IP 地址映射為域名,于是就有這樣一個服務,維護了 IP 和域名的映射關系,它就是非常重要的基礎設施——DNS 服務器。DNS 服務器是一個分布式數據庫,分布在世界各地。
為提高效率,DNS 是按照一定的結構進行組織的,不同層次之間按照英文句點. 來分割。
在域名中,我們的層級關系是按照從左到右、從低到高排列的,不同層級由低到高維護了一個樹形結構,最高一級的根節點為 root 節點,就是我們所謂的根域名服務器,因此 cosmos.com 完整的域名應該是 cosmos.com.,后面的 . 相當于.root。
但是所有域名的頂級域名都一樣,因此被省略;再下一級.com 為頂級域名;再下一級的 cosmos 為權威域名。
因為這是一個樹形結構,所以客戶端只要請求到一個 DNS 服務器,就可以一層層遞歸和迭代查找到所有的 DNS 服務器了。按照由高到低的優先級,DNS 域名解析的過程排列如下。
DNS解析 > 瀏覽器DNS緩存 > hosts文件 > 本地DNS服務器 > ISP DNS服務器
操作系統協議棧(傳輸層和網絡層)
已經根據 URL 拿到需要請求的唯一地址了,接下來就要委托操作系統將 HTTP 報文發送出去了,這個過程由操作系統中的協議棧負責處理。
TCP/IP 協議棧是現在使用最廣泛的網絡協議棧,Internet 就是建立在 TCP/IP 協議?;A上的。除 TCP/IP 協議棧外,我們的操作系統內核可以支持多個不同的協議棧,如后續我們將會用到的 LwIp。
協議棧內部分為幾部分,分別承擔著不同的作用。
協議棧的上半部分負責和應用層通過套接字(Socket)進行交互,它可以是 TCP 協議或 UDP 協議。應用層會委托協議棧的上部分完成收發數據的工作
協議棧的下半部分則負責把數據發送給到指定方的 IP 協議,由 IP 協議連接下層的網卡驅動。
可靠性傳輸:建立 TCP 連接
瀏覽器通過 DNS 解析拿到 Cosmos 的 IP 地址后, 瀏覽器取出 URL 的端口(HTTP 默認 80,HTTPS 默認 443)。隨即瀏覽器會委托操作系統協議棧的上半部分創建新的套接字(Socket)向對應的 IP 發起 TCP 連接請求。
為了確保通信的可靠性,建立 TCP 首先會先進行三次握手的操作,可結合下面的圖示理解。
那么 TCP 的三次握手操作,是如何進行的呢?具體的操作步驟如下。
1.首先瀏覽器作為客戶端會發送一個小的 TCP 分組,這個分組設置了一個特殊的 SYN 標記,用來表示這是一條連接請求。同時設置初始序列號為 x 賦值給 Seq (這次捕獲組的數據為: SYN=1, Seq=1)。
2.服務器接受到客戶端的 SYN 連接后,會選擇服務器初始序號 y。同時向客戶端發送含有連接確認(SYN+ACK)、Seq=0(本例中的服務器初始序號)、Ack=1(客戶端的序號 x +1)等信息的 TCP 分組。
3.客戶端收到了服務器的確定字段后,向服務器發送帶有 ACK=1、Seq=1 (x+1)、Ack=1 (服務器 Ack 信息的拷貝)等字段的 TCP 分組給服務器。即使是發送一個 TCP 分組,也是一次網絡通信,那么對于 TCP 層來說,這一次通信的數據前面就要包含一個 TCP 包頭,向下層表明這是個 TCP 數據包。TCP 包頭其實是一個數據結構,我為你準備了一幅圖,以便理解。
下圖就是 TCP 的包頭,對于 TCP 頭部來說,以下幾個字段是很重要的,你要格外關注。
TCP包頭圖示:
首先,源端口號(Source port)和目標端口號(Destinantion port)是不可少的,如果沒有這兩個端口號,數據就不知道應該發給哪個應用。
其次,你需要注意的是一串有序數字 Sequence number,這個序號保證了 TCP 報文是有序被接受的,解決網絡包的亂序問題。
之后的 Acknowledgement number 是確認號,只有對方確認收到,否則會一直重發,這個是防止數據包丟失的。
緊接著還有一些狀態位,由于 TCP 是有狀態的,是用于維護雙方連接的狀態,狀態發生變更會更新雙方的連接狀態。后面還有一個,窗口大小 Window Size,用于流量控制。
TCP 層封裝好了數據包,會將這個 TCP 數據包向下層發送,而 TCP 層的下層就是 IP 層,下面我們一起去瞧一瞧完成目的地定位的 IP 層。
目的地定位:IP層
在IP協議里面需要有源地址IP和目標地址IP:
源地址IP,就是客戶端輸出的IP地址
目標地址IP,即通過DNS域名解析得到的web服務器ip
因為HTTP是經過TCP傳輸的,所以IP包頭的協議號,要填寫06,表示協議為TCP
假設客戶端有多個網卡,就會有多個IP地址,那IP頭部的源地址應該選擇哪個IP呢?
多個網卡需要填寫源地址時,需要判斷應該是用哪個一塊網卡來發送包。
這個時候就需要路由表規則,來判斷哪一個網卡作為源地址IP
在linux中可以 使用route -n 命令查看當前系統的路由表
根據上邊的路由表 假設Web服務器的目標地址是192.168.10.200
TCP在維護狀態的過程中,都需要委托 IP 層將數據封裝,發送和處理網絡數據包進入網絡層。IP 協議是 TCP/IP 協議棧的核心,IP 協議中規定了在 Internet 上進行通信時應遵循的規則,包括 IP 數據包應如何構成、數據包的路由等,而 IP 層實現了網絡上的點對點通信。
首先來看看 IP 層處理上層網絡數據包的過程,網絡數據包(無論輸入數據包還是輸出數據包)進入網絡層后,IP 層協議的函數都要對網絡數據包做后面這 5 步操作。
1、數據包校驗和檢驗
2、防火墻對數據包過濾
3、IP 選項處理
4、數據分片和重組
5、接收、發送和前送
為了完成上述操作,IP 層被設計成三個部分,分別是 IP 尋址、路由和分包組包。
其實在網絡通信的過程中,每個設備都必須擁有自己的 IP 地址才可以完成通信,我們的** IP 地址是以四組八位的組合進行約定,每組以. 號隔開,再轉化為十進制的方式。這里要注意,IP 地址并不是以主機數目進行配置的,而是根據網卡數**來進行。
有了 IP 地址,就可以通信了,但 IP 層仍然是一個軟件實現的功能邏輯層,那它如何完成通信呢,答案是不能直接完成通信,它只是把 IP 地址及相關信息組裝成一個 IP 頭,把這個 IP 頭放在網絡數據的前面,形成了 IP 包,最后把這個 IP 包發送給 IP 層的下一層組件就行了,IP 頭的格式如下所示。
IP頭部:
有了 IP 頭的網絡數據,就有了發送目的地的信息,那么該如何才能將報文發送到目的地呢?這就要請 MAC 出場了,這個 MAC 層,就是 IP 層的下一層組件
兩點傳輸 —— MAC
生成了IP頭部之后,接下來網絡包需要在IP頭部加上MAC頭部
MAC包頭里面有發送方MAC地址 接收方MAC地址 用于兩點之間的傳輸
MAC包頭的協議類型只有 IP協議(0800) ARP協議(0806)
發送方的MAC地址可以直接讀出來
接收方的MAC地址 需要通過ARP協議幫助我們路由器的MAC地址
ARP協議會在以太網中以廣播的形式 ,對以太網所有設備喊出,這個IP地址是誰的,請把你的MAC地址告訴我
對方回答之后,如果對方和自己處于同一個子網中,就可以將獲得的MAC地址寫入頭部
之后會把查詢結果放到一塊ARP緩存的內存
所以說:
先查詢ARP緩存,如果其中已經保存了對方的MAC地址,就不需要發送ARP查詢,可以直接使用ARP緩存中的地址
而當ARP緩存中不存在對方的MAC地址時,則發送了ARP廣播查詢
linux中可以通過arp -a
之后獲得了MAC頭部的數據包就可以開始走啦
發送方的 MAC 頭比較容易獲取,讀取當前設備網卡的 MAC 地址就可以獲取,而接收方的 MAC 頭則需要通過 ARP 協議在網絡中攜帶 IP 地址,在一個網絡中發送廣播信息,這樣就能獲取這個網絡中的 IP 地址對應的 MAC 地址,然后就能給我們的 IP 包加上 MAC 頭了,最后這個加上 MAC 頭的 IP 包,成為一個 MAC 數據包,就可以準備發送出去了
下面就進入最后的階段,數據的發送,即網絡層中的最低層——物理層
電信號的出口:網卡(物理層)
現在拿到了經過層層處理過的數據包,數據包只是一串二進制數據,網絡上的數據傳送,是依賴電信號的,所以我們現在需要將數據包轉化為電信號,才能在物理的網線上面傳輸。
那么數據包是如何被轉換電信號的呢
數據包通過網絡協議棧的層層處理,最終得到了 MAC 數據包,這個 MAC 數據包會交給網卡驅動程序,而網卡驅動程序會將 MAC 數據包寫入網卡的緩沖區(網卡上的內存).
然后,網卡會在 MAC 數據包的起止位置加入起止幀和校驗序列,最后網卡會將加入起止幀和校驗序列的 MAC 數據包轉化為電信號,發送出去。
互相扒皮——服務器和客戶端
數據包到了之后,看MAC是否符合,符合就收起來,
扒開IP的頭,發現IP地址符合,根據IP頭中的協議項,知道上層是TCP
扒開TCP的頭,里面有序列號,需要看一看這個是不是我想要的,如果是就放入緩存中返回一個ACK,如果不是就丟棄。TCP頭部還有端口號,HTTP的服務器正在監聽這個端口號。
于是服務器就知道HTTP進程要這個包,于是就把這個包發給HTTP進程。
服務器的HTTP進程看到,原來這個請求是要訪問一個頁面,于是就把網頁封裝在HTTP相應報文里。
之后相應報文也要穿上TCP IP MAC頭部,不過這次是源地址時服務器的IP地址,目的地址是客戶端的IP地址。
現在,數據終于通過網卡離開了計算機,進入到局域網,通過局域網中的設備,集線器、交換機和路由器等,數據會進入到互聯網,最終到達目標服務器。
接著,服務器就會先取下數據包的 MAC 頭部,查看是否匹配自己 MAC 地址。然后繼續取下數據包的 IP 頭,數據包中的目標 IP 地址和自己的 IP 地址匹配,再根據 IP 頭中協議項,知道自己上層是 TCP 協議。
之后,還要繼續取下數據包 TCP 的頭。完成一系列的順序校驗和狀態變更后,TCP 頭部里面還有端口號,此時我們的 HTTP 的 server 正在監聽這個端口號,就把數據包再發給對應的 HTTP 進程。
HTTP 進程從服務器中拿到對應的資源(html 文件),再交給操作系統對數據進行處理。然后再重復上面的過程,層層攜帶 TCP、IP、MAC 頭部。接下來數據從網卡出去,到達客戶端,再重復剛才的過程拿到相應數據。客戶端拿到對應的 HTML 資源,瀏覽器就可以開始解析渲染了,這步操作完成后,用戶最終就能通過瀏覽器看到相應的頁面。
畫了兩幅圖,來描述上述過程,第一幅是網絡協議各層之間封裝與拆封數據的過程,如下所示
TCP_IP協議棧:
下面的第二幅圖,是描述客戶端與服務器之間用網絡協議連接通信的過程,如下所示
此時客戶端和服務端之間通過 TCP 協議維護了一個連接狀態,如果客戶端需要關閉網絡,那么會進行四次揮手,兩邊的網絡傳輸過程至此完成。