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