網絡是怎樣鏈接的--客戶端

指南

文章是對整個網絡交互的總結(客戶端部分),本篇會以客戶端爲側重點,比較詳細的講解客戶端發起網絡請求到服務端返回數據這一個交互過程當中客戶端主要作了哪些工做以及經歷了什麼。文內理論知識偏多,不會講得特別深刻,可是看完有一個全面瞭解是足夠的。html

在此以前我先提幾個會在文章內有解釋的問題,能夠先思考一下,題目比較基礎,懂的童鞋能夠在文內看看寫的是否正確,不是太懂的童鞋能夠經過閱讀本文掌握他們,我也是抱着加深本身的記憶來寫這篇文章的:git

  • 一、如何正確分析一個網址URL,如:https://github.com/ChavezChen/CWLateralSlide 和 https://www.baidu.com
  • 二、什麼是DNS服務器,以及它是如何工做的?
  • 三、IP地址知道是用來幹嗎的,那端口號呢?子網掩碼又是個什麼東東?
  • 四、套接字、Socket與socket是什麼東西?這三者之間有什麼關係?
  • 五、鏈接服務器的鏈接是什麼意思?TCP三次握手中常說的SYN,ACK是啥?
  • 六、MAC地址是用來幹嗎的?
  • 七、協議棧的工做內容是什麼?

能夠先嚐試做答一下。固然文內的知識必定不止於回答這幾個問題而已。github

網絡的全貌

首先咱們以web瀏覽器爲例先來大概瞭解一下網絡的全貌(固然移動應用也是相似的):web

  • 一、用戶在瀏覽器輸入一個網址如:www.baidu.com,瀏覽器會按照必定的規則去分析這個網址,而後生成請求消息,瀏覽器會委託搬運數字信息的機制將數據發出去。
  • 二、搬運數字信息機制最早出場的是協議棧(網絡控制軟件),協議棧會將從瀏覽器接收到的消息打包,加上目的地址等控制信息,而後將包交給網卡(負責以太網或無線網絡通訊的硬件)。網卡會將包轉換爲電信號,並經過網線或者無線網信號發送到網絡當中。
  • 三、以最典型的場景爲例,網卡發送的包會通過集線器等設備,到達用來接入互聯網的路由器,路由器後面就是互聯網了。
  • 四、經過路由器進入互聯網內部,互聯網的入口線路稱爲接入網,好比電話線、光線、專線等多種通訊線路統稱爲接入網,接入網鏈接到簽約的網絡運營商並接入,而後進入互聯網的骨幹部分,咱們稱之爲骨幹網,骨幹網有不少運營商和大量路由器,這些路由器相互鏈接,組成一張巨大的網,而咱們的網絡包通過若干個路由器的接力,最終發送到服務器所在的局域網中。
  • 五、到了服務器所在的局域網,這個數據包會遇到防火牆,防火牆會對進入的包進行檢查,接下來網絡包可能還會遇到緩存服務器,若是在緩存內找到須要的數據就直接返回了,沒找到再通過一些機制好比分佈在多臺服務器上的負載均衡器等,以後網絡包就到達了服務器。
  • 六、服務器會將網絡包解包還原爲原始的請求消息,而後交給服務器程序分析請求消息的含義,並按照其中的指示將數據裝入響應消息,而後打包發送回客戶端,響應消息回到客戶端的過程恰好和前面介紹的相反,客戶端拿到數據後會將數據展現到屏幕上。

到這裏,一次網絡的交互也到達了終點。上面有不少詞不理解不要緊,接下來文內都會有講解,而後咱們就開始儘可能往詳細的講每一個環節。面試

每一個環節的詳細介紹

一、生成HTTP請求消息

1.1 第一步工做就是對網址進行解析

因而咱們先來解釋網址究竟是什麼?網址,準確的來講應該叫URL(Uniform Resource Locator,統一資源定位符)或者也能夠理解爲http開頭的那個東西,下圖上一張完整的URL圖: windows

  • 協議:客戶端是具有多種功能的,所以須要一些東西來判斷應該使用哪一種功能來訪問對應的數據,好比訪問訪問http服務器用http協議,而訪問FTP服務器時用ftp協議等等。能夠稱它爲訪問時的協議類型,可是訪問本地的時候用file是不須要使用網絡的,所以也能夠理解爲訪問方法。
  • 用戶名密碼:這些咱們通常省略或者不寫在這,因此先不用管。
  • 服務器域名:咱們能夠理解爲服務器的名稱
  • 端口號:會在後面詳細講解
  • 文件路徑:要訪問的文件的路徑

因此這個URL能夠理解爲:user使用https協議訪問www.baidu.com這個服務器上/dir/目錄下file.html這個文件數組

固然咱們上面說的是一個很是典型的URL,固然也有一些不同的,好比文章開頭的第一個問題,咱們在此多加兩個類型進行分析:瀏覽器

https://github.com/ChavezChen/ 
https://github.com/ 
https://github.com
https://github.com/ChavezChen/CWLateralSlide
複製代碼

首先咱們在前面已經知道了域名以後的「/」後面這部分是表明要訪問的文件路徑緩存

  • 第一種能夠理解爲/ChavezChen/後面原本應該有的文件名被省略了,不過,沒有文件名服務器怎麼知道要訪問哪一個文件呢?其實服務器上會實現設置好文件名被省略時要訪問的默認文件,這個看服務器怎麼設置了,大多數狀況下是index.html或者default.html之類的文件名。
  • 第二種以/結尾,也就是說要訪問目錄層級中最頂層的根目錄,因爲省略了文件名,因此結果就是訪問/index.html這樣的文件了
  • 第三種連/都省略的這種寫法也是很是見到的,它其實和第二種同樣,一樣是訪問根目錄下面的默認文件
  • 第四種比較詭異,因爲末尾沒有/,因此CWLateralSlide應該理解爲文件名纔對,但實際上咱們不該該老是將CWLateralSlide按照文件名來處理,通常來講會按照如下慣例進行處理:若是存在CWLateralSlide文件,則直接做爲文件名來處理,若是存在名爲CWLateralSlide目錄,則將CWLateralSlide做爲目錄處理。

