前言

我大學是學網絡工程專業,也就是那種拉網線,面向網線編程的。依稀記得學習計算機網絡這門課程的時候搭建的 IT宅 itzhai.com 個人網站。

算一下,學這門課程也已經快十年了。

某一天,偶然又看到了這本書:

翻了下,發現裏面的內容竟然還是毫不過時,真的是越底層的知識越有價值呀。 我擦了擦書面的灰塵,決定要爲它寫點什麼 ,於是又從書架上找了相關的書籍:

來回翻閱和梳理總結,逐漸輸出了這篇文章,獻給對網絡不太熟悉,又想快速從入門到熟練的朋友們。

相信大家拿到Socket API,就可以很快寫好代碼,收發消息,傳送文件什麼的,可是底層究竟發生了什麼?TCP、UDP、HTTP是什麼關係、爲啥要有WebSocket編程。我們從TCP/IP協議棧以及一根網線說起,逐步揭開面向網線編程內功心法的面紗。

最後,在這裏解答一個問題:有人問我爲什麼要寫公衆號技術文章呢?工作越久,發現身邊比自己年紀小的人越多,我也時常在想,那些同齡人或者比我大的人都去哪裏了,也許有些人忙於家庭生活不亦樂乎,有些人因爲公司上市拿到可觀的收入轉行了,也許有人在大公司做起了管理工作,開始走管理路線,帶領團隊創造新的產品。我寫公衆號的原因之一,也就是想告訴大家,我一直在做技術,一個堅持寫代碼的大齡技術人,並且希望能夠結實更多志同道合的技術人。沒錯,在說你們呢,不要求三連,這篇文章對你感興趣就點個在看唄。Thanks♪(・ω・)ノ

本文的各種電腦、服務器、路由器小圖標都是我一筆一筆畫的用心只做了儘量美觀有趣好理解的配圖,旨在希望能夠有助於大家理解文章內容,真心希望產品經理也可以看懂。

1、網絡分層

1.1、OSI七層模型

爲了制定一個統一的計算機網絡體系,國際標準化組織ISO提出了一個試圖使各種計算機可以在世界範圍內互聯成網的標準框架:OSI/RM(Open System Interconnection Reference Model 開放系統互連基本參考模型),該模型如下:

媒介層:第一到第三層稱爲媒體層,它們主要與硬件相關,例如路由,交換和電纜規格;

主機層:第四到第七層稱爲主機層,它們是實現網絡服務相關的軟件。

大致介紹一下各層( 注意:看看就好,這不是重點,重點是後面的TCP/IP協議 ):

物理層
數據鏈路層
網絡層
傳輸層
會話層
表示層
應用層

可以發現,這個模型還真有點複雜。但很可惜,這個模型似乎不怎麼流行,原因如下:

  • OSI專家缺乏實際經驗,缺少商業驅動;
  • OSI協議實現過於複雜,運行效率低;
  • 標準制定週期長,市場已被其他標準佔據;
  • 層次劃分不太合理,部分功能在多個分層中出現。

在1980年代末和1990年代初的一段時間內,工程師、組織和國家對哪種標準(OSI模型或 Internet協議套件 )將更能塑造最佳和最強大的計算機網絡存在爭端,導致兩極分化。儘管OSI在1980年代後期開發了其網絡標準,後來更多的供應商網絡上更多采用的卻是 TCP / IP 標準,最終 TCP/IP 成爲了實時的國際標準。

1.2、TCP/IP四層模型

下面我們把TCP/IP模型和OSI模型放一起對比下:

可以發現,TCP/IP體系少了表示層和會話層,數據鏈路層和物理層用鏈路層取代。

應用層
傳輸層
網絡層
鏈路層

而後面我們除了講到各種協議之外,還會順便提及一些底層硬件,爲了更好的進行闡述,我們將把鏈路層再分爲物理層和數據鏈路層,採用以下這種五層模型:

根據運行模式分爲以下兩種:

  • 運行於用戶進程:應用層,關注應用程序的細節,不關注底層網絡通信細節;
  • 運行於內核:傳輸層、網絡層、鏈路層,在內核中執行,主要處理所有的通信細節。

介紹了這麼多概念,是不是比較難懂呢,沒關係,我們列一下每一層主要的協議,接下來我們會詳細的講解各種協議的原理。

1.3、分層模型如何工作

這裏我們通過一個FTP客戶端的通信流程,來說明下兩臺主機是如何工作在TCP/IP分層模型上的:

如上圖,A主機的FTP客戶端要與B主機的FTP服務器進行交互。

我們對設備做一下劃分:

  • 端系統(End system):A主機和B主機
  • 中間系統(Intermediate system):路由器

其中用到的協議也做一下劃分:

  • 端對端協議(End-to-end):包括應用層和傳輸層,端系統直接直接進行交互;
  • 逐跳(Hop-by-hop)協議:網絡層,需要經過端系統中所有的中間系統。

對於應用層來說,他們好像是直接與端系統進行交互的,應用層根本不知道底層通信用了多少個路由器,是在以太網上還是在令牌環網上的。

什麼是三層設備,二層設備?

如上圖,路由器工作在網絡層,屬於第三層,所以經常有人稱他爲三層設備;而後面我們會講到交換機,他是工作在第二層-數據鏈路層,所以也成爲二層設備。

1.3.1、爲什麼要分層?

正如上面的例子,分層之後是的頂層屏蔽了底層的物理和通信細節。底層的通信原理是在較低的協議層中實現的,因此每個進程都將隱藏大多數通信細節。以此類推,在傳輸層,通信表現爲主機到主機,而無需瞭解應用程序數據結構和連接的路由器。而在互聯網絡層,則在每個路由器上遍歷各個網絡邊界。

就像我們搞軟件開發劃分層次一樣, 分層之後,提高了軟件的複用度,封裝每層細節,使用者只需要關注使用的API就可以了,不用關注實現細節。 你不要告訴我你的一個功能涉及的一萬行代碼是寫在一個函數里面的,那太可怕了。

想要了解底層細節的人,就只能拆開TCP/IP協議潘多拉之盒,逐個協議去了解了,這也是本文後邊會繼續探討的內容。高度的封裝,使得頂層開發人員能更快速的通過API開發應用程序。 作爲一個API工程師,你知道怎麼調用API發送HTTP請求就夠了,但是作爲一個有追求的工程師,你瞭解了這些細節之後,就能夠勝任程序調優以及更加底層的開發工作了。 這也是爲什麼我堅持寫 IT宅 itzhai.com 博客的原因:我想探索技術的本質,而不是生活在API構造的童話世界裏面,這樣即使童話世界謊言被拆穿的那天,也不至於失掉技術的信仰,因爲我仍然有能力構建一個新的童話世界。

接下來我們看看數據包從在傳輸過程中是如何封裝的。

1.3.2、數據包的封裝和分用

爲了演示,我們需要構建一個局域網(LAN)。

假設我們現在直接通過兩個網線把兩臺電腦連起來進行通信,需要做哪些工作呢。多虧我大學學的是網絡工程,也是拉過網線的,所以多少還知道一點:

  • 準備一根網線,兩個水晶頭;
  • 水晶頭要做交叉線,採用1-3,2-6交叉接法,保證兩個水晶頭之間能夠正常收發信號;
  • 把兩個水晶頭分別插在兩臺主機的電腦上;
  • 給兩臺電腦配置IP、子網掩碼和網關,必須要配置到同一個網絡中。

這樣我們就構建好了一個最簡單的局域網了。

1.3.2.1、封裝

所謂封裝,就是每一層都會根據用到的協議,把數據封裝成最終的一個數據單元(不同分層有不同的叫法,參考上圖最左邊每層的描述),每一層拿到的上一層的內容,把上一層封裝好的內容作爲當前層的數據,然後加上自己的協議頭或者尾,接着執行該層協議的相關處理邏輯。

有沒有發現, 這有點像裝飾者模式,每一層拿到上一層的內容之後都添加額外的處理邏輯,但是不改變上層傳過來的內容。

如上圖,左邊部分爲封裝的過程:

  • A主機請求B主機的HTTP服務,應用層使用HTTP協議對請求內容進行封裝,加上HTTP請求頭,然後傳給下一層;
  • 傳輸層拿到HTTP數據,使用TCP協議進行處理,加上自己的 TCP頭 ,封裝成 數據段 ,通過TCP協議傳輸給下一層。這一層通過TCP協議保證了 可靠的傳輸
  • 網絡層拿到TCP傳輸段之後,使用 IP協議 進行處理,加上 IP頭 ,封裝成包進一步傳給下一層。這個IP決定了什麼路由或者主機需要接收處理這個包;
  • 數據鏈路層拿到網絡層的包之後,進一步封裝成 數據幀 ,最終通過數據鏈路層進行發送處理,最終數據幀通過 物理層 傳輸給接收端。

