計算機網路——多路複用與多路分解

鴨絨發表於2020-12-06

一、前言
  最近在看《計算機網路——自頂向下方法》這本書,讀了一部分之後發現,這真是一本非常不錯的計算機網路入門書籍,想要學習計算機網路的人可以去買來看看。今天剛讀到運輸層這一章,開頭詳細講解了運輸層的多路複用與多路分解,我覺得頗有收穫,所以寫篇部落格分享一下這一部分內容。

二、解析
 2.1 應用層、運輸層以及網路層的關係
  想要解析多路複用與多路分解,首先得大致瞭解一下計算機五層結構中,應用層、運輸層與網路層的關係。

網路層是五層結構中的第三層,它的作用就是在網路中提供端到端(即主機之間)的邏輯通訊;而運輸層的是五層結構中的第四層,它的作用是提供程式之間的通訊。應用層則是最頂層,作用是為使用者提供與網路打交道的介面。

應用層與運輸層之間通過套接字傳遞資料,套接字是運輸層與應用層的一箇中間媒介,位於兩層之間。運輸層接收到資料後,將它交付到正確的套接字中,應用層程式從的相應的套接字中獲取資料;反之應用層將資料交付到套接字,運輸層從套接字中收集資料。而網路層接收其他主機傳送的資料,去除首部資訊後交給運輸層,由運輸層定向到套接字;反之運輸層也將從套接字中收集的資料封裝後,交給網路層向下傳遞。

在這裡插入圖片描述
2.2 什麼是多路複用與多路分解
  這裡通過一個類比來理解這兩個概念:假設有兩個家庭A和B,各有10名家庭成員。假設這兩個家庭中的每一個成員,在每個星期都要給另一個家庭的10成員各寫一封信,所以A家庭每個星期都要有100封信送到B家庭,B家庭亦是如此。A家庭和B家庭各選出了一個負責人來處理這件事,假設A家庭的負責人是李明,而B家庭的負責人是韓梅梅。這兩個負責人每個星期都需要幹兩件事情:

收集每個家庭成員寫的信,並將它交給郵差,由郵差將信交到另一個家庭中;
郵差將信寄到家來時,負責人統一接收,並根據信上的收件人姓名,將信交給指定的家庭成員;
  多路複用的過程就好比負責人的要辦事情1,而多路分解就好比事情2。下面來看這兩個名稱的解釋:

多路複用:在資料的傳送端,傳輸層收集各個套接字中需要傳送的資料,將它們封裝上首部資訊後(之後用於分解),交給網路層;
多路分解:在資料的接收端,傳輸層接收到網路層的報文後,將它交付到正確的套接字上;
  家庭成員就好比套接字,而這兩個負責人就好比主機中的運輸層,郵差可以理解為網路層。負責人將寄來的信分發給家庭成員的過程,類似於運輸層將資料包分發給指定的套接字;而郵差從一個家庭送信到另一個家庭,也可以類比為網路層中主機之間的通訊。

那這兩個過程具體是如何工作的呢?每個程式可以有多個套接字,運輸層如何知道要將資料交付給哪一個套接字呢?這裡我們需要明確複用/分解的要求:

每個套接字都有唯一標識;
每一個傳遞到運輸層的報文段,都包含一些特殊欄位,來指明它需要交付到的套接字;
  對於每一個套接字,都能被分配一個的埠號。所以,上述要求2中所說的特殊欄位就是源埠號欄位和目的埠號欄位(對於TCP和UDP,這個還有一些其他特殊欄位,將在後面講解)。所以我們知道運輸層如何實現分解服務了:當一個報文段到達運輸層時,運輸層檢測報文段中的埠號,根據埠號,將其定向到指定的套接字中。然後資料通過套接字即可進入套接字對應的程式。

2.3 無連線的多路分解與多路複用
  上面只是講解了一下這兩個概念的一般形式,但是在具體的實現中要稍微複雜一些,不同協議所使用的套接字也有所區別,下面我們來看看UDP協議中的多路分解與多路複用。