1.2 使用HTTP協議訪問服務器

在此以前,咱們先來說一講HTTP協議究竟是怎麼回事:首先,客戶端會向服務器發送請求消息,請求消息中包含了「對什麼」和「進行怎樣的操做」兩部分,其中至關於「對什麼」的部分稱爲URI(Uniform Resource Identifer,統一資源標誌符),「進行怎樣的操做」的部分稱爲方法,方法表示須要讓服務器完成怎樣的工做,也就是咱們常見的「GET、POST、PUT、DELETE」等......咱們拿幾個常見的來舉例說明一下:bash

  • GET:獲取URI指定的信息,若是URI指定的是文件,返回文件。若是URI指定的是程序,返回程序的輸出數據。好比咱們在請求消息中寫上GET方法,而後在URI中寫上存放網頁數據的文件名「/dirl/file.html」,就表示咱們要獲取「file.html」文件中的數據,服務器會讀取該文件中的數據放到響應中返回。
  • POST:客戶端向服務器提交數據。使用POST方法時,URI會指向服務器中運行的一個應用程序的文件名,如:「index.cgi」等。而後服務器會將請求消息中的數據發給指定的程序,最後服務器從指定程序中接收輸出結果返回到客戶端。

其餘的一些方法咱們就不一一舉例了,面試時候GET和POST的區別在網上有很是多的答案,想了解的能夠自行搜索如下。

對URL進行解析以後,會根據解析出來的服務器和文件名信息生成HTTP請求消息,HTTP請求消息在格式上是有嚴格規定的,因此客戶端都會按照規定的格式來生成請求消息:

一、<方法><空格><URI><空格><HTTP版本>   
二、<頭字段名>:<頭字段值>   頭字段名如:Content-Type,表示消息體的數據類型.等
三、...
四、...
五、<空行>   
六、<消息體>

第1行爲請求行,經過這一行大體瞭解請求內容。
2-5行成爲消息頭,每行包含一個頭字段,用於表示請求消息的附加信息。消息頭的行數一直
延伸到空行爲止,好比iOS中能夠經過AFN的requestSerializer來設置頭信息
第6行消息體,包含客戶端向服務器發送的數據。
複製代碼

發送請求後會收到響應,這個後面在說,簡單解釋一下響應的各個狀態碼的含義:

1xx         告知請求的處理進度狀況。臨時。
2xx         成功
3xx         表示須要進一步操做,重定向
4xx         客戶端/請求錯誤
5xx         服務器錯誤
複製代碼

1.3 向DNS服務器查詢web服務器的IP地址

在消息發送以前,咱們還有一個工做須要完成,就是查詢網址中服務器域名對應的IP地址,在委託操做系統發送消息時,必須提供的是IP地址而不是域名。

有童鞋確定會有疑問,既然還要去查詢ip地址那我乾脆在網址中不寫域名寫IP地址不就行了?實際上這樣也能正常工做,可是就像你容易記名字難記電話號碼同樣,要記住一串IP地址也是很是困難的,所以相對IP地址來講,網址中使用域名更好。

那有有童鞋要發問了,那乾脆不用IP地址直接用域名來確認不就行了麼?IP地址的長度是32bit,也就是4字節,而域名最短的也要幾十個字節,換句話說,使用IP地址只要處理4字節的數字,而域名則要處理幾十到255個字符,這增長了路由器的負擔,傳送數據也會花費更長時間。

1.3.1 IP地址的基本知識

在網絡中,全部的設備都會分配一個地址,這個地址至關於現實中的「xx號xx室」,其中「號」是分配給整個子網的,稱爲網絡號,「室」是分配給子網中的計算機的,稱爲主機號,這個地址總體稱爲IP地址。什麼是子網呢?子網能夠理解爲用集線器鏈接起來的幾臺計算機,好比,一個房間有幾臺計算器,他們都鏈接到一個集線器上,那麼他們就造成一個子網,而後多個子網在經過路由器鏈接起來就造成一個網絡。而後你又想到上現實中,啥?沒見過集線器這種東西,家裏都是多臺電腦用網線連到一個路由器上的啊!由於如今不少家用路由器中已經內置了集線器功能,因此你也能夠把一個路由器鏈接的多臺電腦,統稱爲一個子網。

經過IP地址咱們能夠判斷訪問對象服務器的位置,從而將消息發送到服務器。消息傳送的具體過程後面再講,不過咱們先簡單的瞭解一下:發送者發出的消息首先通過子網的集線器轉發到發送者最近的路由器(家用的由於集線器和路由器二合一了,能夠理解爲從網線到了路由器上面),接下來路由器會根據消息的目的地判斷下一個路由器的位置,將消息發到下一個路由器,不斷重複以後消息就到了服務器最近的路由器,最終消息就被傳送到了目的地。整個過程與現實中快遞相似,通過一個一箇中轉站,到達離你最近的中轉站,而後送到你房間。

而後咱們來看一看實際的IP地址,實際的IP地址(IPV4)是一串32比特的數字,按照8比特(一個字節)爲一組分紅4組,分別用十進制表示,而後再用圓點隔開。可是咱們要明確知道目的地,必須先知道網絡號,肯定哪個子網,而後再經過主機號肯定哪一臺計算機,好比一個網吧有100臺計算機,首先咱們得經過網絡號肯定究竟是哪一個網吧,而後再經過主機號肯定是哪臺機器,可是IP地址有4段,到底哪幾段表明網絡號,哪幾段表明主機號呢