1.3.2.2、分用

所謂分用,指的是主機或者中間設備接收到一個物理層傳輸過來的數據幀時,數據開始從協議棧中由底向上升,逐層處理,每層去掉對應的協議的報文首部。每層協議盒都要檢查報文首部的協議標識進行對應的協議處理。

在上圖中,右邊部分爲分用的過程:

  • 主機B接收到物理層傳過來的數據幀之後,首先從首部找到 MAC地址 ,判斷是否發送給自己的,如果不是則進行丟棄;
  • 如果發送包是自己的,則從數據幀確定數據 協議類型 ,再傳給對應的協議模塊,如IP、ARP等;
  • IP模塊接收到數據後獲取 IP首部判斷首部接收的IP IP地址匹配,如果匹配則根據首部協議類型轉發給對應的模塊,如TCP、UDP等;
  • 傳到TCP模塊之後,首先TCP模塊會 計算校驗和 ,判斷數據的 完整性 ,然後處理數據包的 順序接收 相關邏輯;最後檢查 端口號 ,確定具體應該要轉發給應用層的哪個應用程序。
  • 應用層接收到數據之後, 解析 數據進行 展示 ,這裏是HTTP數據包,所以按照HTTP協議的約定進行解析展示。

可以發現,以上流程中,大家感知最深刻的就是傳輸層的TCP或者UDP協議,以及應用層的HTTP協議了,因爲做網站開發,或者網絡通信編程經常會用到它們的API。

上面介紹的並不是很詳細,不過沒關係,後面我們會把主要的協議都拿出來詳細的講解。

當然,正常的網站請求中,中間肯定會涉及到很多路由器,交換機,光纖等底層的物理設備,中間會產生很多的逐跳(Hop-by-hop),每個中間系統都會對數據幀進行分用和封裝的過程。

1.4、TCP/IP協議簇

TCP/IP協議簇內容非常多,這裏列出的是本文可能會介紹到的相關協議,以及他們之間的交互關係:

2、物理層

我們的數據幀究竟是怎麼傳給不同的主機呢。前面我們瞭解到每一個上層都依賴於下層的API,而物理層是最底層的了,它是真的要把數據傳出去了。而數據最終都會變爲0和1,物理層依賴於各種不同硬件技術,通過網絡的電子傳輸技術,把0和1在傳輸介質中進行傳輸。

2.1、通信系統的模型

下我我們舉一個最簡單的例子來說明通信系統的模型。

很久以前,有些同學家裏都是用的電話線進行上網的,這種網絡傳輸模型類似如下這樣:

如上圖,主要包括源系統,傳輸系統,目的系統,可以抽象爲下半部分的模型:

  • 源點:源點產生要傳輸的數據;
  • 發送器:源點產生的數據經過發送器編碼之後進行傳輸;
  • 傳輸系統:傳輸系統可能是簡單的傳輸線,也可能是複雜的網絡系統;
  • 接收器:接收傳輸系統的信號,轉換爲能夠被目的設備處理的信息;
  • 終點:從接收器獲取傳送過來的數字比特流,最終輸出信息。

2.2、物理層解決什麼

傳輸媒介的種類非常多:雙絞線、對稱電纜、同軸電纜、光纜、無線信道等,導致物理層的協議種類較多。

物理層的主要作用是屏蔽掉這些傳輸媒介和通信手段的差異,使物理層上面的數據鏈路層感覺不到這些差異。爲此, 物理層需要處理以下事情

  • 規定接口所用接線器的形狀和尺寸,引腳數目和排列,固定和鎖定裝置等;
  • 規定接口電纜各條線上的電壓範圍;
  • 規定某一電平電壓的意義;
  • 規定不同功能的各種可能事件出現順序。

2.3、物理層也出面試題?

最後我列幾個物理層常見的面試題,一般的開發人員都是工作在傳輸層以上,所以考一些TCP,UDP,HTTP,HTTPS等協議我覺得更貼近開發人員真實的工作場景。當然,如果是通信領域的工程師,物理層都是家常便飯,這些可是通信的基礎知識。即使知識應用開發工程師,瞭解這些也不會喫虧,說不定哪天親戚還需要叫你幫忙拉網線呢。

下面是幾個常見的物理層面試題:

有哪些通信交互方式?單工、半雙工通信、全雙工通信?

單工通信,又稱爲單向通信,只有一個方向的通信,如無線電廣播,電視廣播;

半雙工通信,又稱爲雙向交替通信,雙方都可以收發信息,只能交替進行;

全雙工通信,又稱爲雙向同時通信,雙方可以同時發送和接收數據。

爲了提高信道利用率,有哪些信道複用技術?

所謂信道複用技術,指的是大家共享一個信道進行通信,在接收端在使用分用器,把合起來傳輸的信息分別送到相應的終點;

頻分複用

用戶在分配到一定的頻帶後,通信過程中使用都佔用這個頻帶;

時分複用

將時間劃分爲一段段等長時分複用幀,每一個時分複用的用戶週期性的佔用幀位;

統計時分複用

時分複用,如果用戶沒有任何數據要傳輸,也會週期性的給他分配時隙,這就導致了信道利用率不高。

爲此出現了統計時分複用。

統計時分複用使用STDM幀來傳送複用的數據,把所有用戶數據按時間順序組成STDM幀,放入一個隊列中,依次發送出去,這樣就能夠更合理的共享信道。STDM幀中的數據需要添加用戶地址首部信息,以便能夠正確的分發給目標用戶:

這裏的集中器也叫智能複用器。

除了以上三種,還有波分複用和碼分複用,感興趣的朋友可以自行搜尋資料瞭解,這裏就不繼續展開來講了。

物理層要解決什麼問題?

這個問題上一小節已經回答了。

2.4、物理層設備之集線器

如果我們只是想用幾臺電腦搭建一個局域網,那麼可以通過集線器(Hub)進行搭建,這個硬件工作在物理層,會把自己收到的字節都複製到其他端口,如下圖:

如上圖,其中一臺電腦發送信息之後,Hub以廣播的方式發給其他三臺機器,但是究竟哪臺電腦纔會把消息接收下來呢?這裏我們就要講到數據鏈路層了,在這一層判斷數據包是不是自己的。

3、數據鏈路層

3.1、數據幀格式

我們首先來看看數據鏈路層的傳輸數據幀的格式。

所有的以太網(802.3)幀都基於一個共同的格式。在原有規範的基礎上,幀格式已被改進以支持額外功能。

當前以太網的幀格式如下:

前導 :用在發送方和接收方之間同步時鐘和bit流;

SFD :幀開始界定符,只有一個byte,內容固定爲:10101011 (0xAB);

DST :目標MAC地址;

SRC :源MAC地址;

長度或類型 :0800時,表示IP數據報,0806表示ARP請求/應答,0835表示RARP請求/應答;

FCS :幀檢驗序列,用於數據幀的差錯檢測;

這個包應該發給誰?

判斷是否應該接受這個包,就是通過幀的MAC地址進行判斷的。

這是一個物理地址,叫做鏈路層地址,因爲鏈路層主要解決媒體接入控制問題,所以稱爲 MAC地址 (Media Access Control Address)。實際上,MAC地址就是適配器地址或適配器標識符,當適配器插入到某臺計算機之後,適配器上的標識符就成爲這臺計算機的MAC地址了。

怎麼校驗包是否出現錯誤

FCS是幀校驗序列,也就是循環冗餘檢測,收到數據報之後,會通過一個檢驗計算規則,把計算結果與FCS字段匹配,如果匹配補上,則幀可能在傳輸過程中受損,通常會丟棄該幀。

3.2、ARP: 如何獲取目標機器的MAC地址?

我們知道,在數據鏈路層,是通過MAC地址判斷某一個接收到的包是不是要進一步處理的。但是如果我們不知道對方的MAC地址的時候,如何發送數據鏈路層的幀呢?這就需要用到 數據鏈路層的ARP協議 了。

ARP協議:ARP爲IP地址到硬件地址之間提供了動態映射,我們通過ARP可以把32位的Internet地址轉換爲48位的MAC地址。另外,我們可以使用RARP,把48位的MAC地址轉換爲32位的Internet地址。

另外,爲了保證ARP的高效運行,ARP會維護每個主機和路由器上的ARP緩存,把Internet地址和MAC地址的映射關係保存起來,緩存正常到期時間是20分鐘。

下面是這個過程的演示,其中ARP數據幀只把關鍵信息描述出來了,想要了解完整的幀格式可以用參考 TCP/IP協議詳解卷1

如上圖:

主機A想知道 192.168.1.4 這個IP地址的MAC地址是什麼,發現本地緩存中找不到,於是 廣播 了一個ARP請求,主機B和主機D收到之後,發現自己不是 192.168.1.4 於是忽略這個消息,主機C發現自己就是 192.168.1.4 ,於是響應了一個ARP數據幀。最終主機A收到主機C響應的數據幀,拿到了MAC地址,並把IP地址和MAC地址映射關係保存下來。

3.3、鏈路層設備之交換機

3.3.1、爲什麼需要交換機?

前面我們用了集線器組件網絡,這個時候所有消息都會廣播到其他端口,可以發現集線器轉發了很多不必要的消息,能不能只發給需要的端口呢?這個時候就需要用到交換機了。

當一臺電腦A向交換機發送數據時,交換機會把電腦A的IP和MAC地址記住,保存到一個 轉發表 中,如果 轉發表 中暫時找不到目標IP地址的MAC地址,那麼首先還是會廣播消息,最終轉發表會記錄所有請求過交換機的電腦IP和MAC。當然,轉發表也是有過期時間的。

如上圖,看到交換機的奸笑沒有,與集線器不同,交換機是有靈魂的的,你告訴他你的身份證號和住址了,他就會偷偷記下來。

3.3.2、爲什麼有了IP地址,還需要有MAC地址?

IP地址是工作在網絡層的,後面會講到;

MAC地址是工作在數據鏈路層的,也就是交換機這一層,交換機之間的主機進行通信,都是用的MAC地址,但是一旦走出了局域網,我們就得用大家都公認的IP地址了。

MAC地址就好像是我們的身份證,IP就像是我們的住址,可以根據住址寄送快遞,但是不能根據身份證號碼寄快遞,別人不知道怎麼走呢。

一個局域網,用身份證沒有問題呀,因爲要找某個人,ARP會大喊一聲名字,那個人就會告訴你他的身份證號碼了,這個時候直接以身份證作爲標識傳消息,別人聽到不是自己的身份證就不管了。最終交換機這個小管家記住了所有人的名字跟身份證號碼,就會使用悄悄話的方式傳達消息了。這也就是用到了交換機的轉發表。

3.3.3、交換機拓撲環路問題

假設我現在拉網線,搞了一個這樣的拓撲結構:

如上圖,主機準備發送一個消息出去,結果交換機B收到後,複製數據幀,發送給了交換機A、C、D,此時交換機B認爲主機是在左邊。但是不妙的事情發生了,交換機D收到消息後,由於轉發表還是空的,又是也複製數據幀,轉發到了交換機A、B、C,這個時候交換機B發現怎麼主機的數據又從右邊傳過來了,這些徹底暈了,不知道主機究竟在哪裏。就這樣數據一致在這個網絡裏面打轉,這就拓撲環路導致的問題。

生成樹協議

爲了解決以上問題,於是 有了生成樹協議(Spanning Tree Protocol,STP)。

STP通過在每個交換機禁用某些端口工作,來避免拓撲環路,保證不會出現重複路徑。

STP會找到拓撲結構的一個生成樹,通過生成樹避免環路。生成樹的形成和維護有多個網橋完成,在每個網橋上運行一個分佈式算法。

以上拓撲,結構,最終應用了生成樹,禁用一些端口之後,可能會是這樣:

這樣,消息就不可能再傳回左邊的主機了,從而避免了拓撲環路導致的問題。

建立生成樹:

網橋會發送一種稱爲網橋協議數據單元(BPDU)的幀來輔助形成和維護生成樹。

STP首先會嘗試選舉根網橋,根網橋是在網絡中標識符最小的網橋(也就是說優先級與MAC地址結合),網橋初始化的時候,假設自己是最小的網橋,然後用自己的網橋ID作爲根ID字段的值發送配置BPDU消息,如果發現ID更小的網橋,那麼會停止發送自己的幀,並基於接收到的ID更小的幀構造下一步發送的BPDU消息。發出根ID更小的BPDU端口被標記爲根端口,剩餘端口被設置爲阻塞或者轉發狀態。

3.4、VLAN

3、網絡層

網絡層,Internet layer,最熟知的就是IP協議了(Internet Protocol)。

前面我們將的數據鏈路層,其實只能在局域網內進行通信,因爲都是通過MAC地址進行傳達信息的,要想跨局域網,那麼就得用到IP地址了,這就是網絡層要做的事情了。

首先我們來介紹下網絡的一個協議:ICMP協議。

3.1、ICMP協議

IP協議本身不支持發現發往目的地地址失敗的IP數據包,也沒有提供直接的方式獲取診斷信息,比如在發送途中,經過了哪些路由器,以及往返時間。

爲此,就有了 ICMP協議Internet Control Message ProtocolICMP )專門來負責這些事情。

ICMP並不爲IP網絡提供可靠性,它只是用於反饋各種故障和配置信息。丟包不會觸發ICMP。

ICMP是 RFC 792中 定義的Internet協議套件的一部分。ICMP消息通常用於診斷網絡或探測網絡目的,或者是爲了響應 IP 操作中的錯誤而生成(如 RFC 1122中 所指定),ICMP錯誤響應給原始數據包的源IP地址。

但是黑客經常用ICMP來做壞事,於是網絡管理員可能會用防火牆阻止掉ICMP報文,這樣的話,很多ping、traceroute之類的診斷程序就無法正常工作了。

3.1.1、格式

ICMP報文是在IP數據報內部傳輸的,格式如下:

而ICMP報文的格式如下:

其中:

  • 類型有15個不同的值,描述特定類型的ICMP報文;
  • 某些ICMP報文還是用代碼字段的值來進一步描述不同的條件;
  • 校驗和字段用於ICMP報文的差錯檢查。

以下是常見的差錯報文類型:

類型 代碼 描述 查詢 差錯
0 0 回顯應答(ping應答)
3 目標不可達
0 網絡不可達
1 主機不可達
2 協議不可達
3 端口不可達
4 需要進行分片但設置了不分片比特
5 源站選路失敗
6 目的網絡不認識
7 目的主機不認識
8 源主機被隔離(作廢不用)
9 目的網絡被強制禁止
10 目的主機被強制禁止
11 由於服務類型 T O S , 網 絡 不 可 達
12 由於服務類型 T O S , 主 機 不 可 達
13 由於過濾,通信被強制禁止
14 主機越權
15 優先權中止生效
4 0 源端抑制
5 重定向
0 對網絡重定向
1 對網絡重定向
2 對服務類型和網絡重定向
3 對服務類型和主機重定向
8 0 回顯請求(ping)
9 0 路由器通告
10 0 路由器請求
11 超時
0 傳輸期間生存時間爲0(Traceroute)
1 在數據報組裝期間生存時間爲0
12 參數問題
0 壞的 I P 首部(包括各種差錯)
1 缺少必需的選項
13 0 時間戳請求
14 0 時間戳應答
15 0 信息請求
16 0 信息應答
17 0 地址掩碼請求
18 0 地址掩碼應答

其中,最常用的類型是8:回顯請求(ping),以及0:回顯應答(ping應答)。

3.1.2、查詢報文

查詢報文是有關信息採集和配置的ICMP報文。

我們經常用到的ping程序就用到了ICMP查詢報文。

ping程序

ping程序會發送一份ICMP回顯請求給主機,並等待返回ICMP回顯應答。

ping程序ping不通了,就不能訪問對應的主機了嗎?

我們知道,網絡管理員可能會用防火牆阻止掉ICMP報文的,這樣我們可能就ping不通了,但是主機的可達性不能只取決於IP層是否可達,還與端口號和協議有關,而ping是運行在網絡層的,用於測試網絡連接狀態和信息包發送接收狀況,即使ping不通,我們也可能用telnet遠程登錄到主機的其他端口,如25號端口。

ping程序用到了回顯請求和回顯應答報文,報文格式如下:

Unix系統實現ping程序時,把ICMP報文的標識符設置爲進程ID,在進程內,序號從0開始,每發送一次新的 回顯請求就加1,這樣就可以同時運行多個ping進程了。

ping程序的端口號是什麼?

端口號是傳輸層的東西,ping程序是使用ICMP協議,直接跳過了傳輸層,所以呢,ping程序是沒有所謂的端口號的。

