40张图揭秘,「键入网址发生了什么」
計算機網絡的重要程度不言而言,也是非常的復雜。今天我將從輸入URL這個簡單例子開始,一起探索數據包的心路歷程。先看文章的大綱。
大綱
?
1 源頭------網址
網址即平時所說的URL。就是經常使用的以“Http://”開頭的那一串東東,其實常用的還有很多,比如 "FTP" , "FILE"等,我們所訪問的目標網站不同,網址開頭的寫法也就不同,下面列出常見的幾種URL。
URL基本格式
從上圖可知,URL 中可以包含服務器的域名,文件的路徑,收件人郵件地址,用戶名,密碼等信息??傊甎RL想表達的是:
訪問時所使用的協議。"HTTP" , "FTP" , "FILE"等
用戶名/密碼可選
所需訪問或下載文件的路徑
URL的相貌我們已經銘記于心,而且對于 URL 各個子模塊也有了基本的認識,可別小看這幾個小模塊,慢工出細活。我們拆分后仔細看看
URL拆分
理解URL個元素的含義
URL的拆分
從上面的結果我們可以得出,Web 服務器名稱為 www.xiaolan.com ,文件路徑名為 /dir1/index.html。所以這個URL表示我要訪問www.xiaolan.com這個web服務器上路徑為/dir/index.html的文件。
下面我們對這個URL稍微改動:
(a)http://www.xiaolan.com/dir/
這里注意,dir 后面的文件名被省略了,這樣的話服務器會使用默認的文件名,就反復咱們定義變量的時候,如果沒有賦初值,通常會給默認值。同樣的道理,服務器也會給一個默認的文件名,不同的服務器默認的文件會不一樣,通常會是 Index.html。
(c)http://www.xiaolan.com
這個就比較狠了,后面的"/"直接沒有,那該訪問啥呢?如果沒有路徑名,則代表訪問根目錄下面設置的默認文件。
(d)http://www.xiaolan.com/whatisthis
這末尾的 whatisthis 是什么呢?在這種情況,如果服務器中存在 whatisthis 的文件,則按照文件處理。如果是 wahtsthis 為目錄,則按照目錄進行處理。
?
2 HTTP初探
通過第一步對URL的解析,知道了我們所訪問的目標是什么,接下來是不是就要請求數據了呢?在做請求之前,我們一起回憶一下HTTP的基礎知識
首先HTTP協議定義了客戶端和服務器之間交互的消息內容和步驟。簡單的說呢即請求的信息包括了"請求啥"以及"你要進行什么操作",和我們面試的時候一樣,簡歷上面寫了XX項目,我們是不是也需要清楚自己的項目是什么,你在項目中什么角色一樣且做了哪些部分,別寫上去的東西一問三不知就比較尷尬了
在HTTP中請求啥這部分叫做 "URI",URI主要存放網頁數據的文件名或者是CGI程序如"/Manage/index.html"等。
“進行啥操作”統稱為方法。希望服務器能完成什么工作,比如讀取URI中表示的數據。哪都有哪些方法可以使用呢,這張圖總結常用的幾種方法以及含義
這里提一下比較常用且面試常問的兩個方法
GET
當訪問 Web 服務器獲取網頁數據的時候,使用的幾乎都是 Get 方法。在請求消息中表明使用Get方法,然后在URI 中表明文件名,比如是 /manage/index.html。服務器收到消息后,會打開/manage/index.html并讀取里面的數據,然后存放于相應消息中并返回給客戶端,最后在屏幕中完成呈現。
POST
當我們在購物填寫地址信息,或者填寫問卷信息的時候,將內容填寫到表格中,然后點擊提交這個過程,實際上通常就是采用的POST方式。這樣看來,采用POST的方式提交數據,我們需要準備三樣東西,分別為:所提供的方法,URL 和服務端。服務器收到請求數據后發送給 URI 所指定的應用程序,然后服務端獲取應用程序的執行結果并在響應信息中返回給客戶端。
OK,現在我們目標基本上明確了,將各個需要發送的內容組合并發給服務器。服務器進行解析,根據客戶端的需求完成使命后將需要反饋的信息存放在響應消息中,那么對于客戶端而言,也不知道到底是不是想要的結果。所以,服務端會在響應頭中用一個狀態碼表示操作的結果是成功還是失敗,比如 200 表示成功,404可能為沒找到文件。
此時客戶端收到了服務端的響應信息,瀏覽器覺得這太 lowb 了,給你渲染下并完美的呈現在我們眼前。HTTP的使命就此完成。
?
3 HTTP請求頭------保命天子
看到這里,我相信大家應該了解了 HTTP 的大概樣貌。萬事兒都是有原則的,那么請求的也是有格式的,不聽話就要被打屁屁。
先寫方法,加上空格,然后寫上 URI(文件或者程序的路徑名),行末尾協商HTTP版本號即完成第一行的任務。
第二行為消息頭。這一行主要是對第一行內容的進一步補充。比如會告知客戶端支持的數據類型、壓縮格式,數據有效期等,具體的我放張圖,需要的可以去了解下。
第三行為空行,然后加上需要發送的數據,這為消息體。整個消息也就結束
4 HTTP響應-----我行我素
響應的內容和請求信息的內容類似。只是響應中的第一行內容為狀態碼,表示執行結果是否成功。常見的HTTP狀態碼如下圖所示
響應信息返回后顯示在屏幕中,如果為純文字,到此就結束了。但是大部分時候都會有圖片,視頻,音頻等信息,這個時候怎么辦?
瀏覽器會從響應信息中的文字搜索相應的標簽,如果有圖片等其他信息,則再次請求服務器,按照相應的文件名向服務器發送請求并顯示在剛才預留的空間中。至此,我們訪問網頁的初級過程版本就差不多結束了。下面用一個案例加深下印象。
上圖是簡化版,在這里再穩固幾點
Get和Post哪些區別
請求頭和響應頭哪些位置是需要空格或者空行
常用響應狀態碼和請求方法
到此,我們從表面上知道,從敲入網址,構造請求消息,收到響應,并能將美女圖片給呈現在眼前,這樣就完事了?不好意思,我們時刻都有一顆的去大廠的心,意味著我們不能只知道表面現象還要適當去了解更多的細節。
?
5 刨根
雖然瀏覽器能夠解析我們的網址,但是它并不具備將消息發送到網絡中的能力,那是誰打的輔助?當然是操作系統大哥,為了讓操作系統大哥幫忙,我們得先拜訪下操作系統大哥,問問需要我們提供哪些資源,需要什么,我們就全力配合它。
IP地址
我們在瀏覽器輸入的是網址,但是操作系統需要的是IP地址,所以我們需要想辦法進行轉換。轉換的方法就需要請教 DNS 了。很簡單,我們告訴DNS,"我的域名是www.xiaolan.com,請告訴我的 IP 地址",OK,DNS服務器很爽快,回復"你的IP地址是xxx.xxx.xxx.xxx"。那么問題來了,我們是如何向 DNS 發送的這個查詢呢?我們先來復習DNS
DNS
有些小伙伴說 Mac 地址不能作為標識嗎?可是太不容易記憶了,從而出現了簡化了 IP 形式,可以它被直接暴露給外網不說,還讓人類覺得比較麻煩,干脆用幾個字母算了,也就是域名了。域名不僅僅能夠代替 IP,還有很多其他的用途比如在 Web 應用中用來標識虛擬主機。
說了這么多,協議頭部,到底有哪些字段,其含義是什么都還不知道,那怎么去分析報文,下面我們一起再看看報文什么樣子
DNS報文結構
基礎結構部分
DNS報文基礎部分為DNS首部。其中包含了事務ID,標志,問題計數,回答資源計數,回答計數,權威名稱服務器計數和附加資源記錄數。
事務ID:報文標識,用來區分 DNS 應答報文是對哪個請求進行響應
標志:DNS 報文中標志字段
問題計數:DNS 查詢請求了多少次
回答資源記錄數:DNS 響應了多少次
權威名稱服務器計數: 權威名稱服務器數目
附加資源記錄數: 權威名稱服務器對應 IP 地址的數目
重點!!!!基礎結構中的標志字段細分如下:
標志字段
QR(Response):查詢請求,值為0;響應為1
Opcode:操作碼。0表示標準查詢;1表示反向查詢;2服務器狀態請求
AA(Authoritative):授權應答,該字段在響應報文中有效。通過0,1區分是否為權威服務器。如果值為 1 時,表示名稱服務器是權威服務器;值為 0 時,表示不是權威服務器。
TC(Truncated):表示是否被截斷。當值為1的時候時,說明響應超過了 512字節并已被截斷,此時只返回前512個字節。
RD(Recursion Desired):期望遞歸。該字段能在一個查詢中設置,并在響應中返回。該標志告訴名稱服務器必須處理這個查詢,這種方式被稱為一個遞歸查詢。如果該位為 0,且被請求的名稱服務器沒有一個授權回答,它將返回一個能解答該查詢的其他名稱服務器列表。這種方式被稱為迭代查詢。
RA(Recursion Available):可用遞歸。該字段只出現在響應報文中。當值為 1 時,表示服務器支持遞歸查詢。
Z:保留字段,在所有的請求和應答報文中,它的值必須為 0。
rcode(Reply code):通過返回只判斷相應的狀態。
當值為0時,表示沒有錯誤;當值為1時,表示報文格式錯誤(Format error),服務器不能理解請求的報文;當值為 2 時,表示域名服務器失敗(Server failure),因為服務器的原因導致沒辦法處理這個請求;當值為 3 時,表示名字錯誤(Name Error),只有對授權域名解析服務器有意義,指出解析的域名不存在;當值為 4 時,表示查詢類型不支持(Not Implemented),即域名服務器不支持查詢類型;當值為 5 時,表示拒絕(Refused),一般是服務器由于設置的策略拒絕給出應答,如服務器不希望對某些請求者給出應答。
問題部分
該部分是用來顯示DNS查詢請求的問題,其中包含正在進行的查詢信息,包含查詢名(被查詢主機名字)、查詢類型、查詢類。
查詢名:一般為查詢的域名,也可能是通過IP地址進行反向查詢
查詢類型:查詢請求的資源類型。常見的如果為A類型,表示通過域名獲取IP。具體如下圖所示
查詢類:地址類型,通常為互聯網地址為1
資源記錄部分
資源記錄部分包含回答問題區域,權威名稱服務器區域字段、附加信息區域字段,格式如下
資源記錄部分
資源記錄部分
域名:所請求的域名
類型:與問題部分查詢類型值一直
類:地址類型,和問題部分查詢類值一樣
生存時間:以秒為單位,表示資源記錄的生命周期
資源數據長度:資源數據的長度
資源數據:按照查詢要求返回的相關資源數據
DNS解析詳解
知道了DNS大概是什么,它的域名結構和報文結構,是時候看看到底怎么解析的以及如何保證域名的解析比較穩定和可靠
DNS核心系統
根域名服務器(Root DNS Server),大哥,管理頂級域名服務并放回頂級域名服務器IP,比如"com","cn"
頂級域名服務器(Top-level DNS Server),每個頂級域名服務器管理各自下屬,比如com可以返回baidu.com域名服務器的IP
權威域名服務器(Authoritative DNS Server),管理當前域名下的IP地址,比如Tencent.com可以返回www.tencent.com的 IP 地址
域名結構
核心系統
舉個例子,假設我們訪問"www.google.com"
訪問根域名服務器,這樣我們就會知道"com"頂級域名的地址
訪問"com"頂級域名服務器,可知道"google.com"域名服務器的地址
最后方位"google.com"域名服務器,就可知道"www.google.com"的IP地址
嘿嘿,目前全世界13組根域名服務器還有上百太鏡像,但是為了讓它能力更強,處理任務效率更高,盡量減少域名解析的壓力,通常會加一層"緩存",意思是如果訪問過了,就緩存,下一次再訪問就直接取出,也就是咱么經常配置的"8.8.8.8"等
操作系統中同樣也對DNS解析做緩存,比如說曾訪問過"www.google.com",
其次,還有我們熟知的hosts文件,當在操作系統中沒有命中則會在hosts中尋找。
這樣依賴,相當于有了 DNS 服務器,操作系統的緩存和 hosts 文件,能就近(緩存)完成解析就好,不用每次都跑到很遠的地方去解析,這樣大大減輕的 DNS 服務器的壓力。畫了一個圖,加深印象
域名解析
DNS解析過程
嗯?想必應該知道這個過程了,我們再舉個例子,假設我們訪問www.qq.com
客戶端發送一個 DNS 請求,請問 qq 你的IP的什么啊,同時會在本地域名服務器(一般是網絡服務是臨近機房)打聲招呼
本地收到請求以后,服務器會有個域名與IP的映射表。如果存在,則會告訴你,如果想訪問qq,那么你就訪問XX地址。不存在則會去問上級(根域服務器):"老鐵,你能告訴我 www.qq.com"的IP么
根 DNS 收到本地 DNS 請求后,發現是.com,"www.qq.com喲,這個由.com大哥管理,我馬上給你它的頂級域名地址,你去問問它就好了"
這個時候,本地 DNS 跑去問頂級域名服務器,"老哥,能告訴下www.qq.com"的ip地址碼",這些頂級域名負責二級域名比如qq.com
頂級域名回復:"小本本記好,我給你 www.qq.com 區域的權威 DNS 服務器地址",它會告訴你
本地DNS問權威DNS服務器:"兄弟,能不能告訴我 www.qq.com 對應IP是啥"
權威DNS服務器查詢后將響應的IP地址告訴了本地 DNS,本地服務器將 IP 地址返回給客戶端,從而建立連接。
那如果我們寫段cs程序都得這么麻煩的?不不,上面的是大佬們做好,我們只需要使用相關庫就好了,這里就得說說Socket庫了。
Socket庫
實際上,這是一段程序包含在操作系統的 Socket 庫中,我們只需要調用相關的庫就可以獲得IP。那 Socket 庫又是個什么東西?
庫,文庫, Github 倉庫,總之一定是 xxx 的集合。為了簡便開發,大佬們會將很多方法封裝為庫,開發人員直接調用即可,這樣不僅節省編程的工作量,也提高開發的工作效率,但是如果庫出了問題,你就可能不是 GG 半會兒了。Socket 亦是如此,提供了一些網絡編程相關的庫,方便開發人員調用操作系統的網絡功能。如下圖,當我們調用 gethostbyname 的時候,就會向 DNS 服務器發送查詢消息,然后 DNS 服務器進行響應。響應的信息就會包含查詢到的IP地址,解析器取出IP地址并寫入指定的內存中,瀏覽器只需要從內存地址中取出 IP 地址然后加上HTTP請求信息交給操作系統大哥即可
現在我們拿到了 IP 地址,就可以委托協議棧向這個目標 IP 發送信息了,下面我看看使用Socket庫發送數據的過程
理解下上圖,服務端創建套接字,我們可以想象為一個水管,當服務端監聽進入等待狀態后,客戶端就可以連接服務端并塞數據到管子中,進行數據的收發。當然,如果不想聊天了,任何一方都可以斷開,套接字隨機也就斷開,通信結束??偨Y為這幾個階段
創建套接字階段
管子連接到服務端套接字
收發數據
斷開并刪除套接字
那么再具體的實現中是怎樣的呢?
創建套接字,調用socket函數會返回一個描述符,這個描述符類似于門牌號,通過門牌號就可知道你住在那一房間。隨后的通信直接關聯此描述符即可
連接
創建完套接字,我們就得開始建立連接了,可是還是需要協議棧的幫忙,那么協議棧都干了啥呢?
我們從上到下來刮一遍
最上面是網絡應用程序,其中包含了瀏覽器,郵件客戶端等,緊接著是Socket庫,其中一個功能就是向 DNS服務器發出請求獲取IP。
往下是操作系統大哥內臟,其中包含了協議棧。上面是傳輸層常見的TCP和UDP,分別負責 TCP 協議的收發數據和 UD P的首發數據。
往下是IP,控制網絡數據包的收發操作。主要負責將網絡數據包發送給通信對象。其中包含ICMP,ARP等協議。其中ICMP主要負責告知網絡數據包在發送的過程中產生的錯誤信息,ARP負責根據IP地質查詢MAC地質
再往下就是網卡驅動負責的硬件網卡了。直白點說是對網線的信號執行發送接收操作
將剛才我們創建的客戶端套接字與服務器那邊的套接字連接上。使用的函數為connect,其中需要三個參數:
描述符
connnet會將描述符告訴協議棧,協議棧知道描述符后就來判斷到底使用哪個套接字去連接服務端
地址
這個IP地址即使剛才我們通過DNS獲取的IP地址,并將IP地址告知協議棧
端口
IP地址是用來區分網絡中各個計算機而分配的數值??梢岳斫鉃楣簿值墓秒娫?#xff0c;我們打電話過去找某人還需要知道名字吧,不然打過去找誰?這個某某人就類似端口號,根據這個端口號我們能找到具體的聯系人。所以通過IP+端口的方式確定具體的套接字。端口號那么多,到底指定多少端口?不慌,其實服務器上面使用的大部分端口都事先定義好了,比如HTTP多為80,SMPT通常為35端口。這樣子就可以正兒八經的通信了
通信
一旦套接字建立連接,隨著就可以委托協議棧完成數據的發送操作。具體流程
應用程序準備好需要發送的數據
構造HTTP請求信息
調用write委托協議棧發送數據
那連接的真正含義是什么?
在真正的實體情況下,所謂連接通常是網線的連接,網線確實一直連接著,在這里,連接的意思是通信的雙方能夠交換控制信息,并在套接字中記錄這些信息。
連接意義之一是告知協議棧IP和端口
當創建完套接字以后,并沒有存放任何的數據,自然也就不知道和誰說話。這個時候,如果應用程序要求發送數據,對于協議棧而言還是一臉懵逼。只有將IP和端口告知協議棧,他才會開始干活
服務端通過Socket庫中的read接收消息,這里注意,調用read的時候需要制定用于存放響應消息的內存地址,也叫做接收緩存區。
連接意義二:
服務端創建套接字,但是不知道和誰通信。所以等待客戶端告知"我是XX,我的IP是xxx,端口號是XXX"
具體操作步驟
通過connect將IP地址和端口信息傳遞給協議棧的TCP模塊,它會和服務端的TCP模塊交換信息。具體交換哪些信息呢??蛻舳藴蚀_找到服務端以后,會將頭部控制位中的SYN置為1。TCP 模塊將信息傳遞給IP模塊并委托它進行發送,服務端將接收到的IP模塊傳送給TCP模塊 ,TCP模塊根據控制信息找到端口號相同的套接字并將狀態修改為正在連接。此時將會進行響應,響應的過程中將ACK控制位設置為1表示已經收到對應的網絡包。TCP屬于全雙工通信,為了盡全力保證網絡傳輸信息的不丟失,會進行雙方確認機制。
此時網絡包到達客戶端,通過IP模塊到達TCP模塊,TCP模塊通過頭部信息確認連接服務器的這個操作是否成果。如果此時SYN為1則表示連接成功。然后將響應中的ACK設置1告訴服務器你的響應我收到了。這樣連接操作完成??刂屏鞒探唤o應用程序
?
6 應用階段
當連接后到達應用程序后,此時將決定我們需要發送什么數據 ,怎么發數據,是按照流的方式還是逐字節發送,以及發什么內容,這樣的多樣性對于協議棧而言是不怎么關心的。對于協議棧,它不會是收到什么數據就馬上發送,它會將數據先暫存緩沖區,如果收到數據就發送,難免會出現大量的小包,這樣會讓網絡效率下降。那對于協議棧而言,到底一次滿足多少才進行發送呢?
根據MTU判斷
MTU是一個網絡的最大長度,以太網中為1500字節,減去MTU的頭部長度,所能容納的最大數據長度為1460即MSS。這樣就可避免出現大量的小包問題
根據時間。
協議棧內部有個計時器,到達時間就將網絡包發送出去。
仔細理解這兩點,你會發現兩者沖突了。因為如果考慮長度的優先級更高,那么網絡效率高,但是可能等待緩沖區的時間比較長。如果時間優先級更高,延遲時間就短,但是降低了網絡效率。所以在應用程序中提供了選項,在開發的過程中可以根據實際情況進行設置。
如果HTTP請求消息太長了怎么辦呢?
數據大了則進行拆分,拆分后為了能完整組裝,每個小塊提前做好標識。當判斷需要發送這些數據的時候,就在每一塊的數據前面加上TCP頭部,然后交給IP模塊進行數據的發送 。
ACK確認機制
如果能發出數據,但是我們發了數據卻不知道是否已經收到,或者中途有沒有出現損失數據卻不知情。所以,引入ACK的確認機制進行可靠的傳輸。
我們客戶端在發送數據的時候,會告知對方發送的數據從第幾個字節開始且長度是多少,對于接收方而言也是能很好地清楚是否完整的接收。比如上次接收到的是520字節,那么接下來收到的包是521,說明中間沒什么問題。如果收到的包是1314,中間這段時間可能就出軌了。這樣子,如果沒有遺漏,接收方就會將一共接收到了多少字節寫到ACK中并發送給對方。不知道大家理解沒有,我再換個方式說一遍。發送電報:“我現在發送的數據是從XX字節開始的部分,一共有XX字節哈”,接收端:“到XX字節之前的數據我都接收完了",這就是確認機制。在此跑一個面試題,為什么序號不是從"1"開始?
TCP正是采用這樣的確認機制,數據在傳輸過程中,在諸如網絡集線器等設備就不在有錯誤補償機制,這些設備檢測到錯誤就直接丟棄相應的包。TCP采用ACK的確認機制,這個確認的回復時間是根據什么來定?是固定時間內必須返回ACK呢,還是會根據距離遠近等動態調整呢?
通常來說,在局域網中ACK的返回相對會比互聯網返回所需時間更短。TCP采用動態調整等待時間的方法。這里所說的等待時間是根據ACK返回所需時間來判斷的。也就是說TCP在發送數據后就會持續觀測ACK返回時間,如果發現慢了則會延長等待的時間。
我們每發一個包,等待確認后再發送另一個包。那么在等待的這個過程是不是就浪費了時間呢。為了改變這樣的情況,TCP采用了滑動窗口的方式管理數據發送和ACK號的操作。
滑動窗口
發送一個包后,不傻等ACK的返回,而是繼續發送后續的包,這樣就充分的利用這段空閑時間。但是這樣也出現了一個問題,可能出現發送包的頻率太快以致于接收方處理不過來出現堆積。
首先,TCP接收方收到包以后,并不是馬上處理交給應用程序,而是先存在暫存區,但是發送方實在是太快了,接收方處理不過來,暫存區也滿了。怎么解決?我們希望發送方能夠隨時知道接收方的接收數據能力,這樣就不會無腦的扔數據過去了。ok,TCP 就是這樣處理的,它會告訴發送方自己最多還能處理多少數據,然后發送方就會根據接收方的大小進行數據發送控制,這也就是滑動窗口的精髓所在。
通過這樣長途跋涉終于發送了HTTP請求信息,等待著響應信息,客戶端通過read獲取響應信息,和發送數據時協議棧工作類似,從接收緩沖區中取出數據并傳遞給應用程序
斷開連接
在 Web 使用的HTTP協議規定,如果web服務器發送完消息后,就應該主動的斷開操作??蛻舳酥罃嚅_后,就當再執行read調用時就會被提醒收發數據已結束,隨即也調用 close 進行斷開操作。前面我們說過,每獲取一次數據就會執行一次連接,這樣的效率是非常低的,所以在 HTTP1.1 中就可以一次連接多次請求和響應?!?/p>
假設服務器端調用close程序,此時協議棧會生成斷開信息的TCP頭部,也就是將控制位中的FIN置為1,然后委托給IP模塊向客戶端發送數據
客戶端收到服務端的 Fin 為1的包后,為了告知服務端已經收到了這個 Fin 包,會返回一個ACK號,等待應用程序來處理數據。當應用程序調用read的時候,發現服務端告訴它的是數據已經全部收到,所以客戶端隨即開始關閉操作,生成FIN比特為1的TCP包,然后交給IP模塊發送給服務器,然后服務端段返回 ACK 表示收到。這樣客戶端與服務端全部關閉結束。
7 IP
上面講述了想要實現通信,在TCP連接揮手時需要請IP模塊幫忙并封裝為包發送給就近的網絡設備,網絡設備根據頭部控制信息確定目的地址,如何確定的呢?轉發設備中有一張映射表,其中表中能表示"你可以將包發送到XX目的地",此時IP協議再委托以太網協議,尋找路由器的以太網地址(mac地址),如果有多個轉發設備,原理過程一樣,最終到達接收方的網絡設備。
整個流程算是了解了,我們繼續深究下IP模板到底是如何完成收發操作的。當TCP委托IP模塊進行數據包傳送的時候,告訴了目的地址是在哪里,然后經過一系列的中間網絡設備尋找以太網地址也就是mac地址,所以現在擁有了IP頭部和mac頭部,發送給網卡等硬件設備,網卡將數字信息轉換為電信號或光信號并發送出去。
當接收方收到數據包會做出響應,其路線相反。數據包以電信號的方式從網線發出,傳遞給IP模塊,IP模塊將MAC頭部、IP頭部后面數據傳遞給TCP模塊
IP地址通過TCP模塊獲取目的地址,而TCP模塊是從應用程序中獲取IP地址,對于IP模塊而言,只是乖乖的將包發往應用程序指定的接收方,那假設這個IP地址是錯誤的怎么辦呢,IP模塊不管,他只是負責打個包發出去,因為這個事兒是應用程序的任務?,F在我們已經知道IP模塊中有填寫目的IP地址,還有哪些重要的控制信息呢
從上圖我們發現還需要32字節的發送方IP地址,如果當前計算機只有一張網卡,那就是計算機的IP地址。
協議號:代表包從哪個模塊來。如果是TCP模塊則填寫06,如果是UDP模塊填寫17。
MAC
生成了IP頭部后,需要在IP頭部加上MAC頭部,其中包含了接收方和發送方的MAC地址信息,因為在以太網的世界里需要按照以太網的規則辦事兒
以太網類型
以太網類型代表后面內容的類型,比如如果是IP地址相關則為0800
發送方MAC地址
MAC地址在網卡生產時就放入ROM中,取出存放于MAC頭部即可。
接收方MAC地址
要知道接收方的MAC地址,又需要找幫手了(ARP),在局域網中大喊一聲“xx這個IP地址是哪個?麻煩把你的MAC地址告訴我”,此時就有人給予回應"這是我的IP地址,我的MAC地址是XX",但是我們不可能每次都一頓喊,所以就有殺手锏"ARP緩存",一次詢問后就會保存于緩存表中,下次再來如果能匹配到表就可直接獲取MAC地址。
此時IP模塊完成所有任務。下面就到網卡
8 網卡
上面辛辛苦苦的將包組裝完成,但都是數字信息,我們需要轉換為電信號或者光信號才能在網絡上傳輸,這就網卡的作用。但是就當當的一塊網卡能干啥,啥也干不了,他需要插上去并裝上網卡驅動,計算機開機啟動之時對網卡進行初始化才能開始使用。
網卡驅動從IP模塊獲取包之后,復制到網卡緩沖區,然后告知MAC層,MAC模塊從緩沖區取出包并加上頭部和起始幀,末尾加上幀校驗序列
發送信號分為兩種方式,一種是集線器方式,一種是交換機的全雙工模式。
集線器方式
發送信號之前需要先檢查線路中是否存在其他信號,以免造成沖突。MAC模塊從頭部開始逐比特轉換為電信號,然后交給 PHY 模塊發送出去,PHY模塊將信號轉換為可以在網線上傳輸的格式并通過網線發送出去。但是我們知道,由于電磁波接觸到金屬等半導體后會產生電流,與信號摻雜在一起,這樣勢必就會對原有的信號造成影響,為了盡量的避免這種影響,使用了雙絞線的方式來抑制噪聲。為什么雙絞線就可以抑制噪聲嘞,因為當電磁波接觸到信號線時,假設電流方向為右,當使用雙絞線的方式螺旋纏繞后,兩個信號線所產生的的電流方向就會相反,從而相當于負負得正低效,不的不說闊學家們牛掰
全雙工模式
全雙工模式可以讓發送和接收操作同時進行且不產生碰撞,因為在全雙工模式下,無需等待其他信號就可發送信號,所以比半雙工更快
接收方
在半雙工的通信過程中,發送信號到達結合搜模塊,信號的開頭是報頭,從起始幀分隔符開始將后面的信號轉換為數字信息,即 PHY模塊 先開始工作,將信號轉換為通用格式并交付給MAC模塊,MAC模塊從頭開始將信號轉換為數字信號并存放緩沖區,這里注意,到達信號末尾的時候需要檢查FCS,檢查方法是通過響應算法計算出結果并和包末尾比較,如果不一致則會當做錯誤包丟棄。FCS沒問題,再通過MAC頭部接收方的地址查看是否給自己的包,如果不是也就沒必要亂收,直接丟棄,如果MAC地址一致則將包存放緩沖區,此時MAC模塊完成任務。
我們知道計算機會執行千萬種任務,它不會隨時監控網卡的行蹤,所以需要打斷計算機當前執行的任務,告訴它網卡現在發生的事情,這就是中斷。網卡驅動被中斷處理程序調用后,會從網卡的緩沖區中取出收到的包,并通過 MAC 頭部中的以太類型字段判斷協議的類型,如果是0080則代表IP協議,那么網卡驅動就講這樣包給TCP/IP協議棧。此時IP模塊開始工作
檢查IP頭部,保證格式正確
查看接收方IP,如果接收的IP地址與客戶端發送過來IP一致則接受這個包,否則就很可能除了問題,此時IP模塊會通過ICMP將錯誤告知發送方,ICMP包含了哪些錯誤提示呢,總結如下
此時IP模塊交給TCP模塊,TCP模塊根據IP頭部的接收方和發送方IP地址,以及TCP頭部的的發送,接收端口信息,組成<發送地址,接收地址,源端口,目的端口>四元組信息查找對應的套接字,從而可查看通信的狀態并執行相關的通信。
9 防火墻
看似一切到達服務器還比較順利,順利歸順利,但是我們的大部分項目中不得不考慮安全因素,不是什么數據包都可以隨便進來,所以必須使用某種手段過濾掉一部分數據包,這就是防火墻
不知道大家用過Tcpdump、Wireshark等工具沒,它的過濾機制類似于防火墻的原理,那么為了實現過濾,我們就需要深刻了解各層協議的頭部構造,只有熟悉其頭部字段,才能在過濾表達式中施展魔法。
通過IP 端口等過濾
比如常見明文協議HTTP使用的80端口,我們可以通過設置IP+端口的方式限制其他數據包的通行。
設置控制位的方式
比如在TCP三次握手的時候會交換或者更新ack syn等信息,我們則可以通過設置相應位置來達到我們過濾的目的
隨著系統越來與牛逼,收益越來越來,老板跑來:“小伙計,用戶反映請求后半天收不到消息誒”。豈不是廢話么,系統做得好,跑路少不了,錢不到手,怎敢跑路,成,一頓性能測試猛如虎,哎呀,加個負載均衡試試?
負載均衡
隨著用戶訪問量的劇增,單臺服務器明顯感覺到了壓力,再這樣下去用戶可能直接要干我,同事小A牛逼啊,上來就是:"上性能高一點的服務器啊",小B也不賴:“多買幾臺服務器不就完事了?” 好,我們就聽聽小B的方案
從曾經的一臺服務器,增加到現在到五臺服務器,相當于每臺服務器分擔1/5,這樣壓力自然小了很多,那問題來了,怎么才能將請求分散到各臺服務器呢?哪都有哪些負載均衡的方案?
砸錢
最初實現負載均衡采取的方案很直接,直接上硬件,當然也就比較貴,互聯網的普及,和各位科學家的無私奉獻,各個企業開始部署自己的方案,從而出現負載均衡服務器
HTTP重定向負載均衡
也屬于比較直接,當HTTP請求到達負載均衡服務器后,使用一套負載均衡算法計算到后端服務器的地址,然后將新的地址給用戶瀏覽器,瀏覽器收到重定向響應后發送請求到新的應用服務器從而實現負載均衡,如下圖所示
優點:
簡單,如果是java開發工程師,只需要servlet中幾句代碼即可
缺點:
加大請求的工作量。第一次請求給負載均衡服務器,第二次請求給應用服務器
因為要先計算到應用服務器的IP地址,所以IP地址可能暴露在公網,既然暴露在了公網還有什么安全可言
DNS負載均衡
了解計算機網絡的你應該很清楚如何獲取IP地址,其中比較常見的就是DNS解析獲取IP地址。用戶通過瀏覽器發起HTTP請求的時候,DNS通過對域名進行即系得到IP地址,用戶委托協議棧的IP地址簡歷HTTP連接訪問真正的服務器。這樣不同的用戶進行域名解析將會獲取不同的IP地址從而實現負載均衡
乍一看,和HTTP重定向的方案不是很相似嗎而且還有DNS解析這一步驟,也會解析出IP地址,不一樣的暴露?每次都需要解析嗎,當然不,通常本機就會有緩存,在實際的工程項目中通常是怎么樣的呢
通過DNS解析獲取負載均衡集群某臺服務器的地址
負載均衡服務器再一次獲取某臺應用服務器,這樣子就不會將應用服務器的IP地址暴露在官網了
反向代理負載均衡
這里典型的就是Nginx提供的反向代理和負載均衡功能。用戶的請求直接叨叨反向代理服務器,服務器先看本地是緩存過,有直接返回,沒有則發送給后臺的應用服務器處理。
IP負載均衡
上面一種方案是基于應用層的,IP很明顯是從網絡層進行負載均衡。TCP./IP協議棧是需要上下層結合的方式達到目標,當請求到達網絡層的時候。負載均衡服務器對數據包中的IP地址進行轉換,從而發送給應用服務器
注意,這種方案通常屬于內核級別,如果數據比較小還好,但是大部分情況是圖片等資源文件,這樣負載均衡服務器會出現響應或者請求過大所帶來的瓶頸
數據鏈路負載均衡
它可以解決因為數據量他打而導致負載均衡服務器帶寬不足這個問題。怎么實現的呢。它不修改數據包的IP地址,而是更改mac地址。應用服務器和負載均衡服務器使用相同的虛擬IP
以上介紹了幾種負載均衡的方式,但是很重要的負載均衡算法卻沒有設計,其中包含了輪詢,隨機,最少連接,下面分別對此進行介紹(假設以Nginx為例)
輪詢
輪詢是Nginx中默認的處理負載的方式,從方式名稱應該可以猜出輪詢即輪流的分配到后端的服務上。舉個例子來說,假設目前后端有4臺服務器,此時過來6個連接,如果采用輪詢的方式,他就是這樣工作A->1,B->2,C->3,D->4,A->5,B->6
upstream?XXX{server?localhost:8081;server?localhost:8082;server?localhost:8083;}server?{listen?80;server_name?www.xiaolan.com;location?/{proxy_pass?http://xxx;}}Hash方式*處理公式:abs(客戶端ip.hash())%服務器數量*
因為客戶端的ip地址是唯一不變的,所以,通過hash算法計算出ip地址對應的哈希碼值,通過哈希碼值對服務器的數量進行一個求模運算。這樣就可以保證每個客戶端訪問的服務器都是保持不變的,因為hash算法的散列特點,也可以近似的當作平均分配。
upstream?H_xx{ip_hash;server?localhost:8081;server?localhost:8082;server?localhost:8083;}server?{listen?80;server_name?www.xiaolan.com;location?/{proxy_pass?http://H_xx;}出現的問題
Hash算法中的散列特點,會導致某臺服務器請求量過高,其他服務器請求卻很少的情況。比如A服務器處理請求1000,而B服務器請求只有80,C服務器請求為20。我們希望后面的請求盡量來C服務器,所以出現了下面的方案
最小連接方式
采用這種方式,Nginx會將請求發送給當前處理請求數量最少的服務器從而緩解集群的壓力
upstream?XXX{leash_conn;server?localhost:8081;server?localhost:8082;server?localhost:8083;}server?{listen?80;server_name?www.xiaolan.com;location?/{proxy_pass?http://XXX;}}既然是將請求分給目前連接數最少的服務器,那好,我們看看這種情況。A服務器買的比較早,承受的并發數為200,B服務器稍微能承受的服務器并發數高一點500,C服務器能承受的并發數為1000。目前各個服務器情況如何呢?此時A服務器已經處理了199個連接,B服務器處理了499個連接,C服務器處理了500個連接,我們當然希望接下來的請求交給C服務器處理,不然對于AB而言豈不是壓死了最后一根稻草,所以出現下面這種方式
基于權重的方式
通過設置權重的方式合理分配請求連接數
upstream?XXX{server?localhost:8081?weight=6;server?localhost:8082?weight=2;server?localhost:8083?down;}server?{listen?80;server_name?www.xiaolan.com;location?/{proxy_pass?http://xxx;}}此時通過weight權重進行資源的分配。down表示當前服務器不參加負載均衡。
有道無術,術可成;有術無道,止于術
歡迎大家關注Java之道公眾號
好文章,我在看??
總結
以上是生活随笔為你收集整理的40张图揭秘,「键入网址发生了什么」的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 省市二级联运
- 下一篇: OWIN的理解和实践(三) –Middl