01

通訊的基本功:send、receive 與 socket

send 與 receive 兩個動作,搭配同步/非同步、訊息目的地、可靠性、順序四個面向,構成所有通訊的基礎語彙。

先讀原文開場,旁邊就是白話

這是一本英文書。左邊放原文、右邊放白話導讀——你既讀得懂,也順手碰了原文。

原文 · 行程間通訊 145 4 INTERPROCESS COMMUNICATION 4. 2 The API for the internet protocols 4. 3 External data representation and marshalling 4. 4 Multicast communication 4.
白話導讀

send 與 receive 兩個動作,搭配同步/非同步、訊息目的地、可靠性、順序四個面向,構成所有通訊的基礎語彙。

行程間通訊的特性

send 與 receive 兩個動作,搭配同步/非同步、訊息目的地、可靠性、順序四個面向,構成所有通訊的基礎語彙。

STEP 1

深度探秘

兩個動作撐起整個世界:send 與 receive

行程怎麼對話?

在分散式系統裡,不同電腦上的行程 (process) 不能像同一台電腦上的程式那樣共用記憶體,它們唯一能做的就是傳訊息。而傳訊息說穿了只有兩個動作:

  • send:把一串位元組送到某個目的地
  • receive:從某個地方把訊息收進來

每個目的地都配著一個佇列 (queue):傳送方把訊息丟進遠端的佇列,接收方從自己本地的佇列把訊息拿出來。

四個關鍵面向

光是 send/receive 還不夠,我們得問四個問題,才能描述一個通訊到底長怎樣:

面向 在問什麼
同步 vs 非同步 送/收的時候要不要『卡住』等對方?
訊息目的地 訊息要送到哪裡?怎麼指定?
可靠性 訊息會不會掉?會不會壞掉或重複?
順序 收到的順序,會不會跟送出的順序不一樣?

這四個面向就是接下來整章的共同語彙。

💡
關鍵

通訊的核心只有 send 與 receive 加一個佇列,再用同步性、目的地、可靠性、順序四個面向去描述它。

STEP 2

生活妙喻

同步是打電話,非同步是寄信

同步通訊:打電話

同步 (synchronous) 通訊就像打電話:你撥了號,得一直握著話筒等對方接起來才能講話;對方也得停下手邊的事來接。雙方在『每一則訊息』都會同步等待對方——send 與 receive 都是阻塞 (blocking) 操作。

撥號的人會卡在那裡,直到對面有人喊『喂』。

非同步通訊:寄信

非同步 (asynchronous) 通訊則像寄信:你把信投進信箱(複製到本地緩衝區)就可以轉身去做別的事,信會在背景被送出,你不用站在信箱旁邊等。這就是非阻塞 (non-blocking) 的 send。

至於收信端,可以選擇『一直守在信箱旁』(阻塞收),也可以『先去忙、信來了再用輪詢或鈴聲通知我』(非阻塞收)。

為什麼現代系統多半用阻塞收?

因為像 Java 這種支援多執行緒的環境,可以派一個執行緒專門守信箱(阻塞收),其他執行緒照常工作——又簡單又不浪費。非阻塞收反而要處理『訊息半路插進控制流』的複雜度,所以反而少用。

💡
關鍵

同步像打電話(雙方都得停下等待),非同步像寄信(投了就走,背景幫你送)。

STEP 3

實用超能力

可靠性與順序怎麼看

可靠性:兩個性質

判斷一個通訊服務可不可靠,看兩個性質:

  • 有效性 (validity):訊息保證會送達嗎?就算掉了『合理數量』的封包,最終還是會到,才算可靠;只要掉一個封包就可能送不到,就是不可靠
  • 完整性 (integrity):到達的訊息有沒有壞掉、有沒有被重複?

順序:收到的順序 = 送出的順序?

有些應用要求訊息按傳送者的順序 (sender order) 抵達。對這些應用來說,亂序到達就等同一種失效 (failure)

想像聊天室:如果你先送『你好』再送『再見』,對方卻先看到『再見』,那就出事了。

位置透明性的小技巧

如果用戶端直接用固定 IP 指名服務,那服務就被釘死在那台機器上,不能搬家。解法是:

  • 用戶端只記服務的名字,再透過名稱伺服器 (name server)binder 在執行時把名字翻譯成位置。

這樣服務就能換機器,用戶端完全無感——這叫位置透明性 (location transparency)

用戶端: 我要找『天氣服務』
名稱伺服器: 它現在住在 138.37.88.249:6789
用戶端: 好,我往那裡送
💡
關鍵

可靠性看有效性(不掉)與完整性(不壞不重複);順序看到達順序是否等於送出順序;用名稱伺服器換得位置透明性。

🔆
生活妙喻:同步 vs 非同步通訊 ≈ 打電話 vs 寄信

同步像打電話,撥號者得握著話筒卡在原地等對方接;非同步像寄信,投進信箱就轉身離開,郵差在背景幫你送。

🔆
生活妙喻:訊息佇列 ≈ 家門口的信箱

每個目的地都有一個信箱,別人把信投進來、你有空再去收。送信的人把信丟進你的信箱,你從自己的信箱拿信。

🔆
生活妙喻:位置透明性與名稱伺服器 ≈ 打 165 反詐騙專線而不是記某位警員的手機

你只記得一個好記的名字(服務名),背後對應到誰、住哪裡由總機(名稱伺服器)幫你接通,警員換人換位置你都不必改。

本節字彙

阻塞 (blocking)
執行某動作時行程會被『卡住』,直到動作完成才繼續往下走。
🧠 塞車的『塞』——卡在那動不了。
有效性 (validity) 與完整性 (integrity)
可靠通訊的兩個性質:有效性指訊息保證送達,完整性指訊息不壞掉也不重複。
🧠 有效=有送到,完整=沒缺角沒影分身。
位置透明性 (location transparency)
使用者用名字找服務,不必知道也不在意它實際在哪台機器上,服務可換位置而使用者無感。
🧠 你看不到(透明)它在哪,但它就在那為你服務。
一個線上多人遊戲伺服器希望玩家送出操作後能立刻繼續玩,不要因為等待傳輸而卡住畫面。它應該偏好哪種通訊形式?
某通訊服務只要網路掉了『一個』封包,訊息就可能送不到。依本節定義,這個服務在哪個性質上不及格?
為什麼在像 Java 這種支援多執行緒的環境裡,阻塞式 receive 其實沒什麼缺點?

Socket 與 port:通訊的端點

socket 是行程通訊的端點,綁定到一個 Internet 位址加一個 port。一個 port 只有一個接收者卻可有多個傳送者。

STEP 1

深度探秘

socket 是行程通訊的『插座』

socket 是什麼?

不管是 UDP 還是 TCP,行程要通訊都得透過一個叫 socket 的抽象——它是通訊的端點 (endpoint)。一則訊息就是從一個行程的 socket,傳到另一個行程的 socket。

要能訊息,一個 socket 必須綁定 (bind) 到:

  • 本機的某個 Internet 位址,加上
  • 一個 local port(一個整數,代表電腦內部的訊息目的地)

port 的規矩

每台電腦有 2^16(約 6 萬多)個可用的 port。幾條重要規則:

  • 一個 port 只能有一個接收行程(IP multicast 是例外),但
  • 任意多個行程都可以送訊息到同一個 port
  • 一個行程可以同時用多個 port 來收訊息
  • 同一台電腦上,不同行程不能共用同一個 port

每個 socket 都綁定一種協定——不是 UDP 就是 TCP。

伺服器與用戶端的分工

伺服器會把 socket 綁到一個公開的 port,讓用戶端知道往哪送;用戶端則隨便綁一個空閒的本地 port 就好。

💡
關鍵

socket 是通訊端點,要綁到(Internet 位址, local port)才能收訊息;一個 port 一個接收者,但可有多個傳送者。

STEP 2

生活妙喻

公司大樓的總機與分機

把電腦想成一棟公司大樓

  • Internet 位址 = 大樓的地址(找到是哪棟樓)
  • port = 大樓裡的分機號碼(找到樓裡的哪個人)
  • socket = 桌上那台電話機(真正收發的設備)

要打給某人,你得同時知道『地址 + 分機』。一通電話只會響在『綁定那個分機的那台電話』上。

為什麼一個 port 只有一個接收者?

因為一支分機同時只接給一個人。但反過來,任何人都能打進這支分機(多個傳送者),就像很多客戶都能撥同一支客服分機。

伺服器公開 port = 把客服專線印在名片上