我們發送一個ping請求,數據在協議棧中的處理流程如下:

  1. A主機的ping應用程序向服務器發起回顯請求,說了一句: hi
  2. 直接傳輸到網絡層的ICMP協議,進行ICMP數據封裝:
    1. 8表示回顯請求,112是發起請求的進程號,1表示請求序號
  3. IP協議拿到數據後進一步加上IP頭,加上自己的IP和目標IP,傳輸給數據鏈路層;
  4. 數據鏈路層拿到IP數據包,準備封裝成幀,這個時候會去尋找目標IP的MAC地址,如果在A主機的ARP映射表找到了IP的MAC地址,那就直接拿來用了,否則會發起一個ARP廣播請求,獲取到MAC地址。至於跨網關這種ping,會多了轉發的功能更,後面會進行介紹。最終數據鏈路層封裝成數據幀,從網絡接口發出去;
  5. 服務器拿到數據幀之後,拿到MAC頭,判斷MAC地址是自己的,就基於拿到Frame data,按首部協議傳給對應的模塊,即IP模塊;
  6. IP模塊拿到數據,判斷到IP跟自己對上了,與是繼續拿到IP data,傳輸給ICMP協議,ICMP協議收到消息,準備應答:
    1. 0表示回顯應答
  7. 然後按照同樣的流程,把數據包發送回A主機。

可以發現, ping程序是直接用到了網絡層的ICMP協議,不經過傳輸層

是什麼原因導致ping失敗了?

ping失敗的原因有很多:

  • 可能是輸錯了IP;
  • 可能是網絡配置不正確,如錯誤的子網掩碼;
  • 可能有防火牆軟件組織了ping;
  • 可能是硬件故障,如損壞了的以太網適配器,電纜,路由器,集線器等。

如果叫你自己實現一個ping程序,你會怎麼做呢?

提示:爲了能處理ICMP網絡報文,我們需要用到原始套接字(SOCK_RAW),而不是SOCK_STREAM或者SOCK_DGRAM套接字。

更多提示: Homework 6: A raw socket ping tool ,思路都在這裏了,大家動手做一做,然後就可以有直接操作網絡層的工作經驗了。:smirk:

3.1.3、差錯報文

差錯報文是有關 IP數據報傳遞 的ICMP報文。要是發送IP數據報中途產生了異常,那麼就會響應ICMP差錯報文。

但是不是所有情況都會響應ICMP差錯報文,如以下場景:

  • ICMP差錯報文不會產生另一個ICMP差錯報文;
  • 目的地址是廣播地址或者多播地址的IP數據報不會產生差錯報文;
  • 作爲鏈路層廣播的數據報不會產生差錯報文;
  • 源地址不是單個主機(源地址爲零地址、環回地址、廣播地址或者多波地址)的數據報不會產生差錯報文;

爲什麼要這些規則呢? 假如允許ICMP差錯報文對廣播分組響應,那麼就會導致廣播風暴了。

下面我們舉一個ICMP差錯報文的例子來說明下。

目標不可達

上面的表格我們瞭解到,如果類型爲三則表示目標不可達,而根據具體的代碼可以進一步劃分:

類型 代碼 描述 查詢 差錯
3 目標不可達
0 網絡不可達
1 主機不可達
2 協議不可達
3 端口不可達
4 需要進行分片但設置了不分片比特

下面我們看一個端口不可達的例子來演示下ICMP差錯報文附加的信息。

ICMP端口不可達案例

這裏我們演示通過tftp訪問一個不存在的端口號,查看其返回的ICMP響應差錯報文。tftp應用在傳輸層是通過UDP來進行傳輸數據的

下面我們tftp請求之前先開啓tcpdump抓包:

sudo tcpdump -i en0 -nn host 我的IP and 目標IP

然後執行tftp命令:

tftp
tftp> connect 目標IP 8090
tftp> get test.foo
Transfer timed out.

可以發現,在等待了大約25秒之後,最終輸出:Transfer timed out.

觀察tcpdump抓包日誌:

16:51:16.739632 IP 我的IP.64517 > 目標IP.8090: UDP, length 20
16:51:16.768590 IP 目標IP > 我的IP: ICMP host 193.112.43.165 udp port 8090 unreachable, length 56
16:51:21.743056 IP 我的IP.64517 > 目標IP.8090: UDP, length 20
16:51:21.751958 IP 目標IP > 我的IP: ICMP host 193.112.43.165 udp port 8090 unreachable, length 56
16:51:26.748387 IP 我的IP.64517 > 目標IP.8090: UDP, length 20
16:51:26.757631 IP 目標IP > 我的IP: ICMP host 193.112.43.165 udp port 8090 unreachable, length 56
16:51:31.752851 IP 我的IP.64517 > 目標IP.8090: UDP, length 20
16:51:31.794217 IP 目標IP > 我的IP: ICMP host 193.112.43.165 udp port 8090 unreachable, length 56
16:51:36.755172 IP 我的IP.64517 > 目標IP.8090: UDP, length 20
16:51:36.774400 IP 目標IP > 我的IP: ICMP host 193.112.43.165 udp port 8090 unreachable, length 56

可以發現這裏執行了五次UDP請求,每次請求都響應了一個ICMP包,爲 udp port 8090 unreachable 端口不可達,產生了ICMP不可達報文,該報文一般格式如下:

爲什麼需要返回IP首部 :因爲IP首部包含了協議字段,使得ICMP知道如何解釋後面的8個字節;

爲什麼需要原始IP數據報中數據的前8個字節 :因爲這裏麪包含了源端口和目的端口。

不過看起來我的電腦好像忽略了ICMP報文,還是繼續重試了4次。

注意:ICMP報文是在主機之間交換的,網絡層的協議,不需要端口號,而以上20個字節的UDP數據報是包含了源端口號和目標端口號信息的。

爲什麼TFTP客戶程序會繼續重發呢?

因爲網絡編程中,BSD系統不把從socket接收到的ICMP報文中的UDP數據通知用戶進程,除非該進程以及發送了一個connect命令給該接口。標準的BSDTFTP客戶程序並不發送connect命令,所以它永遠也不會受到ICMP差錯報文的通知。

traceroute程序

traceroute工具用於確定從發送者到目的地路徑上的路由器。

traceroute主要是通過故意設置特殊的TTL,來達到追蹤目的地路徑上的路由器的功能。

TTL運行原理

TTL:是 Time To Live的縮寫,該字段指定IP包被路由器丟棄之前允許通過的最大網段數量。每經過一個路由器,TTL就會減一,然後再把IP包轉發出去,如果TTL減到0了,路由器就會丟棄收到的TTL=0的IP包,並向IP包的發送者發送一個ICMP差錯報文,類型爲11,代碼爲0:傳輸期間生存時間爲0。

第一輪,traceroute設置TTL值爲1,那麼遇到第一個路由就返回ICMP容錯報文了,下一輪,TTL設置爲2...這樣依次增加。最終就把整個鏈路的路由器都試出來了。

當然,有點路由器不會回整個ICMP,這也是爲什麼你去traceroute一個公網地址,看不到中間路由的原因。

除此之外, traceroute也可以通過不設置分片,來確定傳輸鏈路的MTU(Maximum Transmission Unit, 最大傳輸單元) :首先發送一個分組的長度正好與出口MTU相等,如果中間遇到窄點的關口,就被卡主了,這個時候會接收到一個ICMP差錯報文,然後調小分組長度重試...

3.2、網關與路由器

在講數據鏈路層的時候,我們用一個交換機,就構建了一個局域網。但是現在我們局域網裏面的一臺機器,想要訪問另一個局域網的機器,怎麼辦呢。這就是本節討論的內容。

我們必須先連接下IP協議。

3.2.1、IP協議

IP是TCP/IP協議簇中最核心的協議,所有TCP、UDP、ICMP等數據都已IP數據報格式進行傳輸。

3.2.1.1、IP協議特點

  • IP協議是不可靠的傳輸協議 ,上一級我們講到到了ICMP協議,每當傳輸出現異常,IP層都會丟棄數據包,並且可能會響應一個ICMP差錯消息給發送端,而任 何要求的可靠性必須由上層如TCP協議來提供
  • IP協議是無連接的 ,也就是說IP不維護任何關於後續數據報的狀態信息,每個數據報相互獨立。具體表現在:可以不按發送順序接收,不用維護連接狀態,免去了維護複製的鏈接狀態信息(後面講傳輸層的TCP協議的時候會介紹到)。

3.2.1.2、IP數據報格式

下面是IP數據報的格式:

  • 版本:協議版本號,指明IPv4還是IPv6;
  • 頭部長度:最長60個字節;
  • 服務類型:包含3bit優先權子字段(已被忽略),4bit TOS子字段(分別代表最小時延、最大吞吐量、最高可靠性和最小費用)和1bit未用位但必須置0;
  • 總長度:指的是整個IP數據報的長度,單位字節;
  • 標識符:唯一地標識主機發送的每一份數據報,通常每發送一個數據報就+1;
  • 標誌:主要用於IP分片;
  • 分片偏移:主要用於IP分片;
  • 生存期:設置數據報可以經過最多的路由器數;
  • 協議:主要表明IP數據是什麼協議,用於對數據報進行分用;
  • 頭部校驗和:校驗數據報是否正確;
  • 源IP地址:發送IP數據報的IP地址;
  • 目的IP地址:IP數據報目的IP地址;
  • 選項:可選數據;
  • IP數據:具體的IP數據;

