一件不可能的工作,還是五件做得到的工作?
到目前為止,你已經認識了許多活動的零件。封包是一張寫了位址的明信片。核心一跳一跳地轉送這些明信片。連結有它的頻寬,而訊號要對抗一道由地理決定、固定的傳播延遲。而且每台合作的裝置都必須遵守一份共用的通訊協定——一種格式、一個順序、以及一組動作。現在退一步問那個嚇人的問題:怎麼可能有人一口氣把整件事設計出來?讓你瀏覽器裡的一次點擊,從地球另一端的伺服器抓回一個網頁,這牽涉到 Wi-Fi 無線電、光纖、數十台路由器、壅塞、遺失的封包、名稱查詢、以及加密。這對單一程式、單一團隊、單一協定來說,遠遠太多了。
出路是網路學裡——也是許多工程領域裡——最強大的一個觀念:分層。你不是寫一個巨大的程式,而是疊起一疊層,每一層只把一件工作做好、對上面那一層提供一個整潔的服務、並悄悄使用下面那一層的服務。網頁瀏覽器不知道、也不在乎 Wi-Fi 怎麼運作;Wi-Fi 不知道、也不在乎它承載的是一個網頁還是一封電子郵件。每一層信任下面那層會把東西送到,並服務上面那層。
一份服務合約,而非一段交情
魔法成分是兩層之間的介面——把它想成一份小而固定的合約。一層承諾提供一項服務(「把你的資料和一個目的地交給我,我會試著送到」),但完全不承諾它「如何」實現這個承諾。只要合約不變,你就能把一層的內部整個拆掉、換上一套截然不同的機制,而上面或下面的層連察覺都不會。這就是為什麼協定堆疊如此強韌:各層是解耦的。
一個你能親身感受的具體例子:你筆電最上層說 HTTP 來抓一個網頁。它底下坐著一層可靠的位元組管線層。今天早上你的封包搭著那條管線走 Wi-Fi;下午你插上了一條乙太網路線;在火車上則是 5G。瀏覽器程式碼一行都沒改。每一段存取連結都是一個不同的底層,履行同一份合約——「把這些位元送過一跳」——所以疊在上頭的一切就只是繼續運作。這份可抽換性,正是分層帶來的全部回報。
層層嵌套的信封:封裝如何運作
如果每一層都各自獨立,那你的資料究竟是怎麼一路下到堆疊底、再在對端一路上來的?機制就是封裝,畫面是一層層嵌套的信封。當你的訊息往發送堆疊向下走時,每一層都把它從上面收到的東西,用自己的標頭——一小段給對方機器同一層看的控制位元組——包起來。下面那層把整個包裹(標頭加上裡面所有東西)當成看不透的貨物,再包一層。資料從不被改寫;它只是被包了又包、包了又包。
每個標頭只攜帶那一層所需的資訊,多一點都沒有。傳輸層標頭帶著連接埠號與序號,好讓資料落進正確的程式、以正確的順序排列。網路層標頭帶著來源與目的 IP 位址,好讓路由器能轉送它。連結層標頭帶著MAC 位址,供下一個單跳使用。在接收主機端,整套流程倒過來跑——解封裝:每一層剝下並讀取屬於自己的標頭、依其行動、再把裡頭的東西往上交給下一層,直到那則赤裸的訊息在最頂端冒出來,與當初送進去時一模一樣。
DOWN the sending stack (each layer adds its own header):
app data .................................. [ HTTP request ]
+ transport header .............. [ TCP | HTTP request ]
+ network header ........ [ IP | TCP | HTTP request ]
+ link header ... [ ETH | IP | TCP | HTTP request | ETH-trailer ]
\___ this is what actually goes on the wire ___/
UP the receiving stack: peel ETH, then IP, then TCP -> HTTP request again.誰讀哪個信封、在哪裡讀
這裡有個微妙又漂亮的地方。中途的裝置不會打開每一個信封——它只拆到自己工作所需的那一層為止,然後就停。這正是核心能保持簡單的原因。一台交換器是第二層的盒子:它讀連結層標頭(MAC 位址)就轉送,連裡頭的 IP 看都不看一眼。一台路由器是第三層的盒子:它剝掉連結層標頭、讀 IP 標頭來決定下一跳,再為新的一跳裹上一個全新的連結層標頭、送出去。只有那兩台端系統——發送端與最終接收端——才會一路爬到傳輸層與應用層、讀那則訊息本身。
- 你筆電的瀏覽器組好一個 HTTP 請求,交給傳輸層。
- 傳輸層用一個 TCP 標頭(連接埠、序號)把它包起來,交給網路層。
- 網路層用一個 IP 標頭(來源與目的 IP)把它包起來,交給連結層。
- 連結層用一個乙太網路(或 Wi-Fi)標頭把它包起來,把位元送上線路。
- 路徑上每台路由器剝掉連結層標頭、讀 IP 標頭、為下一跳重新裹上一個新的連結層標頭——裡頭的 TCP 與 HTTP 信封始終封著。
- 在伺服器端,各層反向逐一剝開,直到那則赤裸的 HTTP 請求抵達網頁程式,它再以同樣的方式回覆回來。
留意這一切包裹的誠實代價:每個標頭都是額外開銷——不是你資料的額外位元組,卻在每一個封包上都被送出去。在一個極小的封包上,這些標頭可能佔了過線資料的一大部分。這是模組化的代價,而設計者判斷它非常值得付。這也是為什麼封包、訊框、資料報、區段這些詞都指「帶標頭的一塊」,只是位於不同層——它們不過是同一份資料,在嵌套的不同深度看到的樣子。
兩張著名的地圖:OSI 對上 TCP/IP
因為分層如此重要,有兩張參考模型因為替各層命名而出了名。OSI 模型是經典的七層教科書地圖(實體層、資料連結層、網路層、傳輸層、會議層、表現層、應用層),由 ISO 標準組織在大約 1984 年發佈。它是一個絕佳的教學工具,給了所有人共用的詞彙——當一位工程師說「那是第三層的問題」時,他指的就是 OSI 編號中的網路層。
但這裡有個你必須一路帶著走的誠實真相:真正的網際網路不跑 OSI。它跑的是 TCP/IP 模型,一個更精簡的四層或五層堆疊(連結層、用 IP 的網路層、用 TCP/UDP 的傳輸層、以及應用層),早在 OSI 完成之前就已經在現場運作了。OSI 最上面三層(會議層、表現層、應用層)在實務上就直接合併成一個「應用層」。所以把 OSI 當作你用來學習的地圖、大家用來度量的共用尺,把 TCP/IP 當作真正把眼前這一頁送到你面前的那台機器。