伺服器把它的 port『公告』出去,就像把客服分機印在名片上,客戶照著撥就能找到它。客戶自己用哪支分機打出去?隨便挑一支沒人用的就行。

flowchart LR
  用戶端socket -->|訊息| 約定的port
  約定的port --> 伺服器socket
  其他傳送者 -->|也能送到| 約定的port
💡
關鍵

Internet 位址是大樓地址、port 是分機、socket 是桌上電話;一支分機一個接聽者,但人人都能打進來。

STEP 3

實用超能力

用 Java 的 InetAddress 處理位址

不必手動處理 IP 的細節

UDP 和 TCP 底下的 IP 封包都送往 Internet 位址。Java 提供 InetAddress 類別來代表位址,而且很貼心——你不必知道實際的 IP 數字,只要給它一個 DNS 主機名稱,它會自動去查。

// 給 DNS 名稱,自動查出對應的 Internet 位址
InetAddress aComputer =
    InetAddress.getByName("bruno.dcs.qmul.ac.uk");
// 若名稱查不到,會丟出 UnknownHostException

為什麼這樣設計很聰明?

InetAddress 把『位址到底用幾個位元組表示』這件事包起來

  • IPv4 用 4 個位元組
  • IPv6 用 16 個位元組

你的程式碼完全不用改,就能同時相容兩者——這就是封裝 (encapsulation) 的威力:細節藏在類別內部,外面只看到乾淨的介面。

小結一張表

角色 該做的事
伺服器 把 socket 綁到公開的 server port,等人來連
用戶端 綁一個空閒的本地 port,往伺服器的 port 送
💡
關鍵

用 InetAddress.getByName 給 DNS 名稱就能取得位址,且自動相容 IPv4/IPv6——位址的位元組細節被封裝起來了。

🔆
生活妙喻:Internet 位址、port 與 socket ≈ 公司大樓的地址、分機與桌上電話

Internet 位址是大樓地址(找對棟),port 是分機號碼(找對人),socket 是真正收發的電話機;要找到某人得同時知道地址加分機。

🔆
生活妙喻:一個 port 只有一個接收者、卻可多個傳送者 ≈ 客服分機

一支客服分機同一時間只給一位專員接聽,但任何客戶都能撥進這支分機——一個接聽者、無數來電者。

🔆
生活妙喻:InetAddress 封裝位址細節 ≈ 把 DNS 名稱當成聯絡人名字

你只記聯絡人名字(DNS 名稱),手機自動查出號碼(IP);號碼是市話還是國際碼(IPv4/IPv6)你完全不必操心。

本節字彙

socket
行程通訊的端點,必須綁定到一個 Internet 位址加一個 local port 才能收訊息。
🧠 想成牆上的『插座 (socket)』——通訊的插頭得插在這裡。
port
電腦內部用整數標示的訊息目的地,像大樓裡的分機號碼;一個 port 只有一個接收行程。
🧠 port=『門』,訊息要走哪道門進到哪個行程。
InetAddress
Java 中代表 Internet 位址的類別,可由 DNS 名稱建立,並把 IPv4/IPv6 的位元組差異封裝起來。
🧠 Inet = Internet,這個類別專管網路位址。
一個 socket 想要能夠『接收』訊息,最少需要綁定哪些東西?
三個不同的用戶端行程同時想送訊息給某伺服器在 port 6789 上的 socket。這在 port 規則下可行嗎?
為什麼把 Internet 位址封裝在 InetAddress 類別裡,是個聰明的設計?
02

兩塊積木:UDP 與 TCP

UDP 把一個訊息當一個獨立封包寄出,不確認也不重送。簡單、低負擔,但會掉封包、可能亂序——屬於 omission failures。

UDP 資料報通訊

UDP 把一個訊息當一個獨立封包寄出,不確認也不重送。簡單、低負擔,但會掉封包、可能亂序——屬於 omission failures。

STEP 1

深度探秘

把一則訊息當一個獨立封包寄出去

UDP 在做什麼?

UDP(User Datagram Protocol)提供最簡單的通訊形式:把一則訊息當成一個獨立的封包——稱為資料報 (datagram)——直接寄出去。

關鍵是:沒有確認 (acknowledgement),也沒有重送 (retry)。送出去之後就不管了,到不到是另一回事。

要送或收,行程得先建一個 socket,綁到本機位址與一個 local port。receive 會連同訊息一起回傳傳送者的位址與 port,讓你能回信。

幾個要注意的細節

議題 說明
訊息大小 收方要先準備一個固定大小的位元組陣列;訊息太大會被截斷 (truncate)。多數環境限制約 8 KB。
阻塞 send 通常不阻塞(交給底層就返回),receive 會阻塞直到訊息到達。
Timeout 可在 socket 上設逾時,避免對方掛掉時 receive 無限等下去。
Receive from any receive 不指定來源,任何送到這個 socket 的訊息都收,並告訴你寄件人是誰。
💡
關鍵

UDP 把訊息當獨立資料報寄出,不確認不重送;送不阻塞、收會阻塞,訊息太大會被截斷。

STEP 2

生活妙喻

UDP 就像投明信片

明信片的特性

UDP 寄資料報,就像寄一張明信片

  • 你貼上地址投進郵筒就走,不會收到『已送達』回執(沒有確認)
  • 萬一郵局弄丟了,你不會知道、也不會自動補寄(沒有重送)
  • 好幾張明信片可能不照你寄的順序到(可能亂序)
  • 但明信片很輕便、成本低(沒有保證送達的額外負擔)

為什麼有人就愛用明信片?

因為對某些情境,『偶爾掉一張』完全可以接受,而換來的是速度快、開銷小

  • DNS(查網域名稱):查不到就再問一次,沒必要為單次查詢建立可靠連線。
  • VoIP(網路語音):晚到的語音封包就算補送回來也沒用了,丟掉繼續講比卡頓好。

重點不是『可靠最好』,而是『合不合用』。對即時、容錯、講求輕量的應用,UDP 的明信片風格反而是優點。

💡
關鍵

UDP 像投明信片:沒回執、不補寄、可能亂序,但輕便快速——對 DNS、VoIP 這類容忍掉包的應用反而剛好。

STEP 3

實用超能力

UDP 的失效模型與 Java 寫法

UDP 的失效模型

UDP 資料報會遇到兩種狀況:

  • 遺漏失效 (omission failures):因為檢查碼 (checksum) 錯誤、或來源/目的地沒有緩衝空間,封包偶爾被丟掉。
  • 亂序 (ordering):訊息有時不照傳送者的順序到達。

好消息:靠 checksum,收到的訊息『被弄壞卻沒被發現』的機率極低——所以完整性大致有保障,問題主要在有效性(會掉)。

想要可靠怎麼辦?

用 UDP 的應用得自己加檢查。一個常見手法是確認 (acknowledgement):收到就回一聲,沒回就重送,把不可靠的服務疊成可靠的。

Java 的兩個類別

// 傳送端:建 socket、打包、送出
DatagramSocket aSocket = new DatagramSocket();
byte[] m = message.getBytes();
InetAddress aHost = InetAddress.getByName(hostName);
DatagramPacket request =
    new DatagramPacket(m, m.length, aHost, 6789);
aSocket.send(request);

// 接收端:準備緩衝區、阻塞等訊息
byte[] buffer = new byte[1000];
DatagramPacket reply =
    new DatagramPacket(buffer, buffer.length);
aSocket.receive(reply); // 會阻塞到訊息到達
  • DatagramPacket:裝訊息+長度+目的地位址與 port
  • DatagramSocket:負責 send / receive,可用 setSoTimeout 設逾時
💡
關鍵

UDP 的失效是遺漏(會掉)與亂序;完整性靠 checksum 大致無虞,要可靠得自己加確認與重送。Java 用 DatagramPacket 與 DatagramSocket。

🔆
生活妙喻:UDP 資料報 ≈ 投明信片

貼上地址投進郵筒就走,沒有送達回執、弄丟也不補寄、好幾張可能亂序到,但輕便快速、成本低。

🔆
生活妙喻:用確認把不可靠疊成可靠 ≈ 請對方收到後回個訊息,沒回就再寄一次

明信片本身不保證送達,但你可以約定『收到請回一張』,沒收到回信就重寄,自己堆出可靠性。

🔆
生活妙喻:訊息被截斷 ≈ 把長信硬塞進太小的信封

收方準備的緩衝區若比訊息小,超出的部分就被剪掉,像信紙塞不進信封就被裁短。