3.2.2、路由器

路由器一般充當一個網關,屬於三層設備。會把MAC和IP頭取下來根據內容進行處理。路由器有五個網口,分別可以連接5個局域網,每個網口和局域網的IP地址相同的網段,每個網口都是對應的局域網的網關。

5個網口中一般包含一個外網網口,外網網口用於連接到WAN上。

路由器除了有交換機的功能外,更擁有路由表作爲發送數據包時的依據,在有多種選擇的路徑中選擇最佳的路徑。

一層設備、二層設備、三層設備分別有什麼區別?

路由器是屬於OSI第三層的產品,交換機是OSI第二層的產品。

第二層的產品功能在於,將網絡上各個電腦的MAC地址記在 MAC地址表 中,當局域網中的電腦要經過交換機去交換傳遞數據時,就查詢交換機上的MAC地址表中的信息,將數據包發送給指定的電腦,而不會像第一層的產品(如集線器)每臺在網絡中的電腦都發送。

而路由器除了有交換機的功能外,更擁有 路由表 作爲發送數據包時的依據,在有多種選擇的路徑中選擇最佳的路徑。此外,並可以連 接兩個以上不同網段的網絡 ,而交換機只能連接兩個。路由表存儲了(向前往)某一網絡的最佳路徑、該路徑的“路由度量值”以及下一個(跳路由器)

網關地址一般是網段的第一個或者第二個,如192.168.23.0/24這個網段,網關地址可能是192.168.23.1/24或者192.168.23.2/24。

在不同的局域網中,私有IP地址是會重複的,而我們要訪問公網的時候,一定要分配一個共有IP地址,所以,我們在訪問公網的時候,需要路由器幫忙把私有IP變爲共有IP,這種叫做NAT網關,普通內網之間的通信用到的稱爲轉發網關。

我們先來看看轉發網關

3.2.2.1、轉發網關

假設主機A和主機B屬於同一個內網,他們通過兩個路由器連接起來,如下圖:

主機A要訪問主機B,流程如下:

  • 主機A發現要訪問的主機B不是在同一個網段,準備先找到 網關 ,把消息發給網關,網關地址是192.168.1.1,主機A通過 ARP 獲取到了網關的MAC地址,然後發送如下數據包:

    • SRC MAC: 主機A的MAC
      DST MAC: 路由器A的192.168.1.1網口的MAC
      SRC IP : 192.168.1.3
      DST IP : 192.168.3.4
  • 路由器A的 192.168.1.1 網口接收包之後,準備把包轉發出去。而路由器A中的路由表中匹配到了,要想發送給 192.168.3.4/24 ,需要從 192.168.2.1 這個網口出去,下一跳地址爲 192.168.2.2/24 。路由器通過ARP拿到了下一跳 192.168.2.2/24 d的MAC地址,然後發送如下數據包:

    • SRC MAC: 路由器A的192.168.2.1網口的MAC
      DST MAC: 路由器B的192.168.2.2網口的MAC
      SRC IP : 192.168.1.3
      DST IP : 192.168.3.4
  • 路由器B的 192.168.2.2 網口接收包之後,準備把包轉發出去。路由器B中判斷到目標IP在 192.168.3.1 這個網口所在的局域網,於是通過ARP拿到了 192.168.3.4 的MAC地址,然後發送如下數據包:

    • SRC MAC: 路由器B的192.168.3.1網口的MAC
      DST MAC: 主機192.168.3.4網口的MAC
      SRC IP : 192.168.1.3
      DST IP : 192.168.3.4

最終,主機B收到數據包。

可以發現在轉發網關中, 源IP和目的IP地址都是不會變的,因爲整個內網不可能有衝突的IP

但是,假如我們要訪問外網,情況就不一樣了,最終可能會請到到另一個局域網,另一個局域網的私有IP是可能跟我們所在的局域網一樣的,爲了避免衝突,於是就有了NAT網關。專門在把數據包發送出去之前,把IP改爲公網IP。

3.2.2.2、NAT網關

現在假設主機A要訪問另一個城市的主機B,這裏爲了演示NAT,我們把模型簡化一下,假設路由器出去之後就是公網IP了,如下:

假設路由器A和路由器B都直接接入了互聯網。

現在主機A想訪問主機B:

  • 由於是不同的局域網,主機A不會知道主機B的IP的,而主機B接入互聯網的之後,領取到了一個互聯網的IP,就是上圖路由器WAN口的IP: 203.0.113.103 ,所以主機B會把這個IP作爲主機B的IP,最終發出如下IP數據包:

    • SRC MAC: 主機A的MAC
      DST MAC: 路由器A的192.168.1.1網口的MAC
      SRC IP : 192.168.1.3
      DST IP : 203.0.113.103
  • 192.168.1.1網口接收到包之後,發現要想訪問 203.0.113.103 ,就要從 203.0.113.102 這個網口出去,發給路由器B,路由器B中判斷到目標IP就是 203.0.113.103 這個網口,於是通過ARP拿到了 203.0.113.103 的MAC地址,然後發送如下數據包:

    • SRC MAC: 路由器A的203.0.113.102網口的MAC
      DST MAC: 路由器B的203.0.113.103網口的MAC
      SRC IP : 203.0.113.102
      DST IP : 203.0.113.103
    • 因爲消息是要發到公網的,最終SRC IP會被NAT爲公網的IP 203.0.113.102;

  • 最終路由器B接收到消息,通過NAPT得到最終接收數據報的IP爲當前局域網的192.168.1.3/24,最終把消息轉發給了這個IP所在的主機B。

NAPT是如何把一個公網IP翻譯爲局域網IP的?

傳統的NAT(traditional NAT)包括基本NAT(basic NAT)和網絡地址端口轉換(Network Address Port Translation, NAPT)。基本NAT只執行IP地址的重寫,本質上是將私有地址改寫爲一個公共地址,這往往取自於一個由ISP提供的地址池或共有地址範圍,這種NAT不是最流行的,因爲無助於減少需要使用的IP地址數量。

比較流行的做法是使用NAPT,NAPT使用傳輸層標識符如TCP或者UDP端口,或者ICMP查詢標識符來確定一個特定的數據報到底和NAT內部哪臺私有主機相關聯。

如果局域網兩個端口號一樣,那麼NAPT會重寫端口號,保證不一致。如下圖,三個局域網的IP需要轉換爲公網IP,由於有兩個的端口重複了,於是NAPT進行了端口重寫:

3.3、路由策略

3.3.1、靜態路由

我們通過route命令和iproute命令都可以進行路由策略的配置和查詢。

  • 可以指明去哪個網絡,走哪個網口,網口的IP是什麼;
  • 也可以創建不同的路由表,針對不同的請求來源,走不同的路由表配置;
  • 當然,也可以按照權重給下一跳地址走配置;
  • 同一個路由,也可以配多個運營商的網絡,針對不同的IP,採用不同的運營商網絡
  • ...

配置時非常靈活的,但是在複雜的網絡環境下手動配置路由成本太大了,並且網絡結構也是經常發生改版的。

所以,我們可以使用動態路由路由器,這種路由器會根據路由協議算法生成動態路由表,動態的隨着網絡運行狀況調整路由表。

3.3.2、動態路由協議

網絡是複雜的,爲了生成動態的路由表,需要配合特定的算法,主流的動態路由主流有兩種算法。

內網路由協議

基於鏈路狀態算法實現的OSPF協議(Open Shortest Path First, 開放式最短路徑優先):主要用於數據中心內部,因此也成爲 內網路由協議 (Interior Gateway Protocol,IGP),關鍵是找到最短的路徑。

OSPF是一種鏈路狀態路由協議。可以將其視爲網絡的分佈式地圖。

外網路由協議

基於距離矢量算法實現的BGP協議(Border Gateway Protocol,外網路由協議):距離矢量,就是每個路由器都保存一個路由表,路由表每行保存了下一跳的路由器,以及距離下一跳路由器的距離。也成爲邊界網關協議。

在BGP的世界中,每個路由域都稱爲自治系統或AS。BGP所做的工作通常是通過選擇遍歷最少自治系統的路由:最短的AS路徑來幫助選擇通過Internet的路徑。

我們會把重點放在傳輸層以上,所以動態路由協議這部分我們暫時不做不深入研究。

4、傳輸層

傳輸層涉及到兩個重要的協議:UDP和TCP,本節我們重點介紹這兩個協議。

4.1、UDP協議

4.1.1、UDP數據報格式