因而咱們須要另一些附加信息來確認IP地址的哪一個部分是網絡號,哪一個部分是主機號,這個附加信息就是 子網掩碼 子網掩碼的格式和IP同樣,其中每段所有爲1的爲網絡號,爲0的表示主機號,子網掩碼的每一段要不所有爲1要不所有爲0,也就是十進制的255和0,用十進制的來表示子網掩碼和IP地址的關係:

IP地址  :      10 . 1.  2.3            10 .  1.  2.3
子網掩碼:      255.255.255.0            255.255.  0.0
第一個IP地址的網絡號爲10.1.2 主機號爲3
第二個IP地址的網絡號爲12.1 主機號爲2.3    
總結:子網掩碼的255對應的IP地址部分就是網絡號,0對應的就是主機號
固然有兩種特殊狀況:IP地址的主機號爲255表示向子網上的全部設備發送包,即「廣播」。
IP地址的主機號爲0表明整個子網,其餘的狀況一律視爲某個子網的某一臺主機。
複製代碼

1.3.2 經過解析器向DNS發出查詢

查詢IP地址的方法很是簡單,只要詢問最近的DNS服務器「www.baidu.com」的IP地址是什麼就能夠了,DNS服務器會回答說「該域名的IP地址爲xxx.xxx.xxx.xxx」。-----DNS:Domain Name System,域名服務系統

那客戶端是經過什麼向DNS查詢IP的呢?咱們將負責域名解析這一操做的叫作解析器,解析器其實是一段程序,它包含在操做系統的Socket庫中。

咱們先來簡單瞭解如下Socket庫。首先,庫就是一堆通用程序組件的集合,iOS中的UIKIT也是一個庫。Socket庫也是一種庫,其中包含的程序可讓其餘的應用程序調用操做系統的網絡功能(還包含不少發送和接收數據的程序組件)。就像iOS使用AFN框架就能夠發起網絡請求相似的。

解析器的用法很是簡單,只要寫上解析器的程序名稱(OC中稱爲一個方法或者一個函數)「gethostbyname」而後參數帶上對應的域名就能夠了,好比:

IP = gethostbyname("www.baidu.com"); 
複製代碼

順帶一提,向DNS服務器發送查詢消息是,咱們固然也須要知道DNS服務器的IP地址。這個地址是咱們事先就設置好的,windows電腦在IPV4\IPV6的屬性裏面設置,mac電腦在網絡的高級裏面設置。以下圖:

1.3.3 DNS服務器的工做原理

解析器向DNS服務器發送的查詢消息包含如下3種信息:

  • 域名:web服務器或郵件服務器的名稱
  • Class: 用來識別網絡的,不過現在除了互聯網沒有其餘網絡了。所以這個值永遠是表明互聯網的IN
  • 記錄類型:表示域名對應何種類型。例如當類型爲A時,表示域名對應的是IP地址;當類型爲MX時,表示域名對應的是郵件服務器。

DNS服務器上事先就保存了前面這三種信息對應的記錄數據,當收到客戶端的查詢消息時,DNS服務器會從已有的記錄種查找域名、Class、類型所有匹配的記錄並向客戶端返回響應消息(IP地址)。

看起來挺簡單的,可是實際上還有互聯網中存在不可勝數的域名,將這些信息所有保存在一臺DNS服務器中是不可能的,所以必定會出如今某臺DNS查詢不到信息的狀況,因而咱們來看一看此時DNS服務器是如何工做的

答案很簡單,就是將信息分佈到多臺DNS服務器中,而後這些DNS相互配合接力,從而查詢到須要的信息。首先DNS服務器中的全部信息都是按照域名以分層次的結構來保存的,可能不是很好理解,因而咱們先來簡單介紹域名的層次,域名的層次越靠近右邊層次越高,如:www.baidu.com,這個若是按照公司的的組織結構來講,大概就是com公司的baidu部門的www。以域來講頂層是com域,而後是baidu域,而後是www。com還有上一層域,咱們叫根域,根域通常省略,用.號代替如:「www.baidu.com.」。

因而根據域名的層次,咱們來分析DNS服務器的層次。按照一個原則:負責管理下級域的DNS服務器的IP地址保存在它的上級DNS服務器中。也就是說,負責管理www.baidu.com這個域的DNS服務器的IP地址保存在管理baidu.com這個DNS服務器中,而管理baidu.com域的DNS服務器IP地址保存在管理com域的服務器中。注意⚠️這裏的IP地址是指DNS服務器的IP地址,不是域名的IP地址。這樣咱們就能夠經過上級DNS查詢下級DNS的IP地址,也就能夠向下級DNS服務器發送查詢請求了。還有一項工做就是根域的DNS服務器信息保存在互聯網中全部DNS服務器中,這樣一來任何DNS服務器均可以查詢根域的DNS服務器。

客戶端會先訪問離得最近的DNS服務器(也就是客戶端在TCP/IP設置中填寫的DNS服務器IP地址),若是這臺DNS服務器中沒有存放咱們須要的域名的IP地址,最近的這臺DNS服務器會直接去根域的DNS查找,根域中也沒有則向下查詢,例如www.baidu.com,先向跟域查詢,沒找到則向com的DNS服務器查詢,而後是baidu.com,一直向下知道查詢到了爲止。

可是每次都查詢會很麻煩且耗時,因而DNS服務器有一個緩存功能,能夠記住每次查詢的域名,緩存中的信息爲了防止有改變而查詢到老的數據,會設置一個有效期,過時則會刪除。固然客戶端也能夠在hosts配置域名對應的IP地址