本節字彙

資料報 (datagram)
UDP 中一則獨立、自帶目的地的訊息封包,送出後不保證送達。
🧠 data + gram(電報),一封自成一體的資料電報。
遺漏失效 (omission failure)
封包因檢查碼錯誤或緩衝區不足而被丟掉,導致訊息沒送到。
🧠 omission=省略、漏掉——訊息被『漏』掉了。
檢查碼 (checksum)
附在訊息上的校驗值,讓收方能偵測訊息是否在傳輸中損壞。
🧠 check(檢查)+ sum(總和),算個總和來核對有沒有壞。
DNS 名稱查詢選擇跑在 UDP 之上,最主要的理由是什麼?
接收方為 UDP 訊息準備了 512 位元組的緩衝區,但送來的訊息有 800 位元組。會發生什麼?
一位開發者擔心 UDP『會掉封包』,想在 UDP 之上做出可靠傳輸。下列哪個方向最對?

TCP 串流通訊

TCP 提供一條雙向位元組串流,靠確認、序號、重送與流量控制,隱藏訊息大小、遺失、重複與亂序等網路細節。

STEP 1

深度探秘

一條沒有邊界的雙向位元組串流

TCP 給你什麼抽象?

TCP(Transmission Control Protocol)提供的不是一封封訊息,而是一條位元組串流 (stream of bytes):可以往裡寫、也可以從裡讀,而且沒有訊息邊界——你寫進去的『一則訊息』跟對方讀出來的不一定對齊。

TCP 幫你把網路的種種麻煩藏起來

網路問題 TCP 怎麼處理
訊息大小 由底層決定收集多少資料再打包成 IP 封包送出
遺失 確認 (acknowledgement) 機制;逾時沒收到確認就重送
流量控制 (flow control) 配速:寫太快就阻塞寫方,等讀方消化
重複與亂序 每個 IP 封包帶序號,據此剔除重複、重排亂序

先連線,再溝通

兩個行程必須先建立連線才能在串流上通訊。一方扮用戶端(發出 connect 請求),一方扮伺服器(accept 接受)。連好之後,雙方各有一條輸入串流與一條輸出串流,地位平等。

💡
關鍵

TCP 是沒有訊息邊界的雙向位元組串流,靠確認+重送、序號、流量控制隱藏遺失、重複、亂序與速差;通訊前要先建立連線。

STEP 2

生活妙喻

TCP 像打電話前先接通的專線

從明信片升級到專線電話

如果說 UDP 是『投明信片』,那 TCP 就是先撥號接通、確認雙方都在線上,再開始講話的專線電話

  • 先建立連線:撥號(connect)→ 對方接聽(accept),通話才開始。
  • 保證聽清楚:話沒聽到,會請對方再講一次(確認+重送)。
  • 配速不淹沒:你講太快、對方記不下來時,會請你慢一點(流量控制)。
  • 照順序聽到:你先說的話對方先聽到(序號重排)。

為什麼說『沒有訊息邊界』?

打電話時,你說的話是連續的聲音流,不是一句句獨立的封包。對方聽到的是一段連續語音,得自己判斷句子在哪裡斷。TCP 也一樣:它只保證『位元組照順序、不漏不重複』,但不替你標記哪裡是一則訊息的結尾

sequenceDiagram
  participant 用戶端
  participant 伺服器
  用戶端->>伺服器: connect 請求
  伺服器-->>用戶端: accept 接受
  用戶端->>伺服器: 寫入位元組串流
  伺服器-->>用戶端: 寫回位元組串流
💡
關鍵

TCP 像先接通的專線電話:先連線、保證聽清楚、會配速、照順序;但話語是連續流,沒有現成的訊息邊界。

STEP 3

實用超能力

TCP 的失效模型、用途與 Java 寫法

TCP 也不是『絕對可靠』

TCP 用 checksum 擋損壞、用序號擋重複、用逾時重送補遺失,所以正常情況下保證送達。但如果:

  • 封包遺失超過某個上限,或
  • 網路被切斷、嚴重壅塞

TCP 收不到確認,過一陣子就會宣告連線斷掉。所以嚴格說,TCP 並非在所有情況下都可靠。連線斷掉時還有兩個尷尬:

  • 行程分不清是網路斷了還是對方行程掛了。
  • 無法確定自己最近送出的訊息對方到底收到沒。

哪些服務跑在 TCP 上?

HTTP(網頁)、FTP(檔案傳輸)、Telnet(遠端終端)、SMTP(寄信)——都需要可靠、有序的位元組串流。

Java 的兩個類別

// 伺服器:開一個 listening socket 等連線
ServerSocket listenSocket = new ServerSocket(7896);
while (true) {
    Socket clientSocket = listenSocket.accept(); // 阻塞等連線
    // 通常為每個用戶端開一條新執行緒來服務
    new Connection(clientSocket);
}

// 用戶端:建 Socket 即同時連到指定主機與 port
Socket s = new Socket(hostName, 7896);
DataInputStream in = new DataInputStream(s.getInputStream());
DataOutputStream out = new DataOutputStream(s.getOutputStream());
out.writeUTF(message); // 寫入輸出串流
String reply = in.readUTF(); // 從輸入串流讀
  • ServerSocket:伺服器用來監聽連線,accept 取得一個新的 Socket
  • Socket:一對行程的連線兩端,提供 getInputStream / getOutputStream
  • 伺服器常為每個用戶端開一條執行緒,這樣等某個用戶端輸入時不會卡住其他人。
💡
關鍵

TCP 正常時保證送達,但連線斷掉時分不清是網路還是對方掛了;HTTP/FTP/SMTP 都用它。Java 用 ServerSocket 與 Socket,常為每個用戶端開一條執行緒。

🔆
生活妙喻:TCP 連線 ≈ 先撥號接通的專線電話

通話前要先撥號(connect)對方接聽(accept)才開始;過程中保證聽清楚、會配速、照順序,比明信片可靠得多。

🔆
生活妙喻:沒有訊息邊界的位元組串流 ≈ 電話裡連續的語音

對方聽到的是一段連續聲音,得自己判斷句子在哪裡斷;TCP 只保證位元組順序與完整,不替你標記訊息結尾。

🔆
生活妙喻:流量控制 ≈ 說話太快時對方請你慢一點

你講太快、對方記不下來時,會請你放慢;TCP 在讀方來不及消化時會阻塞寫方,避免淹沒對方。

本節字彙

位元組串流 (stream of bytes)
TCP 提供的雙向資料流,可連續讀寫,沒有訊息邊界。
🧠 stream=水流,位元組像水一樣連續流動,沒有一段段的界線。
流量控制 (flow control)
TCP 自動配速的機制:寫方太快時被阻塞,等讀方消化完再繼續,避免淹沒對方。
🧠 flow(流量)+ control(控制)——控制資料流的速度。
確認與重送 (acknowledgement & retransmission)
收到資料就回報確認,傳送方逾時未收到確認就重送,用來克服封包遺失。
🧠 收到喊『有』,沒聽到『有』就再講一次。
一個應用先後在 TCP 串流上寫入『HELLO』再寫入『WORLD』,接收端讀取時最不該假設的是什麼?
TCP 連線突然斷掉後,使用該連線的行程會面臨哪個本質困境?
在 TCP 串流上,寫入方比讀取方快很多時,TCP 的流量控制會怎麼做?
03

把資料打包送上路:外部資料表示與 marshalling

不同電腦對整數位元組順序(big-endian/little-endian)、浮點數、字元編碼的存法都不同,要溝通就得約定一套外部格式,並把結構攤平成位元組。

為什麼需要外部資料表示

不同電腦對整數位元組順序(big-endian/little-endian)、浮點數、字元編碼的存法都不同,要溝通就得約定一套外部格式,並把結構攤平成位元組。

STEP 1

深度探秘

程式裡是結構,網路上只有位元組

兩個世界的落差

程式執行時,資料以結構化的物件存在記憶體裡(一堆互相連結的物件);但網路上傳的訊息只是一串位元組 (sequence of bytes)

所以不管用什麼通訊方式,送出前都得把資料結構攤平 (flatten) 成位元組序列,到了對面再重建 (rebuild) 回來。

為什麼不能直接把記憶體內容倒出來送?

因為不同電腦對同一個值的存法不一樣

差異點 說明
整數位元組順序 big-endian:最高位元組在前;little-endian:最低位元組在前
浮點數 不同架構的表示法也不同
字元編碼 有的用 ASCII(一字元 1 byte),有的用 Unicode(一字元 2 bytes)