UDP基本沒幹啥事,繼承了IP包的特性:數據可能丟失,順序傳輸無法保證。UDP與後邊介紹的TCP不一樣,是無狀態的。我們來看看UDP數據報的格式:

  • 源端口號:發送數據報方使用的端口號,用於標識發送進程;
  • 目的端口號:接收數據包方使用的端口號,用於標識接收進程;
  • UDP長度:UDP頭部和UDP負載數據的字節長度;
  • UDP校驗和:UDP校驗和覆蓋UDP頭部和UDP數據和一個僞頭部(區別:IP頭部校驗和只覆蓋IP頭部),僞頭部衍生子IPv4頭部字段的12個字節,或者衍生子IPv6頭部字段的一個40字節的僞頭部;
  • 負載數據:具體的UDP數據。

可以發現,UDP與下層不同,是需要端口號的。

爲什麼UDP需要端口號,TCP和UDP端口號可以相同嗎?

類似ICMP協議回顯請求的標識符,UDP的端口用於區分是哪個進程的數據包,如果沒有端口號,那麼就不知道應該把數據包最終交給哪個進程來處理了。

TCP端口號由TCP來查看,UDP端口號由UDP來查看,TCP端口號和UDP端口號是相互獨立的,所以是可以相同的。每個請求都有源IP、目標IP、源端口號、目標端口、協議五個元素來標識的,每個協議的端口池是完全獨立的。

爲什麼UDP的端口號最多是65535個?

在UDP/TCP協議中源端口和目的端口都只有16位,也就是說端口的取值範圍爲0~65535。

4.1.2、UDP特點

UDP在IP層之上,沒有做其他的封裝,主要表現如下特點:

  • 數據可能丟失,順序傳輸無法保證;
  • 無狀態,不需要像TCP那樣要建立連接;
  • 沒有擁塞控制,來一個包就發一個。

4.1.3、UDP使用場景

基於UDP的特點,UDP主要用於以下場景:

  • 需要資源少,在網絡情況比較好的內網,或者對對包不敏感的場合。如DHCP和TFTP就是基於UDP的;
  • 廣播場景,不需要一對一建立連接,如DHCP;
  • 需要時延低,允許丟包,不關注網絡擁塞的場景,如視頻直播這種流媒體,實時遊戲,通信,物聯網等領域。

4.2、TCP協議

TCP是我們平時用到最多的協議,特別是做web開發的時候,或者互聯網後端開發,真的是時時刻刻都會用到,這裏我會展開來講。《TCP/IP詳解-卷1:協議》一書中花了6章來講解TCP的各種功能,單單是從TCP/IP協議棧的名稱就可以看出,TCP協議的分量有多重了。爲此,面試官張口就聊TCP咋的咋的。

與UDP不同,TCP做了很多功能的封裝與實現。

先來簡單介紹下TCP協議:

TCP給應用程序提供給了一種與UDP完全不同的服務。

TCP是面向連接的可靠的服務: 面向連接 指TCP的兩個應用程序必須在它們可交換數據之前,通過相互聯繫來建立一個TCP連接;

TCP提供了一種字節流抽象概念給應用程序:TCP不會自動插入記錄標誌或者消息邊界,這意味着TCP沒有限制應用程序的寫範圍。發送端分兩次發10字節和30字節,接收端可能會以兩個20字節的方式讀入。

我們還是先來看看TCP數據報的格式吧,這個可比UDP複雜多了,但是也是設計的恰到好處的。

4.2.1、TCP數據報格式

如上圖,頭部深黃色部分爲TCP特有的重點字段,後面TCP相關功能基本都是靠這些特有的字段來實現的。

  • 源端口號和目的端口號:同UDP一樣,主要用於區分數據應該轉發給哪個應用;
  • 序號:這個序號是爲了解決亂序問題,32位無符號數,到達2^32-1後再重新從0開始;
  • 確認號:確認已經接收到了哪裏,該確認序號表示該確認號的發送方期望接收的下一個序列號。該字段只有在ACK位字段被啓用的情況下才有效,所以也成爲ACK號或者ACK段;
  • 狀態位:該狀態位會讓TCP連接雙方的狀態發生流轉,常見的狀態爲,後面講建立連接和斷開連接的時候會用到:
    • ACK:回覆狀態,啓用該狀態的情況下,確認號有效,連接建立之後一般都是啓用狀態;
    • SYN:發起一個連接;
    • RST:重置連接,連接去表,經常是因爲錯誤導致;
    • FIN:結束連接,表示該報文的發送方已經結束想對方發送數據;
    • CWR:擁塞窗口減小,發送方降低發送速率;
    • ECE:ECN回顯,發送方接收到了一個更早的擁塞通告;
    • URG:緊急,表示緊急指針字段有效,很少用到;
    • PSH:推送,表示接收方應該儘快給應用程序傳送這個數據——沒有被可靠的實現或用到;
  • 窗口大小:流量的窗口大小,用於流量控制,通信雙方各聲明一個窗口,這個大小表明了自己當前的處理能力;
  • 校驗和:覆蓋了TCP的頭部和數據,以及僞頭部數據(與UDP使用的相似的僞頭部進行計算);
  • 緊急指針:只有在URG位啓用的時候纔有效;
  • 選項:如最大段大小等其他的可選項;
  • 數據:TCP數據報的數據內容。

4.2.2、TCP特點

TCP基於以上數據報的各種字段,實現了以下功能:

  • 數據的順序傳輸;
  • 丟包重傳,保證可靠;
  • 連接維護;
  • 流量控制,保證穩定;
  • 擁塞控制,及時調整,最大程度保證傳輸正常進行。

4.2.3、連接管理

我們首先來看看連接是如何建立的,這裏就涉及到TCP的三次握手了。

4.2.3.1、TCP三次握手

三次握手流程如下:

可以發現,爲了實現可靠連接,雙方都需要發起建立連接。具體流程如下:

  • 第一次握手:主動連接方發送一個SYN報文段指明自己想要連接的端口號,以及客戶端消息的初始化序列化ISN(c);
  • 第二次握手:服務器接收到消息後,也發送自己的SYN報文,包含了服務端的初始化序列號ISN(s),並設置確認號ack=客戶端序列號+1;
  • 第三次握手:客戶端應答服務器的SYN,將服務端的序列號+1作爲ack返回給服務端。

總結一下: 客戶端與服務端利用SYN報文交換彼此的初始化序列號。在我們熟悉的Socket編程中,三次握手在執行connect的時候觸發。

其中的ACK應答和遞增的序列化是可靠性的保證。

爲什麼是三次握手,而不是兩次或者四次?

如果是兩次:

客戶端請求建立連接,服務端收到了請求,並且做出了響應,很明顯,服務器沒法知道這個響應究竟有沒有被接收,也許可能客戶端遲遲收不到SYN響應,於是結束了請求。這個時候再傳消息網絡層就會收到一個ICMP目的不可達的差錯報文。

同理:客戶端的SYN請求如果遲遲沒有服務器的響應,那麼也會重發SYN,最終如果服務端可能收到兩個SYN,客戶端想要建立一個連接,但是服務器收到兩個SYN之後,建立了兩個連接(當然,實際上的三次握手服務端是會判斷客戶端的請求序列號的,發現是同一個序列號,並不會建立多個連接,這也說明 序列號的重要性 )。

爲什麼不需要四次呢?因爲如果服務端和客戶端雙方都發起SYN,並且收到ACK之後,就都知道對方接受了自己的請求了,已經沒有必要再繼續確認下去了。

爲什麼UDP端口號爲65535個?

在TCP、UDP協議的開頭,會分別有16位來存儲源端口號和目標端口號,所以端口個數是2^16-1=65535個。

4.2.3.2、TCP四次揮手

接下來我們看看連接關閉的流程, 連接的任何一方都可以發起關閉操作,此外,也支持雙方同時關閉連接 。在傳統的情況下,負責發起關閉連接請求的通常是客戶端。

這個流程又被稱爲四次揮手:

  • 連接的主動關閉者發送一個FIN段請求關閉連接,攜帶了Seq=K,指明接收方希望看到的自己的當前序列號;攜帶了ack=L,指明自己想要接受到的下一個消息的序號。這個時候,連接主動關閉者表明了自己已經沒有數據要發送了,但是仍然可以接受被動關閉者發送的數據;
  • 連接的被動關閉者進行了ACK回應, ack爲K+1,表明自己已經成功接收到了主動關閉者發送的FIN 。但是自己還未準備好關閉,所以主動關閉者會進入FIN_WAIT_2等待狀態;
  • 緊接着被動關閉者也發送了一個FIN端請求關閉連接,攜帶了Seq=L。告訴主動關閉者自己也準備好了關閉;
  • 最後連接的主動關閉者接收到了對方的FIN關閉請求,也回應了一個ACK,同樣的ack=L+1,表明自己已經成功接收到了被動關閉者發送的FIN;

