為什麼不乾脆一次把整個東西送出去?
在上一篇裡,我們看到網際網路是一個由網路組成的網路,邊緣有主機,核心則是交換設備。現在想像你要把一張 10 MB 的相片,從你的筆電傳給地球另一端的朋友。最直覺的做法,是從這頭到那頭拉一條乾淨的線,然後把整個檔案當成一條不間斷的串流灌進去。老式的電話網路大致就是這樣運作的,它有個整潔的名字:電路交換(circuit switching)。問題在於,這種做法既浪費線路,又一旦出狀況就糟糕透頂。
在電路交換裡,網路會先從頭到尾預留一段固定的容量——一條專屬路徑——你才能送出哪怕只是一個位元。想像你為了開一輛車過去,就把從你家到朋友家整條高速公路車道都包下來:你只要一暫停,車道就空在那裡,可是別人也不准用。更糟的是,只要這條預留路徑上有一條鏈路壞掉,你整通連線就斷了,因為路徑就只有那一條,而它沒了。預留對一通穩定的語音通話很好用,但真正的電腦流量是「爆量式」的——先來一陣猛的,接著什麼都沒有——所以預留的車道多半閒著。
於是網際網路做了一件近乎沒禮貌的事:它把你的相片切成許多稱為封包的小塊,然後一個一個分別送出。事先不預留任何路徑。每個封包自己找路走,接收端最後再把它們拼回去。這就是封包交換(packet switching),而這整個主題接下來的內容,本質上都是這一個決定所帶來的後果。
封包就是一張寫了地址的明信片
想像封包最乾淨的方式,就是把它看成一張明信片。卡片中間是訊息——你相片的一小片,大概幾百到一千五百位元組左右。在這段訊息四周,寄件人會寫上一個標頭:寄件人是誰、收件人是誰,還有幾項雜務註記。郵局從不會拆開訊息來讀;它只會看地址,然後把卡片往正確的下一個城鎮一推。每個封包都把自己描述得清清楚楚,所以它可以完全獨立於同袍兄弟之外旅行。
這種「自我描述」的結構並非偶然——它遵循一套通訊協定,也就是我們先前認識過的那本「大家講好的規則手冊」。一套通訊協定會固定三件事:訊息的格式(哪些位元代表地址、哪些代表長度)、交換的順序(先送這則訊息、再送那則回覆),以及收到每則訊息時要採取的動作(如果地址不是我的,就把它轉送出去)。正因為每台裝置都同意同一本規則手冊,一支台北手機寫出來的封包,才能被一台素未謀面的法蘭克福路由器完美讀懂。
儲存轉送:先收齊整張卡片,再傳下去
封包很少能一跳就抵達目的地。它會沿著核心,從一台交換器跳到下一台交換器,而在每一台上都會發生一件很具體的事,叫做儲存轉送(store-and-forward)。交換器必須先收下整個封包、把它存進記憶體,才能往下送出第一個位元。它沒辦法在封包尾巴還在抵達時,就開始轉送封包的前端——它得先把整張卡片拿到手,有一部分原因是這樣它才能檢查封包在途中有沒有被弄壞。
這正是把資料切成許多小封包之所以划算的原因。假設一個檔案必須接連穿過三條鏈路。如果你把它當成一整塊巨物送出,第二條鏈路就得閒著,直到第一條鏈路把整塊東西吞完為止。但若切成封包,前面的封包就能已經在第二條鏈路上飛奔,而下一個封包還在穿越第一條鏈路——這些鏈路就像生產線一樣平行運作。小封包也能把任何單一錯誤的損害控制住:如果有一張明信片被弄花了,你只要重寄那一張卡片,而不是整本相簿。
儲存轉送的代價,是每台交換器都必須把封包暫存在一個緩衝區裡——也就是一條等候隊伍。當封包抵達的速度快過外送鏈路能排空它們的速度時,它們就會排隊。這是整張網路中最重要、也最捉摸不定的延遲來源,更是網路為什麼會這一秒爽快、下一秒卻拖泥帶水的直接原因。我們會在下一節給它一個名字:排隊延遲。
時間都跑去哪了?四種延遲
當一個封包穿過一跳時,它所承受的延遲,其實是四件分開的事情加在一起。把它們分清楚會非常有幫助,因為它們的行為截然不同。可以想像你在一間繁忙的郵局寄信:排隊、櫃員讀你的地址、實際把信搬上卡車,以及卡車開過全國的那段路程。
- 處理延遲(processing delay)——交換器讀取標頭,決定要把封包送往哪裡。這就是櫃員瞥一眼你地址的那一下。如今它非常微小,通常只有幾微秒。
- 排隊延遲(queuing delay)——封包在緩衝區裡,排在其他要往同一條鏈路出去的封包後面等待。這就是排在你前面的那群人。它會隨著鏈路有多忙,從零一路擺盪到很大,所以它是最難預測的一種。
- 傳輸延遲(transmission delay)——把封包的每一個位元推上鏈路所需的時間,等於封包大小除以鏈路速率。這就是把貨搬上卡車:包裹愈胖、搬上去就愈久。一個 12000 位元的封包要送上一條 100 Mbps 的鏈路,需要 12000 / 100000000 = 0.12 毫秒。
- 傳播延遲(propagation delay)——那些位元一旦上了線之後,以大約三分之二光速實際傳到另一端所需的時間。這就是卡車真正在開的那段路。在橫跨一塊大陸的光纖上,光是這一項就有數十毫秒,而且砸再多錢也沒辦法讓光跑得更快。
三個意思不一樣的詞:頻寬、傳輸量、延遲
人們會說一條連線「很快」,但這短短一個字,藏著三個不一樣的概念,而初學者(還有廣告)老是把它們混為一談。頻寬(bandwidth)是一條鏈路的最大速率,以每秒位元數來衡量——比方說 100 Mbps 或 1 Gbps。把它想成一根水管的粗細:愈粗的水管每秒能輸送愈多水。頻寬是鏈路本身的性質,是上限,並不是你實際拿到的速度。
傳輸量(throughput)則是你此時此地、從頭到尾實際達到的速率。一條鏈條的強度取決於它最弱的一環,所以在一條由兩段鏈路(速率分別為 R1 與 R2)組成的路徑上,你的傳輸量是 min(R1, R2)——鏈條中最窄的那根水管,決定了它下游的一切。如果你家裡的鏈路是 1 Gbps,但你正在下載的那台伺服器只推得出 20 Mbps,你拿到的就大約是 20 Mbps。如果水是從上游一根細管慢慢滴進來的,全世界最粗的水管也救不了你。
延遲(latency)則是完全另一回事:它是單一封包跑完整趟旅程所需的時間,主要由上面講過的傳播延遲與排隊延遲所主宰。這裡有一個值得燒進腦海的迷思:買更多頻寬,並不會減少延遲。更粗的水管讓每秒能流過更多位元,但每一個位元仍然得以光速實際走完那段距離。從 100 Mbps 升級到 1 Gbps,對你連到另一塊大陸上某台遊戲伺服器的來回時間毫無幫助——那段延遲是由地理與物理決定的,不是由你水管的粗細決定的。
層層相套的信封:分層與封裝
還剩一個問題:一個封包到底是怎麼把它的地址、長度與錯誤檢查欄位填好,而不需要靠一支巨大又糾結成一團、什麼都做的程式?答案是網路領域最大的一個觀念——分層(layering)。我們把整件工作切成一疊層,每一層只處理一件事,並向上面那一層提供一項整潔的服務。應用程式只操心你的相片;傳輸層只操心可靠送達;網路層只操心橫跨整個網際網路的地址;連結層只操心下一跳。每一層都信任下面那一層會做好自己的部分,而且絕不偷看它是怎麼做的。
讓分層化為實體的機制,叫做封裝(encapsulation):層層相套的信封。你的資料一開始是一則裸訊息。傳輸層把它裝進一個寫了連接埠號的信封;網路層再把那個裝進一個更大、寫了 IP 位址的信封;連結層又把那個裝進另一個寫了硬體位址的信封。每一層只加上自己的標頭,從不碰裡面的東西。到了目的地,這些信封會以相反的順序被打開,最外層先拆,每一層只讀自己那層的標頭。下面的圖示就顯示了你的位元組,是如何在往下穿過協定堆疊的每一步,逐步長出一個標頭的。
Going DOWN the stack at the sender (each layer adds its own header):
Application: [ your photo bytes ]
Transport (TCP): [ TCP hdr | your photo bytes ]
Network (IP): [ IP hdr | TCP hdr | your photo bytes ]
Link (Ethernet): [ Eth hdr | IP hdr | TCP hdr | photo | Eth trailer ]
^outermost: read first by the next hop
Going UP the stack at the receiver: strip Eth, then IP, then TCP,
hand the bare photo bytes to the application. Reverse order.有兩張分層的藍圖在整理這件事。七層的 OSI 模型是一套教學參考——乾淨、完整,很適合用來指認一個問題住在哪裡。但真正的網際網路,跑的其實是更精簡的 TCP/IP 模型:應用層、傳輸層、網路層、連結層。當一位工程師說「那是第三層的問題」時,他指的就是網路(IP)層。兩者都記在心裡,但別忘了線路真正聽命的是哪一個。我們會在本級的第五篇裡,把 OSI 與 TCP/IP 並排攤開來細談。