如果甲機把整數直接倒出來,乙機照自己的習慣解讀,數字就錯了。所以需要一套大家都同意的格式——外部資料表示 (external data representation)

💡
關鍵

程式裡資料是結構化物件,網路只能傳位元組;不同電腦對整數順序、浮點數、字元編碼的存法不同,所以要約定一套外部資料表示。

STEP 2

生活妙喻

big-endian 與 little-endian:先寫哪一位?

一個簡單到吵不完的問題

要寫數字『一千兩百三十四』(1234),你會從哪一端開始寫?

  • big-endian:從最大位寫起——千、百、十、個(1-2-3-4)。像我們平常寫阿拉伯數字。
  • little-endian:從最小位寫起——個、十、百、千(4-3-2-1)。

兩種寫法都對,只要讀的人知道作者用哪一種。問題是:如果甲用 big-endian 寫、乙卻當 little-endian 讀,1234 就會被讀成天差地別的數字。

這就是著名的『大小端之爭』——名字其實出自《格列佛遊記》裡為了『水煮蛋該從大端還小端敲開』而開戰的小人國。

字元編碼:同一個字母,不同的代號

字元也一樣:ASCII 用 1 個位元組存一個英文字母,Unicode 為了容納全世界的文字,用 2 個位元組。沒講好就會雞同鴨講。

解法:先講好一套『通用文字』

就像聯合國開會大家先約定用某種官方語言,電腦之間也先約定一套外部格式,發送前翻成它、收到後翻回自己的習慣。

💡
關鍵

big-endian 從最高位寫起、little-endian 從最低位寫起;字元編碼也各有不同。沒講好就會誤讀,所以要先約定一套通用格式。

STEP 3

實用超能力

marshalling:自動又可靠的打包術

兩條讓電腦互通的路

要讓任意兩台電腦交換二進位資料,有兩種作法:

  1. 傳前轉成約定的外部格式,收到再轉回本地格式。(若兩台已知是同型,可省略轉換)
  2. 用傳送者的格式傳,並夾帶『我用哪種格式』的標示,由收方視需要轉換。

注意:位元組本身在傳輸途中永遠不會被改動,改的是『怎麼解讀它們』。

marshalling 與 unmarshalling

  • marshalling(封送):把一堆資料項組裝成適合放進訊息的形式——也就是把結構化資料與基本值翻成外部資料表示。
  • unmarshalling(解封送):在抵達端把它們拆開、還原成等價的資料項。

為什麼最好交給機器做?

因為 marshalling 得照顧到每個基本元件最細的表示細節,手工做極易出錯;自動產生的程序還能順便處理緊湊度 (compactness)。所以在 RMI/RPC 中,這些工作通常由中介軟體 (middleware) 自動完成,應用程式設計師根本不用碰。

甲機物件 --marshalling--> 約定外部格式的位元組 --網路--> 乙機
乙機 --unmarshalling--> 還原成等價物件
💡
關鍵

marshalling 是把資料攤平成外部格式、unmarshalling 是還原;位元組不變、變的是解讀方式。手工易錯,通常由中介軟體自動完成。

🔆
生活妙喻:外部資料表示 ≈ 聯合國開會約定的官方語言

各國代表母語不同,先約定一種官方語言發言與翻譯;電腦傳資料前也先翻成一套大家同意的外部格式,避免誤讀。

🔆
生活妙喻:big-endian vs little-endian ≈ 寫數字從千位先寫還是個位先寫

1234 可以從最高位(big)或最低位(little)開始寫,兩種都對,但讀的人必須知道作者用哪種,否則數字就讀錯了。

🔆
生活妙喻:marshalling 由中介軟體自動完成 ≈ 請專業打包公司裝箱搬家

手工打包易漏易錯,交給專業打包公司(中介軟體)按標準裝箱、到目的地拆箱還原,又快又不出錯。

本節字彙

marshalling(封送)
把一組資料項組裝、翻譯成適合放進訊息傳輸的外部資料表示。
🧠 marshal=『集合整隊』,把散落的資料整隊打包送上路。
big-endian / little-endian
整數多位元組的兩種排列順序:big-endian 最高位元組在前,little-endian 最低位元組在前。
🧠 big=大端先(高位先),little=小端先(低位先)。
外部資料表示 (external data representation)
一套大家同意、用來表示資料結構與基本值的標準格式,讓不同電腦能互通。
🧠 external=對外的、共用的,不是各自內部的格式。
甲電腦把一個多位元組整數以 big-endian 直接倒進訊息,乙電腦卻以 little-endian 解讀。最可能的後果是什麼?
課文說『位元組本身在傳輸途中永遠不會被改動』。這句話真正的意思是?
在 RMI/RPC 系統中,為什麼 marshalling 通常交給中介軟體自動完成,而非請應用工程師手寫?

三種風格:CORBA CDR、Java 序列化、XML

CDR 是緊湊二進位、不帶型別(雙方先知道型別);Java 序列化是二進位但夾帶完整型別;XML 是文字、自我描述、可讀但較肥。

STEP 1

深度探秘

三種打包資料的哲學

三位選手登場

同樣是把資料攤平送出去,業界有三種代表性風格:

1. CORBA CDR(Common Data Representation)

  • 緊湊的二進位格式
  • 不帶型別資訊:因為假設收發雙方事先就知道資料的順序與型別(透過 IDL 介面定義)
  • 像 string 會以『長度(unsigned long)+字元』表示,並對齊到對應位元組邊界

2. Java 物件序列化(serialization)

  • 二進位,但夾帶完整型別資訊(類別名稱+版本號)
  • 收方就算事先不知型別,也能從序列化內容自行重建物件
  • 只給 Java

3. XML(Extensible Markup Language)

  • 文字格式,用標籤 (tags) 描述結構,自我描述
  • 人類可讀、跨平台,但訊息較肥、處理較慢
風格 編碼 帶型別資訊? 適用範圍
CORBA CDR 二進位 否(雙方先約定) 多語言 RMI
Java 序列化 二進位 是(完整) 僅 Java
XML 文字 是(標籤/命名空間) 跨應用、Web 服務
💡
關鍵

CDR 緊湊二進位但不帶型別(靠 IDL 先約定);Java 序列化二進位且帶完整型別、只給 Java;XML 文字、自我描述、可讀但較肥。

STEP 2

生活妙喻

寄包裹的三種貼標方式

同樣寄包裹,標籤怎麼貼?

想像你要寄一份『個人資料:姓名 Smith、地點 London、年份 1984』給朋友。

CDR:什麼標籤都不貼的緊湊包裹

你和朋友事先講好:『第一格是姓名、第二格是地點、第三格是年份』。所以包裹裡只放值,不寫欄位名——最省空間,但雙方得先有共識。

像兩個有默契的老同事,遞個眼神就懂。

Java 序列化:附上完整出貨單的包裹

包裹裡除了值,還夾一張詳單:『這是 Person 類別、版本 X、欄位有 name(String)、place(String)、year(int)』。朋友就算第一次收到也能照單組裝——但這套詳單格式只有 Java 看得懂

XML:每樣東西都貼名牌的包裹

<person id="123456789">
  <name>Smith</name>
  <place>London</place>
  <year>1984</year>
</person>

每個值都用 <name>...</name> 這種成對標籤包起來,誰拿到都看得懂、人眼也讀得出來——代價是包裹變大、處理變慢

自我描述 vs 事先約定

CDR 不自我描述(靠默契),Java 與 XML 都自我描述(自帶說明),只是 Java 把說明塞進序列化內容,XML 可引用外部的命名空間 (namespaces) 來定義標籤意義。

💡
關鍵

CDR 像有默契不貼標的緊湊包裹、Java 像附完整出貨單但只有 Java 懂、XML 像每樣都貼名牌人人可讀但較大。

STEP 3

實用超能力

怎麼選?以及 reflection 的妙用

兩個關鍵的設計分水嶺

挑序列化方案時,常看兩件事:

  1. 要不要事先約定型別?

    • CDR:要。靠 CORBA IDL 描述型別,再由介面編譯器自動產生 marshalling/unmarshalling 程式。
    • Java:不用事先寫型別描述。靠 reflection(反射) 在執行時自動查出類別名稱、欄位的名稱與型別與值,完全通用地序列化。
    • XML:用標籤自我描述,產生方式依情境而定。
  2. 要緊湊還是要可讀/可攜?

    • 要省頻寬與速度 → 二進位(CDR / Java)
    • 要跨平台、人可讀、跨不同應用 → XML(可壓縮以省頻寬)