可以發現, 因爲TCP是全雙工的,雙方都要單獨發起關閉請求,只有當連接雙方都發起FIN關閉請求操作,並且得到確認之後,才完成一個完整的關閉操作 ,這也是被稱爲四次握手的原因。

信息發送期間的狀態流轉如上圖所示。其中主動關閉者在CLOSED狀態之前,有一個TIME_WAIT狀態,那麼問題來了:

爲什麼要有TIME_WAIT狀態呢?

我們知道主動關閉者在應道對方的FIN請求,有可能對方是收不到的,如果收不到的情況下,那麼對方就可能認爲自己的FIN請求丟失了,需要重新發起FIN請求,所以 主動關閉者需要有一個足夠長的等待時間,讓對方有重試的機會

等待時間是2MSL(Maximum Segment Lifetime,報文最大生存時間),這也是報文在網絡上最大的生存時間,超過了這個時間就會被丟棄。RFC 793中規定MSL爲2分鐘,實際應用中常用的是30秒,1分鐘和2分鐘等。如果超過了這個時間,那麼主動關閉者就會發送一個RST狀態位的包,表示重置連接,這個時候被動關閉者就知道對方已經關閉了連接:

如果主動關閉者不進行等待,會出現什麼問題呢?如下:

可以發現,由於端口複用,主動關閉者已經開啓了另一個連接,這個時候被動關閉者還在重試發起FIN請求,導致新主動關閉者新的連接收到了很多沒用的包。因爲包是有序列號的,所以可以判斷到不是本次連接該接收的包。爲此,我們需要讓主動關閉者進行等待,確保被動關閉者不會再發FIN請求了,再進行端口複用。

4.2.3.3、完整連接流程

完整連接流程如下:

可以發現, 每個TCP連接在正常的建立和關閉的基本開銷是7個報文段 ,如果只是需要交換很少量的數據時,有寫程序更願意選擇使用UDP協議。但是UDP會面臨數據丟失,擁塞管理,流量控制等問題。

4.2.4、TCP狀態機

介紹了三次握手和四次揮手,我們再看看看以下這個TCP狀態機就清晰多了。

如果沒有看過三次握手和四次揮手流程,不建議直接看這個狀態機,真的是太複雜了...不過爲了方便大家能夠更直觀的看出狀態流轉,我還是繪製了下,加了一些說明:

4.2.5、數據傳輸

4.2.5.1、如何保證可靠傳輸:ACK+序列號

假設主機A通過TCP向主機B發送數據,當主機A的數據到達主機B時,主機B會發送一個確認應答消息ACK。主機A收到ACK之後,就知道自己的數據已經被對方接收了:

如果主機一直沒有收到ACK,一定時間之後,就會重發,因此,即使主機A的數據報沒有發到主機B,或者主機B的ACK數據包丟失了,也有重傳機制,確保雙方最終可以通過重傳確保能夠正確收到消息:

從上圖也可以看出,主機A實際發了兩次同樣的數據給主機B,主機B可以通過序列號,判斷是重複數據,然後就丟棄了,但是還是會發送一個ACK告訴主機A已經收到消息。

4.2.5.2、流量控制與窗口管理

在TCP頭部中,爲了實現流量控制,包括順序問題與丟包問題,我們重點關注TCP頭部的這三個字段:序列號,序列號與確認號:

(注意:後面部分數據傳輸圖中的發送方統一稱爲客戶端或者發送端,接收方統一稱爲服務端或者接收端,實際的數據傳輸,可以是兩臺電腦之間,或者是兩臺服務器之間)

其中TCP頭部的窗口字段表明自己的處理能力,代表着可用緩存空間的大小,以字節爲單位。

接下來再看看滑動窗口。

TCP連接的每個端都可以收發數據,每個端的收發數據量是通過一組窗口結構來維護的。 每個端都會包含一個發送窗口結構和接收窗口結構。

發送窗口結構

發送窗口結構如下圖所示:

其中:

  • SND.WND: 提供窗口大小 是由接收 返回的ACK 中的窗口大小字段控制的;
  • SND.UNA:記錄窗口左邊界的值;
  • SND.UNA + SND.WND:記錄窗口右邊界的值;
  • SND.NXT:記錄下次發送的數據

所謂窗口,就是左右邊界會根據情況進行調整的窗口,由主要三個動作:

  • 關閉 :窗口左邊界右移,當已發送的數據得到ACK的時候,就會進行關閉,提供窗口大小減小;
  • 打開 :窗口左邊界右移,當已確認的數據得到處理後,那麼接收端可用緩存就會變大,這個時候通過打開操作讓提供窗口大小變大;
  • 收縮 :窗口右邊界左移,使得提供窗口大小減小;

接收窗口結構

接收窗口與發送窗口結構類似,如下圖:

從滑動窗口看如何保證可靠傳輸:順序與丟包問題

爲了避免接收重複數據:接收到的數據包小於左邊界,說明是已經確認過的,將把數據報丟棄;如果接收到的數據報序列號大於右邊界,說明暫時超出了處理能力範圍,也將會被丟棄。

爲了保證已確認數據包的連續性,接收到的數據包的序列號與 已確認 已接受 部分連續的時候,才表示真正的已確認,左邊界纔可以右移。

4.2.5.3、超時重傳機制

基於計時器的重傳超時機制(Retransmission Ttimeout, RTO)

TCP在發送數據的時候會設置一個重傳計時器,如果計時器超時仍然沒有收到ACK確認信息,那麼會進行重傳操作。

如下圖是超時重傳的演示說明例子:

對於接收方來說,1,2,3都已經接收並且發送ACK了,3的ACK丟失了。

ACK丟失的場景:過了一段時間,3的計時器發現超時了,於是會觸發超時重傳。但是這個時候接收方發現3是在已接受已確認區域,於是會丟棄3,並反饋一個ACK;

數據丟失的場景:4和5的數據傳輸丟失了,計數器發現超時,也會進行超時重傳,保證4和5可以傳給接收方,並拿到ACK反饋。

關於重傳時間間隔

ICMP端口不可達案例 中,採用UDP的TFTP客戶端使用簡單且低效的超時重傳策略:設置足夠大的超時間隔,每5秒進行一次重傳;

而TCP的基於計時器的重傳策略是如果發生重試,可以有兩種處理方式:

  • 一種是基於擁塞控制機制,減小發送窗口大小;
  • 另一種是超時時間間隔會一直加倍。

關於重傳時間

重傳時間需要講到 自適應重傳算法 ,一種計算重傳時間的算法,大致流程:

TCP通過採樣RTT的時間,進行加權平均,算出一個值,最終得到一個估計的重傳時間。

初始值:原始值
測量之後:RTO = RTTs + 4*RTTd
(RTTs:加權平均值,RTTd:偏差值)

因爲網絡是不斷變化的,所以重傳時間也會處於變動狀態。

基於反饋信息的快速重傳機制

快速重傳機制是這樣的:當接收方接收到一個序列號大於下一個所期望的報文段的時候,就會檢測到數據流中間丟失的間隔,然後發送冗餘的ACK,向發送者索要確實的間隔。當發送者收到一定數量的冗餘的ACK(稱爲重複ACK的閾值或dupthresh)之後,就不等定時器過期了,直接重傳丟失的 報文。

重複ACK的閾值通常爲3,一些非標準化的實現可基於當前的失序程度動態調整。

如下例所示:發送方的4、5、6、7都已經發送出去了,但是接收方接收到了5、6、7,少了4,會在分別收到5、6、7的時候都發一個3的ACK,向發送方索要下一個數據4。這樣發送方就收到到3個3的ACK了,於是就主動發起了4的重傳,不等待重傳計時器超時了:

帶選擇確認的重傳SACK

雖然重傳保證了數據的到達,但是重傳應該儘可能保證不重傳以正確接收到的數據,而SACK信息能更快速的實現空缺填補並且減少不必要的重傳。

隨着選擇確認選項的標準化[RFC2018],TCP接收端可以提供SACK的功能了,通過TCP頭部的累計ACK號字段來描述其接收到的數據。

每當緩存存在失序數據時,接收端就可以生成SACK,代表着緩存接收狀態地圖,這樣通過將緩存的接收狀態地圖發給發送方,發送方就很快可以知道是什麼數據丟失併發起重傳了。

這種重傳機制下, 窗口內的其他報文段也可以被接收確認 ,但只有在接收到等於窗口的左邊界的序列號時,窗口才會前移。這樣就減少了窗口內的不必要的重傳。

4.2.5.4、流量控制