因而經過DNS服務器查詢IP地址的步驟爲:首先在客戶端的hosts配置中去取域名對應的IP地址,沒取到則向最近的DNS服務器查詢IP地址,查詢時會先從DNS服務器緩存中取,沒取到再到DNS服務器的記錄表中查找,若是最近的DNS服務器沒有找到,則直接到根域的DNS服務器查找,沒找到再根據前面域的層級所述一級一級向下查找。

二、數據的傳輸

經過調用gethostbyname的程序組件(也就是解析器)去DNS獲取IP地址以後,則進入了數據的收發(也就是傳輸)階段,數據的收發大體總結爲如下4個階段:

  • 一、建立套接字(建立套接字階段)
  • 二、將管道鏈接到服務器端的套接字上(鏈接階段)
  • 三、收發數據(通訊階段)
  • 四、斷開管道並刪除套接字(斷開階段)

用僞代碼的形式來表述一下對應階段的情形:

發送階段
...
<內存地址> = gethostbyname("www.baidu.com"); // 向DNS查詢IP地址
...
<描述符> = socket(<使用IPv4>, <流模式>, ...); // 一、建立套接字
...
connect(<描述符>, <服務器的IP地址以及端口號>, ...) // 二、鏈接
...
write(<描述符>, <發送數據>, <發送的數據長度>); // 三、發送
...

接收階段
<接收數據長度> = read(<描述符>, <接收緩衝區>, ...); // 三、接收
...
close(<描述符>); // 四、斷開
複製代碼

2.一、建立套接字

2.1.1 協議棧

首先了解一下協議棧是什麼!協議棧與瀏覽器不一樣,它是屬於操做系統的部分,工做內容也是從表面上看不到的。先分析一下協議棧的結構:

最上層是網絡應用程序,也就是瀏覽器之類的,接下來是Socket庫,Socket庫中包含解析器,也就是用來向DNS查詢IP地址的,在上面已經說過了。

再下來是操做系統內部,其中包括協議棧:

  • 協議棧上半部分分紅兩塊,分別是負責用TCP協議收發數據部分以及負責用UDP協議收發數據部分,關於TCP和UDP後面再詳細講解。
  • 下半部分是用IP協議控制網絡包收發操做部分,在傳送數據時,數據會被分紅一個一個網絡包,而將網絡包發送給通訊對象的操做就是IP協議負責的。此外IP中還包含ICMP協議和ARP協議。ICMP用於告知網絡包傳送過程當中產生的錯誤以及各類控制信息,ARP用於根據IP地址查詢對應的以太網MAC地址(後面會絕體講解)。

再下面就是網卡驅動程序負責控制網卡硬件,最下面的網卡則負責完成實際的收發操做,也就是對網線中的信號執行發送和接收操做。

2.1.2 套接字

在協議棧內部有一塊用於存放控制信息的內存空間,這裏記錄了用於控制通訊操做的控制信息,例如通訊對象的IP地址、端口號、通訊操做的進行狀態等。原本套接字就只是一個概念,並不存在實體,若是必定要賦予它一個實體,那麼存放這些控制信息的內存空間就是套接字的實體。

協議棧在執行操做時須要參閱這些控制信息。例如,在發送數據時,須要看一看套接字中的通訊對象IP地址和端口號。或者在發送數據以後,在等待響應消息時,不能一直等,因而一段時間後須要從新發送丟失的數據。爲此,套接字中必需要記錄是否已經收發響應以及發送數據後通過了多長時間。

上面講的太抽象了,可是咱們知道套接字就是一些控制信息,因此接下來來看看實際的套接字,在終端使用netstat命令顯示套接字的內容

  • Proto:協議類型,tcp4與tcp6區別:tcp6支持操做IPV6/IPV4 而tcp4只能操做IPV4
  • Recv-Q:表示程序總共還有多少字節的數據沒有從內存空間的套接字緩存拷貝到用戶空間
  • Send-Q:對方沒有確認收到的字節數。Q表示Queue隊列
  • Local Address:本地IP地址和端口號
  • Foreign Address:遠程端的IP地址和端口號
  • state:通訊狀態,ESTABLISHEND表示鏈接完成正在進行數據通訊

總結:套接字的實體就是通訊控制信息,協議棧須要根據這些信息判斷下一步行動,這就是套接字的做用。

2.1.3 調用socket時的操做

首先,應用程序會調用Socket庫中的socket程序申請建立套接字,協議棧根據根據申請執行建立套接字操做。建立套接字時,首先分配一個套接字所需的內存空間,而後向寫入初始狀態。

接下來,須要將表示這個套接字的描述符告知應用程序。描述符至關於用來區分協議棧中多個套接字的號碼牌,也能夠理解爲某個套接字的編號,在建立套接字時會返回這個描述符。也能夠看一下第二節開頭的僞代碼找到描述符。

收到描述符後,應用程序在向協議棧進行收發數據委託時就要提供這個描述符,到這裏,套接字就已經建立完成了

2.二、鏈接服務器

首先咱們要明白「鏈接」是什麼意思,咱們不可能說拿個網線連起來就是鏈接,由於網線一直都是鏈接的。那麼「鏈接」究竟是什麼意思呢?鏈接實際上就是通訊雙方交換控制信息,在套接字中記錄這些必要信息並準備數據收發的一連串操做。是否是感受很抽象?接下來詳細說說「鏈接」究竟是什麼意思!

套接字剛建立完成的時候,裏面沒有存聽任何數據,也不知道通訊對象是誰,這時候就算應用程序要發消息,協議棧也不知道發給誰,所以咱們須要把IP地址和端口號等信息告訴協議棧,這是鏈接操做之一。

在服務器端也會建立套接字,服務器程序通常會在系統啓動時就建立套接字,並等待客戶端鏈接。可是服務器也不知道通訊對象是誰,因而咱們須要讓客戶端向服務器告知必要的信息,好比客戶端的IP地址和端口號。可見客戶端向服務器傳達通訊的請求也是鏈接的操做之一。