reflection 為什麼這麼省事?

Java 的 reflection 能在執行時『反問』一個類別:你叫什麼名字?有哪些欄位?型別是什麼?正因如此,不必為每種物件手寫專屬的 marshalling 函式(不像 CORBA 要先用 IDL 描述)。反序列化時,再用類別名稱建類別、依欄位型別建構子,重建出物件。

小提醒:transient

有些東西不該被序列化——例如指向本地檔案或 socket 的參考,換台機器就失效了。Java 可把這類變數宣告為 transient,序列化時跳過它。

public class Session implements Serializable {
    private String userName;        // 會被序列化
    private transient Socket conn;  // 跳過,不序列化本地資源
}

更輕量的後起之秀

除了三巨頭,Google 的 protocol buffersJSON 也很流行,朝著比 XML 更輕量的方向走。

💡
關鍵

CDR 靠 IDL 事先約定並自動產生程式,Java 靠 reflection 通用自動序列化(本地資源用 transient 跳過),XML 自我描述可讀但較肥;另有 protocol buffers、JSON 更輕量。

🔆
生活妙喻:CORBA CDR ≈ 有默契、不貼標的緊湊包裹

雙方事先約定每格放什麼,包裹只放值不寫欄位名,最省空間,但得靠事前共識(IDL)。

🔆
生活妙喻:Java 序列化 ≈ 附完整出貨單的包裹,但詳單只有 Java 看得懂

包裹夾一張寫明類別、版本、欄位型別的詳單,收方照單重建;缺點是這套詳單格式只有 Java 認得。

🔆
生活妙喻:XML ≈ 每樣東西都貼名牌的包裹

每個值都用成對標籤包起來,誰收到都看得懂、人眼也讀得出,代價是包裹較大、處理較慢。

本節字彙

reflection(反射)
程式在執行時查詢某類別的屬性(名稱、欄位、型別、方法)並據此建立物件的能力,讓序列化能通用地自動進行。
🧠 像照鏡子反觀自己——程式在執行時反觀類別長什麼樣。
自我描述 (self-describing)
資料格式本身就帶有說明其結構或型別的資訊,收方不需事先知道型別也能解讀,如 XML 與 Java 序列化。
🧠 資料自己會介紹自己,不靠外部默契。
transient
Java 關鍵字,標記某變數在序列化時應被跳過,常用於本地檔案、socket 等換機就失效的資源。
🧠 transient=短暫的,這東西不值得打包帶走。
CORBA CDR 為什麼可以不在訊息裡夾帶型別資訊,仍能正確解讀?
一個系統需要在 Java 與 Python 等多種語言的服務之間交換、且希望訊息能被人直接閱讀與跨應用使用。最合適的是?
Java 序列化能『通用地』把任何物件序列化,而不必為每種型別手寫 marshalling 函式,靠的是什麼?

遠端物件參考

在分散式物件模型裡,要指名呼叫哪個遠端物件,需要一個跨整個系統都唯一、且時空上不重複的識別碼——遠端物件參考。

STEP 1

深度探秘

怎麼指名『那一個』遠端物件?

問題的起點

在支援分散式物件模型的語言(如 Java、CORBA)裡,用戶端呼叫某個遠端物件的方法時,要送一則呼叫訊息到主管該物件的伺服器行程。問題是:一台伺服器上可能有成千上萬個物件,這則訊息得指名到底要呼叫哪一個。

這個『跨整個分散式系統都有效的識別碼』就是遠端物件參考 (remote object reference)

它有哪些用途?

  • 在呼叫訊息中指定要被呼叫的物件
  • 作為方法呼叫的參數傳入、或作為結果回傳
  • 每個遠端物件有唯一一個遠端物件參考
  • 可以互相比較,看兩個參考是否指向同一個遠端物件

本節關注的是它的外部表示——也就是要怎麼把這個識別碼攤平成可傳輸的形式。

💡
關鍵

遠端物件參考是跨整個分散式系統都有效的唯一識別碼,用來在呼叫訊息中指名要呼叫哪個遠端物件,也可當參數、結果與比較依據。

STEP 2

生活妙喻

全球唯一、永不重發的身分證

為什麼要『時空唯一』?

遠端物件參考必須在空間上唯一(所有電腦的所有行程之間都不撞號)也在時間上唯一(就算某物件被刪掉,它的參考也絕不重用)。

為什麼刪掉了還不能重用?因為可能還有人手上握著一張過期的參考。如果你把這個號碼發給新物件,那個拿著舊參考的人一呼叫,就會接到錯誤的物件——後果不堪設想。

正確的設計是:拿過期參考去呼叫,應該回報錯誤,而不是悄悄連到別的物件。

像什麼?

就像身分證字號:全國唯一、不會兩人共用;而且就算某人過世,他的號碼也不會再發給新生兒——否則查到舊紀錄就會張冠李戴。遠端物件參考要的,正是這種『絕不回收再用』的唯一性。

💡
關鍵

遠端物件參考要時空都唯一、刪除後絕不重用;否則握著舊參考的人會接到錯誤物件。它像永不回收再發的身分證字號。

STEP 3

實用超能力

怎麼造出唯一參考,又能不能搬家?

一種保證唯一的造法

一個經典作法是把幾個欄位串接起來:

| Internet 位址 | port 號 | 建立時間 | 物件編號 | 介面資訊 |
   32 bits        32 bits   32 bits     32 bits
  • Internet 位址 + port:定位到主機與行程
  • port + 建立時間:在該電腦上產生唯一的行程識別
  • 物件編號:每在該行程建立一個物件就遞增,保證同行程內不撞號
  • 介面資訊:例如介面名稱,讓收到參考的行程知道這物件有哪些方法可呼叫

一個重要取捨:能不能搬家?

  • 最簡單的實作:遠端物件只活在建立它的行程裡、行程不在了它也消失。此時參考可直接當位址用——訊息就照參考裡的 IP 與 port 送過去。
  • 想讓物件能搬到別台機器:那就不能把參考直接當位址用,否則物件一搬,舊位址就失效。要多一層間接 (indirection),讓物件能在生命週期中於不同伺服器被啟動。
  • 完全與位置無關:P2P 覆蓋系統(第 10 章)用一種跟位置完全脫鉤的參考,靠分散式路由演算法把訊息導向資源。
flowchart TD
  參考[遠端物件參考] --> 直接[直接當位址用]
  參考 --> 間接[多一層間接可搬移]
  參考 --> 無關[與位置完全無關靠路由]
  直接 --> 簡單[實作簡單但物件不能搬家]
  間接 --> 彈性[物件可換伺服器]
  無關 --> P2P[適用P2P覆蓋系統]
💡
關鍵

經典造法是串接 IP+port+建立時間+物件編號+介面資訊保證唯一;若參考直接當位址用則物件不能搬家,要可搬移就得多一層間接,P2P 則用與位置無關的參考。

🔆
生活妙喻:時空唯一、不重用的遠端物件參考 ≈ 永不回收再發的身分證字號

身分證字號全國唯一、且就算持有人過世也不會再發給新生兒;遠端物件參考也要絕不回收重用,否則拿舊參考的人會張冠李戴。

🔆
生活妙喻:串接欄位保證唯一 ≈ 用『縣市+出生日期+流水號』組出不撞號的編號

把幾個各自有區別力的欄位(IP、port、時間、物件編號)串起來,整體就幾乎不可能與別人重複。

🔆
生活妙喻:參考直接當位址 vs 多一層間接 ≈ 名片印死地址 vs 只印『請洽總機轉接』

名片印死地址最直接,但搬家就作廢;只印『請洽總機』雖多一道手續,搬到哪總機都能幫你轉接。

本節字彙

遠端物件參考 (remote object reference)
跨整個分散式系統都有效、用來指名某個遠端物件的唯一識別碼。
🧠 remote(遠端)+ reference(參考)——指向遠方那個物件的牌子。
時空唯一性
遠端物件參考必須在所有電腦的所有行程間都唯一(空間),且刪除後絕不重用(時間)。
🧠 空間不撞號、時間不回收。
間接 (indirection)
在參考與實際位置之間多加一層轉介,使遠端物件可以搬移到不同伺服器而不讓舊參考失效。
🧠 不直接指地址,而是『請洽總機轉接』多繞一層。
為什麼一個遠端物件被刪除後,它的遠端物件參考絕對不能被重新分配給新物件?
課文提到一種保證遠端物件參考唯一的經典造法。它由哪些部分串接而成?
某 RMI 實作把遠端物件參考『直接當作位址』來送訊息。這帶來什麼限制?
04

