jojoma· 發(fā)布于Ruby China --?
緣起是我想了解一下底層網(wǎng)絡(luò)的原理,看了幾天《TCP/IP 詳解 卷一》,但是這部書讀起來十分吃力。這時候正好看到hn談到這篇Network protocols。所以特地翻譯過來,希望也能有人從中受益。本人知識有限,在一些譯文處補充了原文用詞來輔助理解,翻譯不對之處歡迎指正。
網(wǎng)絡(luò)棧技術(shù),完成了幾件看起來不可能的任務(wù):它在不可靠網(wǎng)絡(luò)基礎(chǔ)上,實現(xiàn)了可靠數(shù)據(jù)傳輸,傳輸過程中鮮有可察覺的問題出現(xiàn)。它在網(wǎng)絡(luò)擁塞時能夠平滑適應(yīng)。它給網(wǎng)絡(luò)中上十億的活動節(jié)點提供地址。它能在受損的網(wǎng)絡(luò)基礎(chǔ)設(shè)施中,往正確的路線發(fā)送數(shù)據(jù)包,即使是亂序到達在接收端,也能將數(shù)據(jù)包重新裝配成正確的順序。它適應(yīng)了深奧的模擬analog硬件需求,比如以太網(wǎng)電纜兩端的電荷平衡。網(wǎng)絡(luò)技術(shù)工作得如此之好,以至于網(wǎng)絡(luò)用戶們從沒聽說過它們,甚至大部分編寫程序的工程師們也不知道底層究竟是如何工作的。
網(wǎng)絡(luò)路由
在古老的模擬電話年代,打電話意味著建立一個連接你和你朋友的電話的、持續(xù)的、電子連接。仿佛真的有根電話線,直接在你和朋友之間工作。當(dāng)然實際沒有這根線——電話連接經(jīng)過了復(fù)雜的交換系統(tǒng)——但是這個連接電子上等效于一根線。
互聯(lián)網(wǎng)的節(jié)點數(shù)太多了,不能套用這個方式。我們不可能給每一臺機器與另一臺機器都建立一個直接連接的、不被打斷的路線用于通信。
相應(yīng)地,數(shù)據(jù)是由一個路由器發(fā)送給下一個路由器,每次傳輸都使數(shù)據(jù)離目的地更近一步,整個鏈式傳遞過程像傳桶隊列 Bucket-brigade。舉個例子,從我的筆記本到 google.com 之間,途徑的每個路由器都連接著許多其他路由器,各自維護著一組不精確的路由表,路由表表示出哪些路由器更靠近互聯(lián)網(wǎng)的哪一部分。當(dāng)一組目的地是 google.com 的數(shù)據(jù)包到達時,路由器在路由表上進行快速查找,并發(fā)送數(shù)據(jù)包到更靠近谷歌的地方。數(shù)據(jù)包很小,所以傳輸鏈上路由器之間的數(shù)據(jù)傳遞耗時也極短。
路由可以拆分成兩個子問題。第一個是,地址:數(shù)據(jù)的目的地用什么表示?這個由IP 協(xié)議,其中的IP地址來處理。IPv4,作為最廣泛使用的IP版本,提供的地址空間只有32位,已經(jīng)全部被分配,所以添加一個新的節(jié)點到公開的互聯(lián)網(wǎng)只能重用已存在的IP地址。IPv6,允許使用 2 ^ 128 個地址(大約 10 ^ 38),在2017年只有20%左右的采用率。
既然已經(jīng)解決了地址的問題,我們現(xiàn)在需要知道如何在互聯(lián)網(wǎng)上路由數(shù)據(jù)包到其目的地。路由是非常快的,沒有時間在遠端數(shù)據(jù)庫中查詢路由信息(所以只能在本地)。這速度有多快呢?舉個例子, Cisco ASR 9922 路由器擁有著每秒最大160TB的處理能力。假設(shè)數(shù)據(jù)包是完整的1.5KB(12000位bit),那么每秒有133億個數(shù)據(jù)包流經(jīng)這個19英寸小機器。
為了快速路由,路由器維護著指示著到達其他IP地址組路徑的路由表。當(dāng)一個新的數(shù)據(jù)包到達時,路由器查詢路由表,告知這個數(shù)據(jù)包最接近目的地的路由器。這個路由器會把數(shù)據(jù)包發(fā)送到下一個路由器,然后再往下一個發(fā)送。BGP協(xié)議的工作就是,在不同路由器之間溝通,保證路由表是最新的。
轉(zhuǎn)換成數(shù)據(jù)包packet switching
如果互聯(lián)網(wǎng)的工作方式是,路由器互相之間沿著線路傳遞數(shù)據(jù),那如果數(shù)據(jù)很大會發(fā)生什么?比如說,如果我們請求一個88.5MB的視頻The Birth & Death of JavaScript。
我們可以試試設(shè)計一個網(wǎng)絡(luò),在這當(dāng)中88.5MB的文件直接由網(wǎng)絡(luò)服務(wù)器發(fā)送給第一個路由器,然后第二個,如此下去。不幸的是,這樣的網(wǎng)絡(luò)不可能在互聯(lián)網(wǎng)級別的規(guī)模下工作,甚至內(nèi)網(wǎng)規(guī)模下都很難。
首先,計算機的存儲量是有限的。如果一個給定的路由只有88.4MB的可用緩存,那它就不能存儲這個88.5MB的視頻文件。這個數(shù)據(jù)會被直接丟棄,甚至更糟,我完全不知道這件事的發(fā)生。如果路由器是如此忙碌以至于丟棄了數(shù)據(jù)之后,都沒時間告訴我它丟棄了數(shù)據(jù)。
其次,計算機都是不可靠的。有時,路由節(jié)點崩潰。有時,船只的??意外損壞水下光纜,導(dǎo)致互聯(lián)網(wǎng)一大部分不可訪問。
基于這些提到的以及更多原因,我們不會在互聯(lián)網(wǎng)中傳遞88.5MB大小的消息。相反,我們把數(shù)據(jù)拆分成許多數(shù)據(jù)包,大小通常在1.4KB左右。我們的視頻文件將被拆分成63214左右個分隔的數(shù)據(jù)包用于傳輸。
亂序數(shù)據(jù)包
使用抓包工具Wireshark觀察The Birth & Death of JavaScript的一次真實傳輸,我能看到接收了一共 61807 個數(shù)據(jù)包,每個 1432 字節(jié)。兩者相乘,我們得到88.5MB,這正是視頻文件的大小。(這不包括其他協(xié)議的開支,如果包含的話,數(shù)字會更大些)
這次傳輸是基于HTTP,一種基于TCP的協(xié)議。傳輸花了 14 秒,所以平均每秒有 4400 個數(shù)據(jù)包到達,或者說每個數(shù)據(jù)包花了250毫秒到達。在這14秒中,我的計算機接收了所有 61807 個數(shù)據(jù)包,也許不是按順序接收,在接收過程中進行重新裝配成完整文件。
TCP數(shù)據(jù)包重新組裝使用的是一種可想象的最簡單的機制:計時器。每個數(shù)據(jù)包在發(fā)送時都被賦予一個序列號。在接收端,數(shù)據(jù)包按序列號排列。一旦他們?nèi)颗藕庙樞颍瑳]有間隔,我們就知道整個文件都接收到了沒有丟失。
(真實情況下,TCP序列號并非是每次增加一的整數(shù),但這個細節(jié)在本文中并不重要。)
就算如此,那我們怎么知道什么時候文件接收完成呢?TCP對此一無所知,這個是更高級別協(xié)議的職責(zé)。舉個例子,HTTP響應(yīng)response中包含一個叫做Content-Length的頭部,說明了返回響應(yīng)的總長度。客戶端讀取這個頭字段,然后一直讀取TCP數(shù)據(jù)包,重新裝配它們,直到達到了此頭字段指定的數(shù)據(jù)大小。這是為什么HTTP頭部(以及其他大多數(shù)協(xié)議的頭部)比響應(yīng)載荷payload率先到達的原因之一,否則我們都不能知曉載荷的大小。
當(dāng)我們在說客戶端的時候,我們實際在說整個接收數(shù)據(jù)的計算機。TCP組裝是在內(nèi)核中完成的,所以瀏覽器、curl和wget這樣的應(yīng)用不需要手動重新裝配TCP數(shù)據(jù)包。但是內(nèi)核不處理HTTP,所以應(yīng)用需要理解Content-Length頭字段并知曉需要讀取多少字節(jié)。
有了序列號和數(shù)據(jù)包重新排序,我們能傳輸大量數(shù)據(jù),即使數(shù)據(jù)包是亂序的。但如果一個數(shù)據(jù)包在傳輸中丟失了,在HTTP響應(yīng)中留下一個空洞怎么辦?
傳輸窗口transmission winsow與慢啟動slow start
我開著Wireshark下載了The Birth & Death of JavaScript。查看抓包記錄,我能看到數(shù)據(jù)包一個接一個被成功接收。
舉個例子,一個序列號為 563321 的數(shù)據(jù)包到達了。像所有TCP數(shù)據(jù)包一樣,它包含了一個“下一個包序號”,指示著接下來一個數(shù)據(jù)包的序列號。這個包的“下一個包序號”是 564753。傳輸過程中下一個數(shù)據(jù)包,的序列號確實是 564753,所以一切正常。這發(fā)生了數(shù)千次,隨著連接開始加速傳輸數(shù)據(jù)。
有時候,我的計算機發(fā)出一條消息給服務(wù)器說,打個比方,“我已經(jīng)接收了包序號小于或等于 564753 的所有數(shù)據(jù)包。”這稱為ACK,確認acknowledgement的簡寫,我的計算機確認接收服務(wù)器的數(shù)據(jù)包。在一個新的連接中,Linux內(nèi)核每接收10個數(shù)據(jù)包后,就發(fā)出一個ACK。數(shù)字 10 由常數(shù)TCP_INIT_CWND控制,常數(shù)在內(nèi)核源碼中被定義。
(TCP_INIT_CWND里的 CWND 表示 擁塞窗口 congestion window:同一時刻可以傳輸?shù)臄?shù)據(jù)總大小。)如果網(wǎng)絡(luò)變得擁塞(超負荷),窗口大小減小,從而減慢數(shù)據(jù)包的傳輸。
十個數(shù)據(jù)包是大約14KB,所以一開始的速度限制是14KB。這是TCP慢啟動的部分:連接建立時擁塞窗口很小。如果沒有數(shù)據(jù)包丟失,接受者將持續(xù)增加擁塞窗口,允許同時傳輸更多數(shù)據(jù)包。
最終,將會有數(shù)據(jù)包丟失,所以接收窗口會減小,減慢傳輸。像這樣自動調(diào)整擁塞窗口,以及其他參數(shù),數(shù)據(jù)發(fā)送者和接收者讓數(shù)據(jù)傳輸最大化利用網(wǎng)絡(luò)帶寬。
這發(fā)生在連接的兩端:每端都發(fā)出ACK確認消息,也維護各自的擁塞窗口。不對稱窗口允許協(xié)議用不對稱的上下行帶寬,最大化利用網(wǎng)絡(luò)連接,就像大多數(shù)住宅區(qū)和移動網(wǎng)絡(luò)連接一樣。
可靠傳輸
計算機是不可靠的,由計算機組成的網(wǎng)絡(luò)更加不可靠。在像互聯(lián)網(wǎng)這樣的大規(guī)模網(wǎng)絡(luò)中,失敗是操作中常見的一部分,并且必須得到良好處理。在一個數(shù)據(jù)包網(wǎng)絡(luò)中,這意味著重傳:如果客戶端接收了序號1和3的數(shù)據(jù)包,但沒有接收到2,那么它需要要求服務(wù)器重新發(fā)出丟失的數(shù)據(jù)包。
當(dāng)每秒接收上千數(shù)據(jù)包時,比如下載我們的88.5MB視頻時,錯誤幾乎百分之百會產(chǎn)生。為了給大家展示,讓我們打開Wireshark。很多數(shù)據(jù)包接收,一切看起來很正常。每個數(shù)據(jù)包都有一個“下一個包序號”,緊接著一個帶著這個序號的數(shù)據(jù)包。
突然問題出現(xiàn)了。第 6269 個數(shù)據(jù)包的“下一個包序號”是 7208745,但那個數(shù)據(jù)包并沒有到達。相反,序列號為 7211609 的數(shù)據(jù)包到達了。這是一個亂序數(shù)據(jù)包:有東西丟失了。
我們很難說出究竟什么出了問題。也許互聯(lián)網(wǎng)中的一個中間路由器超負荷了,也許是我的本地路由器超負荷了。也許有人打開了微波爐,產(chǎn)生了電磁干擾,減慢了我的無線連接。無論如何,這個數(shù)據(jù)包丟失了,唯一的跡象是意外接收到的數(shù)據(jù)包。
TCP并沒有特別的“我丟失了一個數(shù)據(jù)包”消息。相反,ACK消息會被巧妙地復(fù)用來表明數(shù)據(jù)丟失。任何亂序的數(shù)據(jù)包,會導(dǎo)致接收者重復(fù)確認最后的“正確的”數(shù)據(jù)包——正確順序的最后一個。實際上,接收者說的是:“我確認接收到了數(shù)據(jù)包5。在那之后我也接收到了別的數(shù)據(jù),但我知道那不是數(shù)據(jù)包6,因為它并不匹配數(shù)據(jù)包5的下一個包序號。”
如果只是兩個數(shù)據(jù)包在傳輸時調(diào)換了順序,這會導(dǎo)致一次額外的ACK,等到亂序數(shù)據(jù)包接收到之后一切就能正常繼續(xù)下去。但是如果有個數(shù)據(jù)包真的丟失了,意外數(shù)據(jù)包將會一直到達,因而接收者會持續(xù)發(fā)出重復(fù)的、最后一個正常數(shù)據(jù)包的ACK消息。這會導(dǎo)致上百個重復(fù)的ACK消息。
當(dāng)數(shù)據(jù)發(fā)送者一下看到三個重復(fù)ACK消息,它就假定緊接著的數(shù)據(jù)包丟失了,并進行重新傳輸。這被稱為TCP快速重傳,因為它比以前的基于超時的做法要快一些。有趣的是,協(xié)議自身不會顯式地去說“請立即重傳這個消息!”相反,多個ACK消息從協(xié)議自然產(chǎn)生,作為重傳的觸發(fā)器。
(一個有意思的思維實驗:如果一部分重復(fù)的ACK消息也丟失了,沒能到達數(shù)據(jù)發(fā)送者,會發(fā)生什么?)
-- 重傳甚至在網(wǎng)絡(luò)正常工作時都十分常見。在對下載88.5MB視頻進行抓包的過程中,我看到了
-- 因為持續(xù)性成功傳輸,擁塞窗口迅速增大到了將近1MB。
-- 數(shù)千數(shù)據(jù)包按順序出現(xiàn),一切正常。
-- 一個數(shù)據(jù)包到達順序不正確。
-- 數(shù)據(jù)繼續(xù)以每秒幾MB的速度涌入,但丟失的數(shù)據(jù)包依舊沒出現(xiàn)。
-- 我的計算機發(fā)出了不少重復(fù)的最后正常數(shù)據(jù)包的ACK消息,但內(nèi)核也存下待處理的亂序數(shù)據(jù)包,以備后續(xù)的重新組裝。
-- 服務(wù)器接收到了重復(fù)的ACK,并重新發(fā)送了丟失的數(shù)據(jù)包
-- 我的客戶端發(fā)出之前丟失的數(shù)據(jù)包,以及后續(xù)數(shù)據(jù)包的確認接收的消息。簡單確認最近的數(shù)據(jù)包即可,它會隱式地確認之前所有的數(shù)據(jù)包都被接收。
-- 傳輸繼續(xù),但由于丟失的數(shù)據(jù)包,擁塞窗口變小了。
這就是正常情況,這些在每次我對完整下載進行抓包時都會產(chǎn)生。TCP在自己的職責(zé)上做得是如此出色,以至于我們在日常使用中從沒考慮過網(wǎng)絡(luò)是不可靠的,盡管在正常情況下網(wǎng)絡(luò)都會例行性地失敗。
物理網(wǎng)絡(luò)
所有這些網(wǎng)絡(luò)數(shù)據(jù),都必須通過像銅線、光纜、無線電這樣的物理媒介進行傳輸。而在物理層協(xié)議之中,以太網(wǎng)最為著名。它在互聯(lián)網(wǎng)興起之初的流行,導(dǎo)致了我們在設(shè)計其他協(xié)議的時候必須適應(yīng)它的局限。
首先,讓我們把物理細節(jié)弄清楚。以太網(wǎng)與 RJ45 接頭關(guān)系最緊密,后者看起來像更大的八針eight-pin版本的四針手機插孔four-pin phone jacks。以太網(wǎng)也連接著cat5(或cat5e,或cat6,或cat7)電纜,該電纜包含了擰成4對的8根電線。其他媒介也存在,但我們在家中最有可能遇到的就是這些:裹在保護套下的8根電線,以及與之相連的8針插頭。
以太網(wǎng)是一個物理層協(xié)議:描述了位信息如何轉(zhuǎn)換成電線中的數(shù)字信號。它也是一個鏈路link層協(xié)議:描述了兩個節(jié)點之間的直接連接。然而,這是單純的點對點,對網(wǎng)絡(luò)中數(shù)據(jù)是如何路由的并不關(guān)心。以太網(wǎng)這里沒有TCP連接中的連接概念,也沒有IP地址中的可重新分配的地址概念。
作為一個協(xié)議,以太網(wǎng)有兩個主要的工作。第一,每個設(shè)備需要意識到它連接著一些東西,并且連接速度這樣的參數(shù)需要協(xié)商。
第二,一旦鏈路link建立,以太網(wǎng)需要攜帶信息。像更高層次的TCP和IP協(xié)議一樣,以太網(wǎng)的數(shù)據(jù)也拆分成數(shù)據(jù)包。數(shù)據(jù)包的核心是數(shù)據(jù)幀,幀有1.5KB的載荷,外加22字節(jié)的頭部信息。頭部信息中包含源MAC地址和目的地MAC地址,載荷長度,以及校驗和checksum這樣的信息。這些字段令人熟悉:工程師常常需要處理地址、長度以及校驗和,我們也知道為什么它們是必須的。
數(shù)據(jù)幀接著被其他層的頭數(shù)據(jù)包裹起來,構(gòu)造出完整的數(shù)據(jù)包。這些頭部數(shù)據(jù)很...奇怪。它們已經(jīng)開始和模擬電路系統(tǒng)的底層現(xiàn)實發(fā)生碰撞了,所以我們絕不想把這些數(shù)據(jù)放到軟件協(xié)議中去。一個完整的以太網(wǎng)數(shù)據(jù)包包含:
-- 序言preamble,由56位交替的0和1構(gòu)成(7字節(jié))。設(shè)備使用這個來同步時鐘,有點像人們數(shù)數(shù)發(fā)令“1-2-3-開始!”計算機不能數(shù)數(shù)超過1,所以他們通過說“10101010101010101010101010101010101010101010101010101010”來同步
-- 一個8位(1字節(jié))起始幀分隔符,通常是十進制數(shù)字171(二進制表示是10101011)。它標(biāo)識了序言的結(jié)尾,注意分隔符中開始還在重復(fù)“10”,直到末尾有個“11”。
-- 核心數(shù)據(jù)幀,包含了源地址、目標(biāo)地址、載荷等等,如前所述。
-- 一個96位(12字節(jié))的數(shù)據(jù)包間隔,其中的行是留空的。大膽猜測一下,這是留給設(shè)備休息的,因為它們很累了。
總結(jié)一下上面:我們想要傳輸1.5KB數(shù)據(jù)。我們添加22字節(jié)的包含源地址、目標(biāo)地址、數(shù)據(jù)大小以及校驗和的頭信息以創(chuàng)建數(shù)據(jù)幀。我們再添加額外的22字節(jié)的數(shù)據(jù),為了適應(yīng)硬件需求,這些構(gòu)成了完整的以太網(wǎng)數(shù)據(jù)包。
你也許會以為以太網(wǎng)已經(jīng)是網(wǎng)絡(luò)技術(shù)棧的最底層了。不是這樣,但事情確實變得更奇怪了,因為模擬世界的對技術(shù)的影響更甚 pokes through even more。
現(xiàn)實世界中的網(wǎng)絡(luò)
數(shù)字系統(tǒng)并不存在,一切都是模擬的。
假設(shè)我們有一個5伏特 CMOS 系統(tǒng),(CMOS是一種數(shù)字系統(tǒng),不熟悉也沒關(guān)系。)這意味著,完全開啟fully-on的信號將是5伏特,完全關(guān)閉的信號是0伏特。但是沒有信號是完全開閉的,物理世界不這樣工作。實際上,我們的5伏特 CMOS 系統(tǒng),會把任何高于1.67伏特的信號看做1,低于1.67伏特的信號看做0。
(1.67是5的1/3。我們不用關(guān)心為什么分界線在1/3。當(dāng)然如果你想深究,這里有維基百科說明。另外,以太網(wǎng)不是CMOS,甚至跟CMOS都沒有關(guān)系,但CMOS和它的1/3分界線能用來做一個簡單說明make for a simple illustration)
我們的以太網(wǎng)數(shù)據(jù)包必須經(jīng)由一條物理線,也就是改變電線中的電壓。以太網(wǎng)是一個5伏特的系統(tǒng),所以我們會天真地以為,以太網(wǎng)協(xié)議中的1位bit是電線中的5伏特,0位是0伏特。但是有兩個問題:首先,電壓范圍是-2.5伏特到+2.5伏特。其次,更奇怪的是,每組8位信息在到達電線之前,都會被拓展成10位。
8位可以有256種取值,10位有1024種取值,所以可以想象有張表在它們之間映射。每個8位的字節(jié)能被映射成4種10字節(jié)的信息,后者到達接收終點之后會被還原成同一個8位字節(jié)。舉個例子,10位的值 00.0000.0000 也許映射到 8位 0000.0000。但是也許 10位值 10.1010.1010 也能映射到同一個8位字節(jié)。當(dāng)以太網(wǎng)設(shè)備不管看到 00.0000.0000 還是 10.1010.1010,它都能理解這是字節(jié)0(二進制 0000.0000)。
(警告:下面可能需要一些電子電路知識)
上面這種映射的存在,是為了服務(wù)一個極其模擬的需求 extremely analog need:平衡設(shè)備中的電壓。假設(shè)這種8位到10位的編碼不存在,并且我們需要發(fā)送的數(shù)據(jù)恰好都是二進制1。以太網(wǎng)的電壓范圍是-2.5伏特到+2.5伏特,所以我們會使以太網(wǎng)線的電壓維持在+2.5伏特,繼而一直從線的另一端吸引電子過來pulling electrons。
為什么我們要關(guān)心一端從另一端獲取電子呢?因為模擬世界是混亂的,可能會產(chǎn)生各種各樣意外的影響。舉個例子,這樣會給低通濾波器中使用的電容器充電,使得信號電平中產(chǎn)生偏移,最終導(dǎo)致位錯誤。這些錯誤需要時間積累,但我們顯然不希望,僅僅因為我們傳輸?shù)亩M制1比0多,兩年之后網(wǎng)絡(luò)設(shè)備中突然開始產(chǎn)生數(shù)據(jù)錯誤。
(有關(guān)電子電路的說到這里)
通過使用8位-10位 編碼,以太網(wǎng)能保持電線中的0和1的平衡,即使我們要發(fā)送的數(shù)據(jù)都是1或者都是0。硬件會檢測0和1的比例,映射要發(fā)送的8位字節(jié)到不同的10位信號,以達到維持電荷平衡。(新的以太網(wǎng)標(biāo)準,如10GB以太網(wǎng),使用不同的更復(fù)雜的編碼系統(tǒng))
到此打住,因為我們談?wù)摰囊呀?jīng)超出編程的范圍了,但是必須要說明的是,還有更多協(xié)議相關(guān)問題是為了適應(yīng)物理層。在許多情況下,解決硬件問題的方法,都在軟件中實現(xiàn),比如上文使用8位-10位編碼來修正直流偏移DC offset。這對我們這樣的工程師來說可能有點尷尬:我們習(xí)慣于假裝軟件生活在一個完美的柏拉圖式的世界中,沒有物理上庸俗的缺陷devoid of the vulgar imperfections of physicality。事實上,一切都是模擬的,適應(yīng)這種復(fù)雜性是每個人的工作,當(dāng)然也包括軟件。
相互聯(lián)接的網(wǎng)絡(luò)棧
互聯(lián)網(wǎng)協(xié)議族最好理解為一組層的集合。以太網(wǎng)提供物理數(shù)據(jù)傳輸以及兩個點對點設(shè)備之間的鏈路。IP提供了地址層,允許路由器和大規(guī)模網(wǎng)絡(luò)的存在,但是是無連接的,數(shù)據(jù)包雙向傳輸卻無從判斷是否到達。TCP通過使用序列號、確認以及重傳,添加了可靠的傳輸層。
最終,應(yīng)用層協(xié)議如HTTP建立在TCP之上。在這一層,我們已經(jīng)有了地址,以及可靠傳輸和持續(xù)連接的幻覺illusion。IP和TCP將應(yīng)用開發(fā)者,從重復(fù)實現(xiàn)數(shù)據(jù)包重傳、地址處理等等的地獄中拯救出來。
這些層的獨立性是十分重要的。舉個例子,當(dāng)傳輸88.5MB視頻有數(shù)據(jù)包丟失的時候,互聯(lián)網(wǎng)的網(wǎng)絡(luò)中樞路由器并不知道;只有我的計算機和網(wǎng)絡(luò)服務(wù)器知道。這個弄丟了原始數(shù)據(jù)包的路由基礎(chǔ)設(shè)施,還在盡職地將我計算機發(fā)出的許多重復(fù)的ACK消息路由到目的地去。有可能就是同一個路由器,弄丟了數(shù)據(jù)包,幾毫秒之后又帶著重發(fā)的數(shù)據(jù)包來了。這是理解互聯(lián)網(wǎng)的一個重點:路由基礎(chǔ)設(shè)施對TCP一無所知;它做的僅僅是路由。(當(dāng)然這也有例外,不過大多數(shù)情況下就是這樣)
不同層的協(xié)議獨立工作,但它們不是分開獨立設(shè)計的。高層次協(xié)議通常建立在低層次協(xié)議基礎(chǔ)上,HTTP建立在TCP上,TCP建立在IP上,IP建立在以太網(wǎng)上。更底層的設(shè)計決策,即使在幾十年之后,也會影響到更高層次的決策。
以太網(wǎng)是古老的,且涉及物理層,所以它的需求設(shè)置了基本參數(shù)。一個以太網(wǎng)載荷最大是1.5KB。
IP數(shù)據(jù)包需要包含于以太網(wǎng)數(shù)據(jù)幀中。IP的最小頭部大小是20字節(jié),所以IP數(shù)據(jù)包的最大載荷是 1500 - 20 = 1480 字節(jié)。
同樣,TCP數(shù)據(jù)包需要包含于IP數(shù)據(jù)包中。TCP的最小頭部大小也是20字節(jié),所以TCP的最大載荷是 1480 - 20 = 1460 字節(jié)。在現(xiàn)實中,其他頭部和協(xié)議會占據(jù)更多空間,保守估計TCP的載荷大小是1400字節(jié)。
1400字節(jié)的限制影響了現(xiàn)代協(xié)議的設(shè)計。舉個例子,HTTP請求通常很小。如果我們把它們?nèi)M一個數(shù)據(jù)包而不是兩個,就能減小丟失請求某部分的可能,從而減少需要TCP重傳的可能。為了從小請求中擠出每個字節(jié),HTTP/2指定了頭部壓縮,頭部通常很小。沒有TCP、IP和以太網(wǎng)的情境的話,這看起來很不明智:為什么要壓縮一個協(xié)議的頭部,僅僅為了節(jié)約幾字節(jié)大小的空間?因為,正如 HTTP/2 規(guī)范在第2節(jié)的介紹中所說,壓縮允許“多個請求被壓縮成一個數(shù)據(jù)包”。
HTTP/2 的頭部壓縮是為了適應(yīng)TCP的限制,這個限制來自IP的限制,再往上來自以太網(wǎng)的限制。而以太網(wǎng)在上世紀70年代發(fā)展起來的,1980年投入商用,并在1983年標(biāo)準化。
最后一個問題:為什么以太網(wǎng)的載荷大小設(shè)置在1500字節(jié)(1.5KB)呢?其實沒有深層次的原因:只是一個很好的權(quán)衡考量。每個數(shù)據(jù)幀中有42字節(jié)大小的非載荷數(shù)據(jù)。如果載荷的最大值只有100字節(jié),那么只有70%(100/142)的時間花在發(fā)送載荷上。而1500字節(jié)大小的載荷,意味著大約97%(1500/1542)的時間用于發(fā)送載荷,這樣的效率是可觀的。再增加數(shù)據(jù)包的大小的話,會需要設(shè)備擁有更大的緩沖區(qū),這使得再提高一兩個百分比的效率變得十分困難。簡而言之,20世紀70年代末網(wǎng)絡(luò)設(shè)備的RAM限制,導(dǎo)致HTTP/2 引入了頭部壓縮。