流量控制指的是通過控制發送方和接收方的窗口大小,以使得接收方緩存中已接受的數據處理不過來時,通過減小發送方的窗口大小,讓接收方能有足夠的時間來接收數據包;或者是接收方比較空閒時,嘗試讓發送方調大窗口大小,以加快傳輸,合理利用空閒的網絡資源。

流量控制主要是通過TCP頭的窗口大小來調節的。發送端收到接收端的通告窗口之後,得知接收端可接收的數據量。

下面舉例來說明。

正常情況下,發送方左邊界每關閉一格,右邊界就打開一個,多一個可發送的單元:

我們知道接收端接收並確認數據之後,會放到緩存中,等待應用程序處理,如果應用程序一直沒有處理,最終會導致接收端沒有更多空間來存儲到達的數據了,如果應用程序一直沒有處理數據,那麼窗口右邊界可能就不會打開了,最終接收的窗口大小變爲0:

這個時候接收端就會發送一個零窗口通告(TCP ZeroWindow),告知發送端不要再發送數據了,我已經處理不過來了,於是發送方就暫停發送數據了,等待接收端的窗口更新(TCP Window Update)通知:

這樣,接收方就可以有時間來處理接收的數據了,等到有了足夠多的緩存之後,於是會給發送端傳輸一個窗口更新通知。

爲了避免由於窗口更新通知ACK丟失,到時雙方陷入等待的僵局,在發送方停止發送數據之後,會採用一個持續計時器間歇性的查詢接收端,給接收端發送窗口探測(TCP ZeroWindowProbe)請求,要求接收端返回 TCP ZeroWindowProbeAck ,看看是否窗口是否已經增加了:

4.2.5.5、擁塞控制

前面我們講到,可以通過滑動窗口大小來控制流量,從而爲接收方緩解壓力,避免不必要的丟包。

而擁塞控制,就需要用到擁塞窗口了。擁塞控制主要用於避免丟包和超時重傳。

反映網絡傳輸能力的變量稱爲擁塞窗口(congestion window),記爲cwnd。

可以理解爲 滑動窗口是爲接收方服務的,而擁塞窗口是爲整個網絡通道服務的,擁塞窗口大小又會受制於接收方滑動窗口大小,並且會因爲網絡原因進行調整。 因爲網絡通道中的任何一個環節都有可能影響整體的傳輸效率。

4.2.5.5.1、發送實際可用窗口

那麼我們發送端實際可用窗口應該是多少了,這裏我們記實際可用窗口大小爲W,那麼W爲接收端通知窗口awnd和擁塞窗口cwnd的較小者:

W = min(cwnd, awnd)

假設網絡沒有任何問題,並且帶寬足夠寬,數據包不會在傳輸過程遇到需要排隊等待的情況下,這種理想狀況下,也就是沒有網絡延遲,接收方收到一個數據包,立刻就ACK一個,立刻空出一個可傳輸單元,發送的實際可用窗口就是接受方的滑動窗口大小了,如下:

理想是很美好的,但是實際網絡情況是非常複雜的,TCP根本不知道里面會發生什麼情況,也許W還沒到達接收端滑動窗口大小,網絡中就因爲中間的瓶頸導致丟包了,那麼更加會增加重傳的頻率。所以爲了能減少丟包和超時重傳,需要有一些動態發送端窗口大小的策略。

4.2.5.5.2、發送端窗口調整策略

雖然可以通過接收方的ACK得到對方的接收窗口大小,但是因爲剛開始並不知道擁塞窗口是多少,所以只能以越來越快的速率不斷髮送數據,直到出現數據包丟失爲止。

通常TCP在建立新連接的時候會執行慢啓動,直到有包丟失,然執行擁塞避免算法進入穩定狀態。

慢啓動

初始窗口設爲IW(Initial Window, IW),IW=SMSS(發送方的最大段大小)。

先發送初始窗口大小的數據,沒有出現丟包,並且每收到一個ACK,慢啓動算法就會以min(N,SMSS)來增加cwnd的值。可見這是指數性的增長。

直到出現了網絡擁塞,出現丟包、超時重傳,說明已經到達了慢啓動的閾值ssthresh(slow start threshold),這個時候cwnd減少一半,並作爲新的ssthresh。

避免擁塞

一旦達到慢啓動的閾值之後,爲了得到更多的傳輸資源而不影響其他連接的傳輸,TCP實現了擁塞避免算法。一旦確定慢啓動閾值,TCP會進入擁塞避免階段,這個時候cwnd每次的增長值近似於成功傳輸的數據段大小。也就是說由原來慢啓動的指數增長,變爲了線性增長。

4.3、Socket編程

4.3.1、Socket是什麼

Socket是一個抽象層,主要是把TCP/IP層複雜的操作抽象爲幾個簡單的接口提供給應用層調用,進而實現應用進程在網絡中通信。Socket主要是端到端之間的傳輸協議(網絡層之上的協議)。因 爲Socket是一種高層的抽象網絡API,是一種端到端的通信,只能訪問到端到端協議之上的網絡層和傳輸層

Socket起源於Unix, 在Unix中,一切皆文件,Socket也不例外 ,是一種 打開-讀/寫-關閉 的模式實現的。在服務器和客戶端各自維護了一個文件。

4.3.2、基於TCP的Socket通信交互流程

我們先來看一下基本TCP客戶/服務器程序的套接字函數調用過程:

TCP Socket的文件結構

在內核中,Socket是一個文件,不過Socket對應的inode不是保存在硬盤上,而是在內存中,該inode指向了Socket在內核的Socket結構。內核的Socket接口主要由兩個隊列:發送隊列,接收隊列。

內核爲監聽套接字維護的兩個隊列

對於每個監聽Socket,內核都爲其維護了兩個隊列:

  • 未完成隊列(incomplete connection queue):這個隊列的套接字服務端正在等待完成TCP三次握手,處於SYN_RCVD狀態;
  • 已完成連接隊列(completed connection queue):完成了三次握手的Socket連接會進入這個隊列,處於ESTABLISHED狀態。

如下圖:

4.3.3、基於UDP的Socket通信交互流程

UDP不需要三次握手,所以不需要listen和connect,但是交互仍然需要IP和端口號,需要bind。

UDP不用維護連接狀態,所以不需要針對每個連接建立一組Socket,只需要一個就可以了。

以下是UDP的Socket通信交互流程圖:

到目前爲止,我們把物理層、數據鏈路層、網絡層、傳輸層主要的協議和功能都介紹了一遍。基於這些底層的協議棧支撐,我們可以很快的構建出應用層的程序,接下來我們簡單講一下應用層。

5、應用層

應用層位於操作系統用戶態運行,而我們前面講到的那層是運行在操作系統內核態的:

一般我們都是通過Socket網絡API來訪問內核態的各層的協議模塊。

常見的應用層協議如下:

HTTP
HTTPS
流媒體
P2P協議
WebSocket

在應用層,大部分的開發工程師可以大展拳腳。

  • HTTP協議的實現,最經典的莫過於Tomcat服務器了,關於具體的實現,可以參考這本書:《深入剖析Tomcat》;
  • Github上面有一個WebSocket協議的Java實現,感興趣的朋友可以研究下: Java-WebSocket

到這裏,由於本文已經兩萬多字了,這裏只做一些知識的延伸,就不進一步展開來講了。

更多關於應用層的相關介紹,我會後續更新,感興趣的朋友記得關注本公衆號進一步跟進學習交流哦。

這篇文章的內容就差不多介紹到這裏了,能夠閱讀到這裏的朋友真的是很有耐心,爲你點個贊。

本文爲 arthinking 基於相關技術資料和官方文檔撰寫而成,確保內容的準確性,如果你發現了有何錯漏之處,煩請高抬貴手幫忙指正,萬分感激。

大家可以關注我的博客: itzhai.com 獲取更多文章,我將持續更新後端相關技術,涉及JVM、Java基礎、架構設計、網絡編程、數據結構、數據庫、算法、併發編程、分佈式系統等相關內容。

如果您覺得讀完本文有所收穫的話,可以 關注 我的賬號,或者 點贊 吧,碼字不易,您的支持就是我寫作的最大動力,再次感謝!

關注我的公衆號,及時獲取最新的文章。

本文作者: arthinking

博客鏈接: https://www.itzhai.com/network/comprehend-the-underlying-principles-of-network-programming.html

兩萬字長文50+張趣圖帶你領悟網絡編程的內功心法

版權聲明: BY-NC-SA 許可協議:創作不易,如需轉載,請聯繫作者,謝謝!

References

UNIX網絡編程 卷1:套接字聯網API

TCP/IP詳解 卷1:協議(原書第2版). 機械工業出版社

謝希仁. 計算機網絡(第6版). 電子工業出版社

劉超. 趣談網絡協議. 極客時間

相關文章