我們知道,UDP是一個面向無連線,不可靠的運輸層協議,它盡最大努力傳輸資料,但是不保證資料是否到達,或者是否按順序到達,它要做的僅僅是將資料發出,至於發出後如何,它不會在意。

當程式需要傳送UDP資料包時,首先要建立一個UDP套接字,然後應用層通過這個UDP套接字將資料傳遞到運輸層,運輸層為資料加上源埠號以及目的埠號,封裝成資料包後交給網路層,網路層再為資料包封裝上源IP以及目的IP。由於UDP協議僅僅只是將資料發出,所以對於UDP報文來說,最重要的就是目的地址的所在。可能正是因為這個原因,一個UDP套接字的標識就是目的IP+目的埠號。因此對於多個不同的UDP資料包,只要它們的目的IP+埠號相同,就算源地址不同,也會在目的主機中被定向到同一個UDP套接字中,被同一個程式所接收。目的IP決定了資料包將要傳送到哪臺主機,而目的埠號為運輸層的的分解提供了標識。

這裡可能就會有些疑問了,既然這樣,那UDP報文為什麼需要包含源IP+源埠號呢(IP在網路層被封裝)?這是因為UDP是無連線的,當接收到一個UDP報文時,可能想要回送一個報文,這時候不知道源在何處將無法實現。所以當需要向源主機回覆報文時,只需提取UDP報文中的源IP和源埠號,然後將它們作為目的IP+目的埠號即可實現。

在這裡插入圖片描述
2.4 面向連線的多路複用與多路分解
  既然有無連線的實現,自然就有連線的實現。運輸層乃至整個計算機網路最著名的協議——TCP協議,就是一個面向連線的協議。TCP是一個面向連線,可靠的運輸層協議。既然面向連線,那它就需要關注兩個方面:源地址和目的地址,因為TCP的傳輸,需要兩邊協作完成。正因為TCP的特性,導致TCP的套接字和UDP也有所區別。TCP套接字的標識是一個四元組,即源IP+源埠+目的IP+目的埠(UDP是目的IP+目的埠)。我們通過一個例項來講解TCP的多路複用/分解過程。

大部分人使用最多的應用層協議應該就是HTTP協議,而它就是基於TCP協議實現的,當我們在瀏覽器中請求一個頁面時,將經歷以下過程:

Web伺服器監聽80埠,等待客戶端的連線;
使用者在瀏覽器輸入一個URL,回車後,瀏覽器程式建立一個套接字,此套接字由伺服器IP,伺服器80埠,本地IP,本地程式埠,四部分標識;
瀏覽器程式將資料通過此套接字從應用層傳入運輸層,運輸層為TCP報文加上首部(包括源埠和目的埠)後,交給網路層,網路層為其加上網路層首部(包括源IP和目的IP)傳輸傳輸到Web伺服器;
Web伺服器的接收到此資料包後,檢測到資料包請求的是埠80,於是檢測80埠正在執行,且允許連線,則建立一個新的套接字,此套接字由伺服器IP,伺服器80埠,源IP,源埠,這四部分標識;
此後到達的Web伺服器的資料包,若以上四部分完全相等,則將進入此套接字中;
  既然TCP套接字是由源IP+源埠+目的IP+目的埠四部分標識,不難想到,我們無法在同一臺主機上,依靠同一個埠,向伺服器的某個一個埠建立兩個TCP連線,因為這樣將無法區分兩個連線。以下通過Java進行測試:

public static void main(String[] args) throws IOException {
    // 建立第一個TCP連線,結果正常
    Socket socket1 = new Socket("www.baidu.com", 80, null, 8888);

    // 建立第二個連線,與上一個連線的目的IP,目的埠,以及本地埠均相同
    // 結果丟擲異常:
    //  java.net.BindException: Address already in use: JVM_Bind
    Socket socket2 = new Socket("www.baidu.com", 80, null, 8888);
}

以上程式碼執行時丟擲異常,提示地址已經被使用,但是修改第二個socket物件的本地埠後,異常消失,驗證了上面的結論。

相關文章