一次說給一群人聽:群播通訊

群播把單一訊息送給一個群組的所有成員,傳送者不需知道成員是誰。IP multicast 用 Class D 位址表示群組,成員資格是動態的。

IP multicast 怎麼運作

群播把單一訊息送給一個群組的所有成員,傳送者不需知道成員是誰。IP multicast 用 Class D 位址表示群組,成員資格是動態的。

STEP 1

深度探秘

一次說給一整群人聽

點對點不夠用的時候

前面講的 send/receive 都是點對點 (point-to-point):一個傳送者對一個接收者。但有時你要把同一則訊息送給一群行程——例如一個服務由分散在不同電腦上的多個行程組成(為了容錯或提升可用性)。

這時群播 (multicast) 才合適:一個操作就把單一訊息送給群組裡的每個成員,而且通常傳送者對成員是誰、群組多大都無感(成員資格對傳送者透明)。

群播能撐起哪些東西?

用途 怎麼用群播
容錯(複製服務) 把請求群播給所有伺服器副本,掛幾個仍能服務
服務探索 用群播找尋可用的探索服務以註冊或查詢介面
提升效能(複製資料) 資料一變更就群播新值給管理各副本的行程
事件通知傳播 例如 Facebook 有人改狀態,所有好友收到通知

最簡單的群播協定不保證送達、也不保證順序——這點很重要,下一節會深入。

💡
關鍵

群播一次把單一訊息送給整個群組的所有成員,傳送者對成員身分與群組大小無感;常用於容錯、服務探索、複製資料與事件通知。

STEP 2

生活妙喻

群播像廣播電台,不是一通通電話

從『一通通打』到『一次播送』

假設你要通知 100 位社團成員開會。

  • 點對點作法:你一通一通打電話,打 100 次。累、慢,而且你得先有每個人的電話。
  • 群播作法:你在廣播電台的某個頻道講一次,所有轉到那個頻道的人同時聽到。你不需要知道誰在聽、有幾個人聽

這就是群播的精神:講一次,群組全收到,而且你對聽眾是誰無感

頻道怎麼表示?

IP multicast 用一個特別的Class D 位址(IPv4 中前 4 個位元是 1110)來代表一個群組——就像電台的頻率。誰想收,就讓自己的 socket 加入 (joinGroup) 那個群組;不想收了就離開 (leaveGroup)

而且你不必是成員也能對群組廣播——就像不是聽眾也能投稿到電台播出。

flowchart TD
  傳送者 -->|送一次到群組位址| 群組頻道
  群組頻道 --> 成員A
  群組頻道 --> 成員B
  群組頻道 --> 成員C
💡
關鍵

群播像在廣播電台某頻道講一次,所有轉到該頻道的人同時聽到;Class D 位址就是頻道,socket 用 joinGroup/leaveGroup 收聽或退出。

STEP 3

實用超能力

IP multicast 的實作細節與 Java 寫法

幾個關鍵機制

  • 只能透過 UDP 使用:在應用層,IP multicast 是靠『送 UDP 資料報到群播位址+一般 port 號』來達成。
  • 本地 vs 跨網路:本地群播用區網(如乙太網路)本身的群播能力;跨網路則靠multicast routers 把單一資料報轉送到其他網路的路由器,再在當地群播給成員。
  • TTL(time to live):傳送者可指定資料報最多能經過幾個路由器,藉此限制傳播範圍。Java 預設 TTL 為 1,只在本地網路傳播。
  • 位址分配:Class D 位址(224.0.0.0 ~ 239.255.255.255)由 IANA 管理,分成多個區塊;有些是永久群組(即使沒成員也存在,如 NTP 用 224.0.1.1),其餘給臨時群組用。

成員資格是動態的

電腦可隨時加入或離開群組,也可同時加入任意多個群組。當群播訊息抵達一台電腦,會被複製給所有加入了該位址且綁在該 port的本地 socket。

Java 的 MulticastSocket

InetAddress group = InetAddress.getByName("228.5.6.7");
MulticastSocket s = new MulticastSocket(6789);
s.joinGroup(group); // 加入群組才收得到

byte[] m = message.getBytes();
DatagramPacket out = new DatagramPacket(m, m.length, group, 6789);
s.send(out); // 送到群組

byte[] buffer = new byte[1000];
DatagramPacket in = new DatagramPacket(buffer, buffer.length);
s.receive(in); // 收群組裡其他人的訊息

s.leaveGroup(group); // 不收了就離開

MulticastSocketDatagramSocket 的子類別,多了加入/離開群組的能力。

💡
關鍵

IP multicast 在應用層只能透過 UDP,用 Class D 位址表示群組、靠 multicast routers 跨網路、用 TTL 限制範圍;Java 的 MulticastSocket 提供 joinGroup/leaveGroup。

🔆
生活妙喻:群播 ≈ 在廣播電台某頻道講一次

你在某頻道講一次,所有轉到該頻道的人同時聽到,而你不需知道誰在聽、有幾個人聽——成員對傳送者透明。

🔆
生活妙喻:Class D 位址作為群組 ≈ 電台的頻率

想收聽就把收音機轉到那個頻率(joinGroup),不想聽就轉走(leaveGroup);頻率就是群組位址。

🔆
生活妙喻:TTL 限制傳播範圍 ≈ 廣播電台的發射功率

功率小只能傳到附近(TTL 小、只在本地),功率大才能傳得遠(TTL 大、跨越更多路由器)。

本節字彙

群播 (multicast)
一個操作把單一訊息送給一個群組的所有成員,且通常成員身分對傳送者透明。
🧠 multi(多)+ cast(投送)——一次投送給多人。
Class D 位址
IPv4 中前四位元為 1110 的特殊位址(224.0.0.0~239.255.255.255),專門用來表示群播群組。
🧠 D for Distribution——專給群播分送用的位址等級。
TTL (time to live)
群播資料報可經過的路由器數上限,用來限制傳播距離;Java 預設為 1(僅本地)。
🧠 time to live=還能活多久,跳幾次路由器後就『過期』停下。
一個服務由分散在多台機器上的多個伺服器副本組成,想把同一個請求一次送給所有副本。最適合的通訊方式是?
關於 IP multicast 群組的成員資格,下列哪個敘述正確?
在應用程式層面,要使用 IP multicast,實際上是透過哪種協定?

群播的可靠性與順序

IP multicast 和 UDP 一樣會掉訊息(omission failure),且不同成員可能收到不同順序。某些應用需要 reliable multicast 與 totally ordered multicast。

STEP 1

深度探秘

IP multicast 會掉訊息,也可能亂序

跟 UDP 同款的毛病

IP multicast 的失效特性和 UDP 資料報一樣——會發生遺漏失效 (omission failures)。對群播而言,這意味著一則訊息不保證每個成員都收到:可能有些成員收到、有些沒收到。這稱為不可靠群播 (unreliable multicast)

訊息會在哪裡掉?

  • 一個 multicast router 送到另一個 router 的途中,封包遺失 → 該 router 之後的所有接收者都收不到
  • 區網群播時,某個接收者緩衝區滿了就把訊息丟掉。
  • 若某個 multicast router 故障,它後面的群組成員都收不到(但本地成員可能照收)。

順序也是問題

IP 封包經過互連網路時不一定照送出順序到達,於是:

  • 同一個傳送者送的資料報,不同成員可能收到不同的順序
  • 兩個不同傳送者送的訊息,不一定以相同順序到達所有成員
💡
關鍵

IP multicast 與 UDP 一樣會遺漏失效,不保證每個成員都收到(不可靠群播);且不同成員可能收到不同順序。

STEP 2

生活妙喻

嘈雜教室裡的口頭傳話

老師在吵鬧的教室裡喊話

想像老師(傳送者)在一間吵鬧的大教室對全班(群組)口頭宣布事情:

  • 坐後排的、戴耳機的同學可能根本沒聽到(遺漏失效——有人收到有人沒)。
  • 如果消息是靠同學一排排往後傳,中間有人睡著沒傳(router 故障),他後面整排都漏聽
  • 老師連續喊了三件事,但因為回音與分心,有的同學記得的順序跟別人不一樣(亂序)。