此外,當執行數據收發操做時,咱們還須要一塊用來臨時存放要收發數據的緩衝區,它也是在鏈接操做的過程當中分配的。這些就是「鏈接」這個詞代碼的具體含義。

或者你也能夠理解爲:鏈接就是客戶端和服務器確認好通訊雙方身份的過程,其中IP地址確認通訊雙方的地址,端口號確認IP地址對應的計算機中的哪個套接字

2.2.1 控制信息補充

以前咱們所說的控制信息,大概能夠分爲兩類:

第一類是客戶端和服務器聯絡時交換的控制信息。這些信息不只鏈接時須要,包括數據收發和斷開鏈接在內,整個通訊過程都須要。那麼這些控制信息會經過什麼樣的形式傳輸呢?它會放在TCP頭部,一個數據包的表現形式大概是這樣:

在鏈接過程當中,因爲數據收發還沒開始,上圖中的最後「數據」部分是沒有數據的,所以只包含了前面兩部分的控制信息。上面這個圖咱們當前只關心TCP頭部,那麼TCP頭部究竟定義了哪些控制信息呢?

字段名稱 長度(比特) 含義
發送方端口號 16 發送網絡包的程序的端口號
接收方的端口號 16 網絡包的接收方程序端口號
序號 32 發送方告知接收方該網絡發送的數據至關於全部發送數據的第幾個字節
ACK號 32 接收方告知發送方接收方已經收到全部數據的第幾個字節。ACK是acknowledge的縮寫
數據偏移量 4 表示數據部分的起始位置,也能夠認爲表示頭部的長度
保留 6 該字段保留,如今未使用
控制位 6 該字段中的每一個比特分別表示如下通訊控制含義:ACK:表示接收數據序號(ACK號)字段有效,通常表示數據已經被接收方收到;SYN:發送方和接收方互相確認的序號,表示鏈接操做;FIN:表示斷開鏈接;還有3個不經常使用先省略
窗口 16 接收方告知發送方窗口大小(即無須等待確承認一塊兒發送的數據量)比較抽象沒事,後面會講解
校驗和 16 用來檢查是否出錯
緊急指針 16 表示應緊急處理的數據位置
可選字段 可變長度 能夠添加的字段,但除了鏈接通常不多使用

以上的控制信息咱們見得比較多且不多解釋的應該就是控制位,由於咱們常常在TCP3次握手中看到,如今你知道他們是什麼意思了麼?

第二類就是保存在套接字中,用來控制協議棧操做的信息。以前已經講過了這些信息保存在協議棧中的套接字內存空間內。應用程序傳遞來的信息以及從通訊對象接收到的信息都會保存在這裏,還有收發操做的執行狀態等信息也會保存在這裏,協議棧會根據這些信息來執行每一步操做。建立套接字時建立的緩衝區就是臨時保存數據的。很差理解能夠再回頭再看看咱們輸入netstat命令時輸出的結果。

2.2.2 鏈接操做的實際過程(三次握手)

這個過程是從應用程序調用Socket庫中的connect開始。

connect(<描述符>, <服務器的IP地址以及端口號>, ...)
複製代碼

首先,客戶端先建立一個包含表示開始數據收發操做的控制信息頭部,如上的TCP頭部表格所示,包含了不少字段。咱們首先關注端口號,經過端口號,客戶端的套接字就準確找到服務端的套接字,也就是搞清了哪兩個套接字進行通訊。而後咱們將頭部中控制位的SYN設置爲1,能夠認爲這樣就表明鏈接。此外還須要設置適當的序號(假設設置爲X)和端口號,當TCP頭部建立好後,TCP模塊會將信息傳遞給IP模塊委託它進行發送(由於這是鏈接的過程,數據塊是沒有實際數據的)。

而後包經過網絡到達服務器,服務器的IP模塊會將包傳給服務器的TCP模塊,而後TCP模塊經過收到的包的TCP頭部的信息找到端口號對應的套接字,而後套接字會寫入相應的信息而且將狀態設置爲正在鏈接。而後服務器的TCP模塊會返回響應消息,響應消息和客戶端同樣會將控制位的SYN設置爲1,同時會將控制位的ACK也設置爲1,並將頭部字段的ACK號設置爲X+1,而後再將頭部的序號設置爲Y。ACK號表示已經收到客戶端發過來的數據包。

而後,網絡包會返回到客戶端,經過客戶端的IP模塊到達TCP模塊,並經過TCP頭部信息確認鏈接操做是否成功。若是控制位的SYN爲1則表示鏈接成功,這時會向套接字中寫入服務器的IP地址、端口號等信息,同時還會將狀態改成鏈接完畢。到這裏,客戶端的操做就已經完成了。

但其實還剩下最後一個步驟,剛纔服務器響應時控制位的ACK比特設置爲1並將ACK號返回給客戶端確認包已經收到,相應的客戶端也須要將控制位的ACK比特設置爲1並將ACK號設置爲Y+1發回服務器,告訴剛剛服務器發的相應包已經收到,到這裏鏈接操做纔算完成。ACK號與控制位的ACK比特是兩個東西,詳情看如下上面TCP頭部表格,關於ACK號和序號不是太理解的不要緊,在收發數據的時候會有詳細的解釋。

2.三、收發數據

鏈接以後進入數據收發操做,數據收發操做是從應用程序調用Socket庫中的write將要發送的數據交給協議棧開始的。

write(<描述符>, <發送數據>, <發送的數據長度>);
複製代碼