為什麼這對某些事很要命?

  • 如果是『今天可以早退喔~』漏聽一個人沒差(服務探索:偶爾漏掉沒關係)。
  • 但如果是一群一模一樣的伺服器副本要『按相同順序執行相同操作以保持一致』,那麼:
    • 只要有一個副本漏掉一個請求,它就和其他人不一致了。
    • 只要順序不同,幾個副本的狀態就會分岔。

這就是為什麼某些應用需要比 IP multicast 更強的保證。

💡
關鍵

IP multicast 像吵鬧教室的口頭傳話:有人漏聽、傳話鏈斷一節後面全漏、記得的順序還可能不同;對要求一致的複製服務很致命。

STEP 3

實用超能力

什麼時候需要更強的群播保證?

看應用需求決定要多強

回頭看群播的幾種用途,需求差很多:

用途 對可靠性/順序的需求
容錯複製服務 很高:要嘛全收要嘛全不收,且多半要相同順序
服務探索(如 Jini) 低:偶爾漏一個請求無所謂,下次再問
複製資料 視複製方法與『副本要多即時』而定
事件通知 依應用而定

兩種更強的保證

為了滿足高需求應用,需要兩個比 IP multicast 更強的性質:

  • 可靠群播 (reliable multicast):一則訊息要嘛群組所有成員都收到,要嘛全都沒收到(all-or-nothing,原子性)。
  • 全序群播 (totally ordered multicast):送給群組的所有訊息,抵達所有成員的順序都完全相同。這是最嚴格的順序保證。
flowchart TD
  需求{應用要求多強} --> 低[偶爾漏可接受]
  需求 --> 高[必須一致]
  低 --> IP[IP multicast 就夠]
  高 --> R[需要可靠群播 全收或全不收]
  高 --> T[需要全序群播 所有人順序相同]

IP multicast 並不提供這兩種保證——它們要在更高層另外建構(第 15 章會說明如何實作)。所以工程上要先問清楚:我的應用到底需要多強的群播? 別為不需要的保證付出昂貴代價,也別在需要一致時誤用了不可靠群播。

💡
關鍵

依應用需求選保證強度:可靠群播是全收或全不收(原子性),全序群播是所有成員順序完全相同;IP multicast 兩者都不提供,需在更高層建構。

🔆
生活妙喻:不可靠群播的遺漏失效 ≈ 吵鬧教室裡的口頭宣布

老師喊一句,後排或分心的同學可能沒聽到——有人收到有人沒,正是群播不保證每個成員都收到。

🔆
生活妙喻:可靠群播(all-or-nothing) ≈ 全班都簽收的聯絡簿,否則整批作廢

要嘛全班都簽收這則通知,要嘛當作沒發過;不會出現只有部分人收到的尷尬局面。

🔆
生活妙喻:全序群播 ≈ 規定全班按同一份逐字稿、同樣順序抄筆記

所有成員收到所有訊息的順序完全一致,就像每個人手上的筆記順序都一模一樣,狀態才不會分岔。

本節字彙

不可靠群播 (unreliable multicast)
如 IP multicast,因遺漏失效而不保證訊息送達任何特定成員——可能有些收到、有些沒。
🧠 unreliable=靠不住,送出去不保證大家都收到。
可靠群播 (reliable multicast)
提供原子性的群播:一則訊息要嘛所有成員都收到、要嘛全都沒收到。
🧠 all-or-nothing——全有或全無。
全序群播 (totally ordered multicast)
最嚴格的順序保證:送給群組的所有訊息,抵達所有成員的順序都完全相同。
🧠 total order=全體一致的順序,人人筆記順序相同。
IP multicast 的失效特性和下列哪一個最相似?
一個複製服務由多個伺服器副本組成,它們必須以相同順序執行相同操作以維持一致。若直接用 IP multicast 送請求,最大的風險是什麼?
『可靠群播 (reliable multicast)』提供的核心保證是什麼?
05

在網路上再疊一層:覆蓋網路與 MPI

覆蓋網路是疊在底層網路之上的虛擬網路,有自己的定址、協定與路由,能為特定應用提供新服務。Skype 就是用 super node 架起的覆蓋網路。

覆蓋網路與 Skype

覆蓋網路是疊在底層網路之上的虛擬網路,有自己的定址、協定與路由,能為特定應用提供新服務。Skype 就是用 super node 架起的覆蓋網路。

STEP 1

深度探秘

在現有網路之上『疊出』新網路

為什麼要網路虛擬化?

Internet 協定提供了很棒的通訊積木,但問題來了:各式各樣的應用(P2P 檔案分享、Skype、影音串流、多人遊戲…)需求天差地別。直接去改 Internet 協定來迎合每個應用是不切實際的——對一個有利的改動,可能害到另一個。

解法是網路虛擬化 (network virtualization):在既有網路(如 Internet)之上,建出許多不同的虛擬網路,每個都為某類應用量身打造。

覆蓋網路是什麼?

覆蓋網路 (overlay network) 就是這樣一個虛擬網路:由節點與虛擬連結組成,疊在底層網路(如 IP 網路)之上,提供底層原本沒有的東西:

  • 為某類應用量身的服務(例如多媒體內容分送)
  • 在特定環境下更有效率(例如 ad hoc 網路的路由)
  • 額外功能(例如群播、安全通訊)

關鍵在於:覆蓋網路有自己的定址、協定與路由演算法,全部重新定義來滿足特定應用——它其實是一種『存在於標準架構之外的層 (layer)』,因而擁有重新定義核心元素的自由。

💡
關鍵

覆蓋網路是疊在底層網路之上的虛擬網路,有自己的定址、協定與路由,可在不改底層的前提下為特定應用提供新服務。

STEP 2

生活妙喻

在公路上畫出專屬路線

同一條公路,多套路線

把底層 Internet 想成一張既有的公路網。要為不同需求各蓋一條新公路(改底層協定)又貴又互相干擾。

覆蓋網路的作法是:不動公路,而是在地圖上疊一層自己的路線規劃

  • 公車路線(一套覆蓋):站牌、班次、走法都自己定,跑在現有馬路上。
  • 觀光導覽路線(另一套覆蓋):同樣的馬路,卻有完全不同的停靠點與順序。
  • 兩套路線同時存在、互不干擾,都跑在同一張公路網上。

這正呼應了 Saltzer 的端到端論點 (end-to-end argument):與其去改底層公路,不如在上層為特定應用打造專屬路線。

有得必有失

好處 代價
不改底層就能定義新服務 多一層間接 (indirection),可能有效能損失
鼓勵實驗與客製化 比單純的 TCP/IP 架構更複雜
多套覆蓋可共存,架構更開放
💡
關鍵

覆蓋網路像在同一張公路網上疊出多套自訂路線:不動底層、各應用各有專屬走法、彼此共存,代價是多一層間接與複雜度。

STEP 3

實用超能力

Skype:真實世界的覆蓋網路

Skype 是覆蓋網路的經典案例

Skype 是個提供 VoIP(網路語音) 的 P2P 應用。它是個虛擬網路:在『』(目前在線的 Skype 用戶)之間建立連線,不需要 IP 位址或 port 就能發起通話——展示了如何在不改 Internet 核心架構的前提下提供進階功能。

它的架構:兩種節點

flowchart TD
  登入伺服器[Skype 登入伺服器] --> 普通主機A
  普通主機A[普通主機] --> 超級節點1[超級節點 SN]
  超級節點1 --> 超級節點2[超級節點 SN]
  超級節點2 --> 普通主機B[普通主機]
  超級節點1 --> 超級節點3[超級節點 SN]
  • 普通主機 (ordinary host):一般用戶的機器。
  • 超級節點 (super node):剛好條件夠好的普通主機——頻寬足、有全域 IP(沒躲在 NAT 後)、上線時間久。它們扛起加強的角色。

運作三步驟

  1. 登入:透過知名的登入伺服器認證,再連上一個選定的超級節點。每個用戶端快取著一批超級節點的位址(首次約 7 個,後來累積到數百個)。
  2. 找人:超級節點負責搜尋分散在各超級節點上的全域使用者索引,靠『擴張式搜尋』直到找到目標(平均聯絡約 8 個超級節點,約 3~4 秒)。
  3. 通話:找到後,用 TCP 傳遞通話訊號(呼叫與結束),用 UDP 或 TCP 串流音訊(偏好 UDP,但為穿越防火牆有時改用 TCP 加中介節點)。

重點啟示:Skype 把『找人+通話』這套複雜功能,完全做在應用層的覆蓋網路裡,底層 Internet 一行都不用改。

💡
關鍵