首先,協議棧並不關心應用程序傳來的數據是什麼內容。其次,協議棧並非一收到數據就立刻發送出去,而是會將數據存放在內部的發送緩衝區中,並等待應用程序的下一段數據。由於一次性將多少數據委託給協議棧是應用程序自行決定的,協議棧並不控制這一行爲,在這種狀況下,若是立刻發送出去就可能會發送大量的小包,致使網絡效率降低,所以須要在數據積累到必定量再發送,至於要積累多少數據再發送,不一樣種類的版本和操做系統會有所不一樣,可是能根據如下兩要素來判斷:

  • 第一判斷要素是網絡包能容納的最大長度,協議棧裏由MTU參數來表示這一最大長度,MTU是總的長度,由於數據包的數據的長度須要由MTU減去頭部的長度,這一長度叫MSS,當協議棧從應用程序收到數據長度達到或者超過MSS時則會將數據發送出去。
  • 另外一判斷要素是時間,當應用程序發送的數據不是不少事,若是一直等數據可能會形成發送延遲,爲此,協議棧內部有一個計時器,通過必定時間後無論數據長度如何都會把網絡包發出去,這個時間很短,以毫秒計。

進行發送操做時會綜合考慮以上兩個要素以達到平衡。固然應用程序發送數據時也能夠指定一些選項,如:直接發送不等待。

對較大的數據進行拆分

若是一個網絡包的數據很是大,發送緩衝區的數據會被以MSS爲長度進行拆分,拆分出來的每塊數據會被單獨放進單獨的網絡包內。

使用ACK號確認網絡包已收到

網絡包發送出去以後,須要進行確認操做。---這個ACK和控制位的ACK是兩個東西。

首先TCP模塊在拆分數據時會先算好每一塊數據至關於從頭開始的第幾個字節,接下來發送這個數據時,將算好的字節寫在TCP頭部的序號中,而後,發送數據的長度也要告知對方,不過這個在接收方能夠本身算出來,使用接收到的數據的長度減去頭的長度就能獲得數據的長度。有了這兩個數值咱們就能夠知道發送的數據是從整個數據的第幾個字節開始,長度是多少了。(序號的初始值是在鏈接的時候產生一個隨機數告知對方的)

經過這些信息,接收方還能檢測網絡包有沒有遺落。例如,上一次接收到第1000字節,那麼接下來若是收到序號爲1001的包,說明中間沒有遺落;但若是收到的包序號爲2000,則說明遺漏了1000個字節的數據。若是確認沒有遺漏,接收方會將目前爲止接收到的數據長度加起來,將這個數值寫入TCP頭部的ACK號中發送給對方,發送方就能確認接收方到底收到多少數據。例如:發送方發送的包序號爲1,長度爲1000,當接收方接收到這個數據以後,會返回一個ACK號爲10001的包給發送方,發送方收到這個包就知道我以前發的哪一個包你已經確認收到了。

除了在客戶端發送數據給服務端以外,服務端也會發送數據給客戶端,所以在實際發送中會增長一種與以前相反的情形,服務端發送數據時也須要先計算一個序號,而後將序號的數據一塊兒發送給客戶端,客戶端收到後計算ACK號返回給服務器。

TCP採用ACK號這種方式確認對方是否收到了數據,在獲得對方的確認以前,發送過的包都會保存在發送緩衝區。若是對方沒有返回某些包對應的ACK號,那麼就從新發送這些包。

當客戶端在委託協議棧發送請求消息以後,客戶端會調用Socket庫的read程序來接收相應消息。

<接收數據長度> = read(<描述符>, <接收緩衝區>, ...);
複製代碼

而後會經過read轉移到協議棧,而後協議棧會檢查收到的數據和TCP頭部的內容判斷數據是否有丟失,若是沒問題則返回ACK號給服務區確認收到數據,同時會把數據保存在接收緩衝區中,並將數據按照序號的順序鏈接起來還原出原始數據,最後將數據返回給應用程序。

2.四、從服務器斷開並刪除套接字

在返回相應消息以後,客戶端還能夠繼續發起下一個請求,這樣能夠省去再次鏈接的操做,若是接下來沒有請求發送,客戶端會調用Socket庫中的close程序發起斷開操做,斷開的操做順序以下:

  • 客戶端將TCP頭部中控制位的FIN設置爲1,發送出去
  • 服務器將TCP頭部的ACK號改爲對應數值發給給客戶端,確認收到客戶端的斷開請求
  • 服務器再發一個將TCP頭的控制位FIN設置爲1的包給客戶端
  • 客戶端返回ACK號確認收到

這就是斷開連接所謂的四次揮手。斷開以後就會刪除套接字,通常來講會等待幾分鐘再刪除套接字,爲何要等待幾分鐘呢?留給你們本身思考一下🤔。

2.五、IP模塊的工做內容

在前面講到TCP模塊在執行鏈接、收發、斷開等各階段時,都須要委託IP模塊將數據封包發送給通訊對象。接下來咱們要看看IP模塊是如何將包發送給對方的

以前在2.2.1已經說過,包是由頭部和數據兩部分構成,頭部包含了目的地址等控制信息,在前面咱們講了TCP頭部的內容,從2.2.1的圖上能夠看到在TCP頭的前面還有一個IP頭部,在那張圖裏面將IP頭部表述爲以太網和IP的控制信息,實際上他們並非一個頭而是兩個不一樣的頭,分別爲MAC頭部與IP頭部。而IP模塊則負責給TCP委託過來的數據包添加MAC頭以及IP頭

  • MAC頭部:以太網用的頭部,包含MAC地址
  • IP頭部:IP用的頭部,包含IP地址

因而實際上一個數據包的正確表達形式是這樣的:

2.5.一、生成IP頭部

IP模塊接受TCP模塊的委託負責包的收發工做,它會生成IP頭部並附加在TCP頭部前面。下表表示IP頭部的字段

字段名稱 長度(比特) 含義
版本號 4 IP協議版本號,4或6
頭部長度 4 IP頭部長度。可選字段可致使長度變化,所以這裏須要指定長度
服務類型(ToS) 8 表示包傳輸的優先級
總長度 16 表示IP消息的總長度
ID號 16 用於識別包的編號,通常爲包的序列號。若是一個包被IP分片,則全部分片都擁有相同ID
標識(Flag) 3 該字段有3各比特,其中2各比特有效,分別表明是否容許分片以及當前包是否爲分片包
分片偏移量 13 表示當前包的內容爲整個IP消息的第幾個字節開始的內容
生存時間(TTL) 8 表示包生存的時間,這是爲了不網絡出現迴環時一個包永遠在網絡中打轉。每通過一個路由器,這個值就會減1,減到0時這個包就會被丟棄
協議號 8 協議號表示的協議類型:TCP:06;UDP:11;ICMP:01
頭部校驗和 16 用於檢查錯誤,如今已不使用
發送方IP地址 32 網絡包發送方的IP地址
接收方IP地址 32 網絡包接收方的IP地址
可選字段 可變長度 能夠添加的字段,通常不多使用

表中最重要的就是IP地址,接收方的IP表示這個包應該發到哪裏去。這個地址是應用程序在執行鏈接操做時傳給TCP模塊而後再由TCP模塊傳給IP模塊的。發送方的IP地址填寫使用網卡中的IP地址

2.5.二、生成以太網用的MAC頭部

生成IP頭部以後,接下來IP模塊還須要在IP頭部前加上MAC頭部。MAC頭部字段:

字段名稱 長度(比特) 含義
接收方MAC地址 48 網絡包接收方的MAC地址,在局域網中使用這一地址來傳輸網絡包
發送方的MAC地址 48 網絡包發送方的MAC地址,接收方經過它來判斷是誰發送了這個包
以太類型 16 使用的協議類型。通常在TCP/Ip通訊中只使用0800(IP協議)和0806(ARP協議--前面有講這個哦,在2.1.1)

IP頭部的IP地址能夠用於判斷包發送到哪裏。那MAC地址是用來幹嗎的呢?

MAC地址是在以太網中使用的,以太網在判斷目的地時和TCP/IP的方式不一樣,所以必須採用相匹配的方式才能在以太網中將包發送到目的地,MAC頭部就是幹這個用的。那什麼是以太網呢?

以太網是一種爲多臺計算機可以彼此自由和廉價地相互通訊而設計的通訊技術,或者能夠說以太網是互聯網的一個子集,以太網能夠理解爲是一種局域網,只能鏈接附近的設備,這種網絡的本質實際上就是一根網線。生活化一點,以太網就是把你家的電腦,筆記本鏈接到貓上,而後再經過貓鏈接到因特網上去,這樣你才能和國外的朋友喬布斯聊天。所以,你家的電腦,筆記本和貓就組成了一個以太網。因此你知道爲何接收方的IP地址不能在以太網使用了嗎?由於這個IP地址是針對整個互聯網的,而以太網只是互聯網的一部分,因此這個IP地址在以太網不適用

當一臺計算機發送信號時,信號就會經過網線流過整個以太網,最終到達全部設備。不過咱們沒法判斷信號是發給誰的,所以要在信號開頭加上接受者的地址,也就是MAC頭部的接收方MAC地址。好比計算機發一個請求,這個請求首先要到達路由器,那麼到達哪一個路由器呢?因而MAC地址就是告訴網卡消息應該到達這個路由器。相對的MAC頭部發送方的MAC地址是從哪來的?這個MAC地址是在網卡生產時寫入ROM裏的,只要讀取這個值就能夠了。

那麼問題又來了,接收方的MAC地址咱們又是怎麼知道的?

這裏就須要使用到以前所說的ARP地址解析協議。在以太網中,有一種叫作廣播的方法,能夠把包發給同一以太網中的全部設備。ARP就是用廣播的方式實現的。ARP發送一個廣播問「XX這個IP地址是誰的?請把MAC地址告訴我」,而後就有設備會回答「這個IP地址是個人,個人MAC地址是XXX」,因而你就把這個XXX寫到MAC頭部發送!固然爲了節省每次都查詢的時間,ARP也有相應的緩存,首先會從緩存中取,爲了防止地址變換,緩存通常會在幾分鐘後刪除。

那麼問題又來了,ARP發廣播時問「XX這個IP地址是誰的」中的IP地址是哪來的?這個IP地址必定不是咱們發送消息時填的服務器的IP地址吧,由於ARP查詢的只是子網內的目的地MAC地址啊。

首先,發送方將包的目的地也就是要訪問的服務器的IP地址寫入IP頭,協議棧IP模塊有一張IP協議的表,在這個表中找到相匹配的條目的IP地址就能夠了,而後經過這個IP地址經過ARP查詢路由器的MAC地址寫入MAC頭部,網絡包在傳輸過程當中會通過集線器,集線器是根據以太網協議工做的設備。爲了判斷包接下來應該向什麼地方傳輸,集線器裏有一張表(用於以太網協議的表),能夠根據MAC頭部記錄的目的地查找對應的傳輸方向,將包發送到路由器,而後路由器中又有一張IP協議的表(路由表),可根據這張表以及IP頭部中記錄的目的地信息查找出接下來應該發往哪一個路由器(IP地址),接下來又經過ARP查詢MAC地址.....一直進行下去....到達目的地

2.六、網卡的工做內容

IP模塊生成的網絡包只是存放在內存中的一串數組信息,沒有辦法直接發送給對方,所以咱們須要將數字信息轉換爲電信號或光信號才能在網線上傳輸,這纔是真正的數據收發過程,負責這一執行操做的是網卡,這個操做須要網卡驅動程序和網卡一塊兒完成。

網卡驅動從IP模塊獲取包以後,會將其複製到網卡內的緩衝區,而後會傳給網卡內部的MAC模塊,MAC模塊會在包的開頭加上報頭和起始幀分界符,在末尾加上用於檢測錯誤的FCS(幀校驗序列),網卡發出去的包就是這個形式:

網卡中的MAC模塊從報頭開始將數字信息按每一個比特轉換爲電信號,而後又信號收發模塊發送出去,在這裏,將數字信息轉換爲電信號的速率就是網絡的傳輸速率。(網卡的細節並無講的很詳細,瞭解大概就差很少了吧)

2.七、接收數據小結

發送包以後,咱們假設web服務器返回了一個網絡包,到達了網卡,網卡首先會根據報頭和起始幀分界符開始提取網絡包的數據,並使用末尾的FCS來檢查包傳輸過程當中是否有數據錯誤。而後根據MAC頭部的以太類型來交給對應的協議棧,好比這裏的以太類型會是0800(IP協議,這個在2.5.2MAC頭的表裏面咱們有說過的)。

接下來IP模塊會進行工做,首先是檢查IP頭部確認格式是否正確,格式沒問題下一步就是查看接收方的IP地址是不是本身的地址,若是不是本身的地址則會經過ICMP消息將錯誤告知發送方(ICMP在文章的2.1.1有簡單說明)。

若是IP地址正確,這個包將會被接收下來,這時候還須要完成另外一項工做。IP協議有一個叫分片的功能(2.5.1的表格內的標識字段)。簡單來講,網線和局域網只能傳輸小包,所以須要將大的包切分紅多個小包。若是包是通過分片的,IP模塊會將其暫存在內部的內存空間中,等待IP頭部中具備相同ID所謂包所有到達(藉助分片偏移量字段),而後IP模塊會將他們還原成原始的包,這個操做叫分片重組

到這裏IP模塊工做就結束了,接下來包會交給TCP模塊。TCP會根據IP頭部中的接收方和發送方的IP地址,以及TCP頭部中的接收方和發送方的端口號來查找對應的套接字(2.1.2有說明)。找到對應的套接字後,就能夠根據套接字中記錄的通訊狀態,執行相應的操做了。例如,若是包的內容是應用程序數據,則設置ACK號返回確認接收的包(2.3有說明),並將數據放入緩衝區,等待應用程序來讀取;若是是創建鏈接或者斷開鏈接的控制包,則返回相應的響應控制包,並告知應用程序創建和斷開鏈接(2.2.2有說明)。

而後響應包就到達了應用程序,這個過程也就告一段落了

2.八、UDP協議的收發操做

大多數的應用程序都相以前介紹的同樣使用TCP協議來收發數據,固然也有例外,向NDS服務器查詢IP地址的時候就是使用的UDP協議。下面簡單介紹一下UDP協議

先來理解如下爲何TCP要設計得如此複雜?由於咱們須要將數據高效且可靠的發送給對方,爲了實現可靠性,咱們就須要確認對方是否收到咱們發送的數據,若是沒有收到還須要再發一遍。爲了實現高效的傳輸,咱們要避免重發已經送達的包,而是隻重發那些出錯的或者未送達的包。TCP之因此複雜就是爲了實現這一點。

UDP沒有TCP的接收確認、窗口等機制,所以在收發數據以前也不須要交換控制信息,也就是說不須要創建和斷開鏈接的步驟,只要在從應用程序獲取的數據前面加上UDP頭部,而後交給IP進行發送就能夠了。接收也很簡單,只要根據IP頭部中接收方和發送方的IP地址,以及UDP頭部中的端口號找到對應的套接字並將數據交給相應的應用程序便可,除此以外UDP協議沒有其餘功能了,遇到錯誤和丟包也一律無論。如下是UDP頭部的控制信息:

字段名稱 長度(比特) 含義
發送方端口號 16 網絡包發送的端口號
接收方端口號 16 網絡包接收的端口號
數據長度 16 UDP頭部後面數據的長度
校驗和 16 用於校驗錯誤

另一個常見的UDP使用場景就是發送音頻和視頻數據的時候。音視頻數據必須在規定的時間送達,一旦晚了,就會錯過播放的時機,就會形成聲音和圖像卡頓。一旦錯過了播放時機,使用TCP重發包也是沒用的,由於已經卡頓了,這是沒法挽回的。此外,音視頻數據中缺乏了某些包並不會產生嚴重問題2,只會產生一些失幀或者卡頓而已,通常均可以接受。因此使用UDP發送數據效率會更高。

結語

至此,咱們探索網絡是怎樣鏈接的客戶端部分說得差很少了,可能後面有時間會再總結一下網絡包如何通過集線器、交換機、路由器等設備最終到達互聯網,也可能會總結一下服務器端的接收狀況(做爲客戶端開發也能夠適當的瞭解一下)。本篇文章的內容,絕大部分來自同名書本《網絡是怎樣鏈接的》,想更加詳細瞭解能夠閱讀此書。

附上文章前面幾個問題的解答:

一、如何正確分析一個URL: 文內1.1
二、DNS如何工做的:文內1.3
三、IP地址、子網掩碼:文內1.3.1
四、套接字、端口號:文內2.2; 文內2.1.2 
五、Socket、socket:文內1.3.2 ;文內2.1.3
六、MAC地址:文內2.5.2
七、協議棧:文內2.1.1
複製代碼

再留幾個小問題給你們思考(書本上的題),文內都講解過的:

一、表示網絡包收件人的接收方IP地址是位於IP頭部仍是TCP頭部中呢?
二、端口號用來指定服務器程序的種類,它位於TCP頭部仍是IP頭部中呢?
三、對包是否正確送達進行確認的是TCP仍是IP?
四、根據IP地址查詢MAC地址的機制叫什麼?
複製代碼

立刻就要放假回家過年了。最後祝你們假期愉快~新年快樂😄新年一塊兒學習、一塊兒成長、一塊兒進步!

相關文章
相關標籤/搜索