Skype 是覆蓋網路經典案例:用普通主機與超級節點組成 P2P 虛擬網路,靠超級節點搜尋分散式使用者索引找人,再用 TCP/UDP 通話,全程不改 Internet 核心。

🔆
生活妙喻:覆蓋網路 ≈ 在同一張公路網上疊出多套自訂路線

不動底層馬路,而是在上面規劃公車路線、觀光路線等各自的站點與走法,多套共存、互不干擾。

🔆
生活妙喻:多一層間接的代價 ≈ 轉乘比直達多花時間

覆蓋網路為了彈性多繞一層,就像轉乘雖然路線更貼合需求,但通常比直達多花一點時間。

🔆
生活妙喻:Skype 的超級節點 ≈ 社區裡熱心又設備齊全的鄰居當聯絡中心

條件好(頻寬足、有公開門牌、常在家)的鄰居自願當聯絡中心,幫大家轉接與找人,分擔系統的協調工作。

本節字彙

覆蓋網路 (overlay network)
疊在底層網路之上、由節點與虛擬連結組成的虛擬網路,有自己的定址、協定與路由,提供底層沒有的服務。
🧠 overlay=覆蓋層,疊在現有網路『上面』的一層。
網路虛擬化 (network virtualization)
在既有網路之上建構多個各自為特定應用打造的虛擬網路的技術。
🧠 把一張實體網路『虛擬』成許多張,各做各的事。
超級節點 (super node)
Skype 中條件較佳(頻寬、可達性、可用性)的普通主機,承擔搜尋與協調等加強角色。
🧠 super=超級,能力夠好就升級當協調中心。
為什麼要用覆蓋網路,而不是直接修改 Internet 底層協定來支援新應用?
下列哪一項是覆蓋網路相對於單純 TCP/IP 架構的『代價』?
Skype 中的『超級節點 (super node)』通常是怎麼來的?

案例研究:MPI

MPI 是高效能運算社群制定的訊息傳遞標準,把 send/receive 細分成同步/非同步、阻塞/非阻塞等多種變體,給程式設計師精細的控制權。

STEP 1

深度探秘

為高效能運算量身的訊息傳遞標準

MPI 是什麼?

MPI(Message Passing Interface,訊息傳遞介面) 是由高效能運算 (HPC) 社群在 1994 年制定的標準,目的是統一當時各家各自為政、互不相容的訊息傳遞作法。

它延續了訊息傳遞『簡單、實用、有效率』的精神,再加上可攜性 (portability):提供一個獨立於作業系統與語言特定 socket 介面的標準化介面。應用透過一個 MPI 函式庫來使用,支援多種作業系統與語言(如 C++、Fortran)。

為什麼用在 HPC?

因為這類系統效能至上——訊息傳遞輕量、開銷小,正合需求。MPI 也很有彈性,光是各種變體就定義了超過 115 個操作

比 4.2 節多了什麼?

架構模型和 4.2.1 節類似,但多了一個關鍵:傳送端與接收端都有MPI 函式庫緩衝區 (MPI library buffer),由函式庫管理,用來暫存傳輸中的資料。這讓 MPI 能提供更細緻的『送』的語意。

💡
關鍵

MPI 是 HPC 社群制定的訊息傳遞標準,統一各家作法、強調可攜性與效率,定義超過 115 個操作;架構多了由函式庫管理的緩衝區。

STEP 2

生活妙喻

blocking 的真義:『卡到可以安全放手為止』

重新理解 blocking

在 MPI 裡,blocking 不是『卡到對方收到』,而是『卡到可以安全返回為止』——也就是『你的應用緩衝區可以被重複使用了』。但『安全』的定義,依不同變體而不同。

寄快遞的比喻

想像你要寄一份重要文件(應用緩衝區裡的資料),手邊只有這一份原稿,得確定『可以拿這張桌子做下一件事』才算放手:

MPI 操作 什麼時候算『安全放手』 快遞比喻
MPI_Ssend(同步) 對方已收到才返回 一定要等收件人簽收回條才走
MPI_Bsend(緩衝) 資料已複製到 MPI 緩衝區就返回(仍在途中) 交給快遞公司的暫存倉就走,不等送達
MPI_Rsend(就緒) 程式員保證接收端已準備好,省去握手 你已知對方在門口等,直接丟下就走(猜錯就出事)
MPI_Send(通用) 只要求達到上述某種安全等級即可 交給可靠管道,細節不管

MPI_Rsend 是個『有點危險』的操作:如果『對方已就緒』的假設是錯的,它就會失敗——但賭對了就能省掉握手、跑得更快。

💡
關鍵

MPI 的 blocking 意指『卡到應用緩衝區可安全重用為止』;Ssend 等收到、Bsend 複製到緩衝就返回、Rsend 賭對方已就緒以省握手、Send 是通用版。

STEP 3

實用超能力

阻塞/非阻塞、同步/非同步,與群體通訊

兩個維度,優雅對稱

MPI 把『同步/非同步』和『阻塞/非阻塞』兩件事乾淨地分開,於是每個 send 變體都有對稱的兩款:

阻塞 (Blocking) 非阻塞 (Non-blocking)
通用 MPI_Send MPI_Isend
同步 MPI_Ssend MPI_Issend
緩衝 MPI_Bsend MPI_Ibsend
就緒 MPI_Rsend MPI_Irsend
  • 非阻塞版(名字裡有 I):呼叫立刻返回,給你一個通訊請求 handle,之後用 MPI_Wait(等它完成)或 MPI_Test(檢查完成沒)來追蹤進度。命名規則一致,很好記。

收也有兩款

  • MPI_Recv:阻塞收
  • MPI_Irecv:非阻塞收

而且送與收的變體可任意搭配,給程式員對訊息傳遞語意豐富的控制權。

不只一對一:群體通訊

除了點對點,MPI 還定義了豐富的多方通訊 (collective communication),例如:

  • scatter(散播):一對多——把資料切開分送給多個行程
  • gather(收集):多對一——把多個行程的資料匯集回來
flowchart TD
  根行程[根行程] -->|scatter 一對多| 行程1
  根行程 -->|scatter| 行程2
  根行程 -->|scatter| 行程3
  行程1 -->|gather 多對一| 匯集[根行程匯集]
  行程2 -->|gather| 匯集
  行程3 -->|gather| 匯集

一句話總結:MPI 用一致的命名與兩個正交維度,把訊息傳遞的各種語意拆得清清楚楚,再補上 scatter/gather 等群體操作,成為 HPC 的通用語言。

💡
關鍵

MPI 把同步/非同步與阻塞/非阻塞兩維度分開,每個 send 有對稱的阻塞與非阻塞版(含 I 者立即返回並用 MPI_Wait/MPI_Test 追蹤);還支援 scatter 一對多與 gather 多對一等群體通訊。

🔆
生活妙喻:blocking 意指『可安全重用緩衝區』 ≈ 確定可以清桌子做下一件事才算放手

blocking 不是等對方收到,而是等到你的工作桌(應用緩衝區)可以拿來做下一件事為止;什麼時候算安全,依變體而定。

🔆
生活妙喻:MPI_Bsend vs MPI_Ssend ≈ 交給快遞暫存倉就走 vs 一定等收件人簽收才走

Bsend 把資料複製到 MPI 緩衝區就返回(像交給暫存倉),Ssend 則等對方真正收到才返回(像等簽收回條)。

🔆
生活妙喻:MPI_Rsend 的風險與優化 ≈ 已知對方在門口等,直接丟下就走

你保證接收端已準備好,就能省掉確認握手跑得更快;但若這個假設錯了,操作就會失敗。

本節字彙

MPI (Message Passing Interface)
高效能運算社群制定的標準化訊息傳遞介面,獨立於作業系統與語言,支援同步/非同步與阻塞/非阻塞等多種變體。
🧠 Message Passing Interface——專為傳訊息設計的標準介面。
MPI 函式庫緩衝區 (MPI library buffer)
由 MPI 函式庫在傳送端與接收端管理、用來暫存傳輸中資料的緩衝區。
🧠 資料在送達前先寄放的『中繼倉庫』。
群體通訊 (collective communication)
涉及多個行程的多方通訊模式,如 scatter(一對多散播)與 gather(多對一收集)。
🧠 collective=集體的——大家一起參與的通訊。
MPI 由高效能運算社群制定,當初最主要的動機是什麼?
在 MPI 裡,一個 blocking 的 send『返回』時,真正保證的是什麼?
MPI_Rsend 與 MPI_Ssend 的關鍵差異是什麼?