總覽與定位
WebRTC 不是「另一個即時聊天協定」——它是瀏覽器原生的即時媒體與 P2P 資料通道:信令走你自己的通道,媒體走 ICE + DTLS-SRTP。
WebSocket 那篇處理的是應用訊息的全雙工通道;SSE 那篇處理的是單向事件串流。兩者都還在「文字/JSON/位元組訊息」的世界。本篇要進的是另一個世界:音訊、視訊、以及低延遲的二進位 DataChannel——而且經常要直接連到對端 peer,中間沒有你的應用伺服器當轉發站。
這三塊的差別不在技術棧,而在失敗模式與帳單:信令斷了可以重連補送,媒體抖了只能靠緩衝與碼率適配——而最右邊那塊是唯一會按流量計費的,架構討論最後多半卡在它。
聊天與信令走 WebSocket(或 HTTPS):房間加入、誰上線、SDP 交換。媒體走 WebRTC:麥克風、鏡頭、螢幕分享、遊戲狀態的 DataChannel。
NAT/STUN/TURN 的類型表與映射語意已在 NAT 與 Port Forwarding 那篇定調——本篇引用結論(Symmetric 多半要 TURN;host/srflx/relay 三類候選),不重做類型表,改從端到端 ICE 狀態機與媒體拓撲切入。
WebTransport datagram 不是 WebRTC:前者是瀏覽器↔伺服器的 QUIC 應用通道;後者是 peer 連線 + 媒體協定棧。面試若把兩者混成「都是 UDP 所以一樣」,會直接失分。
信令與媒體分離
規格故意把「怎麼找到對方、怎麼談條件」和「談完之後封包怎麼飛」拆成兩個平面——這是整套設計的第一性原理。
- SDP Offer/Answer
- ICE candidate trickle
- 房間/身分/權限
- 掛斷、重協商(renegotiation)
- RTP/RTCP 影音
- SCTP over DTLS(DataChannel)
- ICE 打洞或 TURN 中繼
- NACK/PLI/Simulcast 適配
左右兩欄要的東西正好相反:左邊要「一定要到」,右邊要「晚到不如不到」。把右欄的需求丟進左欄的通道,就會寫出用 WebSocket 推視訊幀的設計。
為什麼要分離?因為兩者的失敗模式完全不同。信令斷了,你可以重連 WebSocket、補發最後一則 SDP;媒體抖了,你要的是 jitter buffer 與碼率適配,而不是把整段視訊塞進一條可靠的 TCP 聊天通道。把視訊幀當 WebSocket binary frame 硬推,短期能 demo、長期會在 NAT、擁塞與瀏覽器功耗上翻車——這也是為什麼「用 WS 傳音訊」幾乎永遠是面試裡的錯誤答案。
控制面一定經過伺服器,媒體面盡量不經過:兩條路徑的端點相同、走法卻完全不同。伺服器完全不碰媒體封包正是 P2P 通話省頻寬的來源,TURN 只在直連打不通時才把媒體拉回中繼。
很多架構圖把「WebRTC server」畫成一個方塊。實務上至少拆成:信令(常與既有後端同進程)、STUN/TURN(coturn 之類)、以及(多人時)SFU。把它們綁成單體,會讓水平擴充與計費邊界糊成一團。
SDP Offer/Answer
SDP 是雙方對「我能播什麼、我要聽什麼、用哪組編解碼」的文字契約;Offer/Answer 是交換這份契約的狀態機。
1 · 最小交握節奏
sequenceDiagram
participant A as Peer A
participant S as 信令伺服器
participant B as Peer B
A->>A: createOffer
A->>A: setLocalDescription
A->>S: offer SDP
S->>B: offer SDP
B->>B: setRemoteDescription
B->>B: createAnswer
B->>B: setLocalDescription
B-->>S: answer SDP
S-->>A: answer SDP
A->>A: setRemoteDescription
Note over A,B: 之後 candidate 經同一條信令陸續補送
看兩端的對稱性:A 與 B 各做一次「產生描述 → setLocalDescription → 經信令送出 → 對端 setRemoteDescription」,圖的下半段就是 Answer 的回程。信令伺服器只負責轉送、不解讀 SDP 內容——這也是它能與既有後端同進程的原因。
順序不能亂:本地先 setLocalDescription,對端才能 setRemoteDescription。現代瀏覽器多半搭配 Trickle ICE——SDP 先送出,candidate 稍後陸續經信令補上,不必等蒐集完才通話。
2 · SDP 裡面試真正會問的欄位
| 區塊/語意 | 你在協商什麼 | 實務影響 |
|---|---|---|
m= lines | 音訊/視訊/application 媒體列 | 有沒有 DataChannel、幾路 track |
| codec/fmtp | VP8/VP9/H.264/AV1、opus | 互通性、硬解、CPU/電池 |
| ICE ufrag/pwd | 連線認證材料 | 與 candidate 綁定;重協商會換 |
| fingerprint | DTLS 憑證指紋 | 防中間人;與信令通道信任綁在一起 |
| a=setup/actpass | 誰當 DTLS client | Answer 端通常定角色 |
交換完 Offer/Answer,只代表雙方同意能力集合;封包能不能通,還要看 ICE。反過來說:ICE 已連上但 SDP 沒談妥 codec,仍然沒有可用的視訊 track。面試答案要拆成「協商成功」與「連線成功」兩層。
ICE:host/srflx/relay 與連線狀態機
ICE 的工作是:蒐集所有可能的位址、兩兩配對探測,挑一條「打得通且夠好」的路徑——打不通就降級到 TURN。
1 · 三類 candidate(引用 NAT 篇結論)
三張卡由左至右就是 ICE 的偏好順序(type preference 126 / 100 / 0):host 最快但適用面最窄,relay 一定通但最貴——中間那張的 Symmetric 但書,正是 relay 必須存在的理由。
2 · 連線狀態(你要會講的那幾個)
stateDiagram-v2
[*] --> new
new --> checking: 開始配對探測
checking --> connected: 找到可用 pair
checking --> failed: 全部 pair 皆失敗
connected --> completed: 探測收斂
connected --> disconnected: 網路抖動
completed --> disconnected: 網路抖動
disconnected --> connected: 自行恢復
disconnected --> failed: 逾時仍未恢復
failed --> [*]
這張圖的重點在分支與回邊,不在那條直線:checking 有兩條出路,而 disconnected 有一條回到 connected 的回邊——有沒有這條回邊,正是它與 failed 的本質差別。
connected 表示已有一對可用 candidate;completed 表示探測收斂。disconnected 常是短暫網路抖動(ICE 會繼續救),failed 才是「這條 PeerConnection 救不回來、該重談或換 TURN」。產品埋點應分開這兩種,不然 on-call 會被誤報淹爆。
STUN 只告訴你反射位址;能不能打洞取決於雙方 NAT 行為。企業網、電信 CGNAT、Symmetric 組合失敗率不低。正式產品的預設心智模型應是:P2P 是優化,TURN 是 SLA——並在儀表板同時看「直連率」與「每分鐘 TURN GB」。
DTLS-SRTP 與 DataChannel
媒體加密不是「再包一層 TLS over TCP」——WebRTC 在 UDP 上做 DTLS,再把金鑰導給 SRTP;資料通道則跑在 DTLS 上的 SCTP。
1 · 為什麼是 DTLS-SRTP
影音是 RTP 封包流,需要的是逐包加解密與可接受的丟包,而不是像 HTTPS 那樣的可靠位元流。流程簡化為:ICE 通了 → 雙方做 DTLS 握手(憑證指紋已在 SDP 對過)→ 匯出金鑰給 SRTP/SRTCP。面試關鍵句:信任錨在信令通道——若攻擊者能竄改你的 SDP fingerprint,媒體加密也守不住。
2 · DataChannel:可靠/不可靠是你選的
| 模式 | 底層語意 | 典型用途 |
|---|---|---|
| ordered + reliable | 近似 TCP | 檔案、白板快照、控制訊息 |
| unordered + partial reliability | 可丟、可不排序 | 游標、遊戲狀態、感測器 |
| 與 WS 對照 | WS 永遠可靠有序 | 聊天/信令仍偏 WS;高頻狀態可 DataChannel |
它不是「免費多一條 TCP」。NAT 穿透、TURN 費用、連線生命週期都與媒體共用。把所有後端 API 搬進 DataChannel 通常得不償失;它擅長的是peer 之間的低延遲資料,不是取代你的 REST/gRPC。
媒體架構:P2P、SFU、MCU 與適配
兩人可以直連;三人以上,拓撲選擇決定的是 CPU、頻寬帳單與誰來做轉碼。
看中間那張:SFU 之所以是預設答案,是因為它把客戶端上行固定成一路,又不像 MCU 那樣把轉碼成本全攬到伺服器上。
- 客戶端同時送高低碼率層
- SFU 依訂閱者網路挑層轉發
- 換來的是上行成本與編碼複雜度
- 吸收網路抖動,換取可播放連續性
- 太大 → 口型不同步、互動感差
- 太小 → 卡頓、PLC 頻繁
兩者都在同一條軸上滑動:Simulcast 用上行成本換下行的品質彈性,jitter buffer 用延遲換播放連續性——動任何一個之前,先確認你要犧牲哪一邊。
SFU 已經讓媒體經過你的資料中心;若 peer↔SFU 還大量走 TURN relay(雙重中繼),帳單與延遲會疊加。部署上常把 TURN 與 SFU 放同一區域、優化 ICE 優先順序,並監控 relay 比例當成 SLO。
互動 · ICE 候選配對空間
ICE 不是走一條固定的狀態序列,而是在「A 的候選 × B 的候選」這張平面上,依優先度由高到低找第一個能通的座標。
第 4 節的狀態機只交代了結果——checking 之後是 connected 還是 failed——沒交代那個結果是怎麼來的。
實際發生的事是:兩端各自蒐集一批候選,兩批做外積形成一張 checklist,每一格是一對 candidate pair;
ICE 依 RFC 8445 的優先度公式把所有格子排序,由高到低逐格送 STUN Binding Request,第一個通的就被提名,其餘取消。
所以「為什麼這通電話走了 TURN」這個問題,答案永遠是一個座標,不是一個狀態。
下面的平面把整個過程攤開。設定兩端的 NAT 型別、網卡數、是否同一 LAN、TURN 是否可用, 平面的尺寸、每一格的優先度與可穿透性、掃描軌跡、被提名的那一格,以及探測次數、收斂時間與 TURN 曝險量,全部由這些輸入算出來。
四個值得停下來看的座標(以預設的兩端 Port-Restricted、不同 LAN、TURN 開啟、A 兩張網卡為起點,
此時平面是 4 × 3 = 12 格,掃到第 6 格的 srflx ↔ srflx 才通,RTT 40 ms、收斂 160 ms):
(1) 把 Peer A 改成 Symmetric,第 6 格當場轉紅,軌跡繼續往下走到第 7 格的 host #1 ↔ relay——
注意被提名的不是 srflx ↔ relay,因為 host 的 type preference 比 srflx 高;RTT 跳到 76 ms、收斂 216 ms。
(2) 但 Symmetric 不等於一定要 TURN:把 Peer B 改成 Full Cone 或 Address-Restricted,srflx ↔ srflx 又通了——
只有「Symmetric 對上 Symmetric」或「Symmetric 對上 Port-Restricted」才真的打不通。
(3) 在 Symmetric 對 Port-Restricted 的狀態下關掉 TURN,平面縮成 3 × 2 = 6 格且全部轉紅,狀態落到 failed。
(4) 勾選「同一個 LAN」,左上角那格(優先度最高的 host #1 ↔ host #1)直接轉綠,探測次數從 6 降到 1,收斂時間 160 → 22 ms。
兩點簡化要說明:本模擬省略了 peer-reflexive 候選,所以「私網 host 對上公網 srflx」一律判為不通;
另外 relay 一端被視為必定可達,實務上仍可能被防火牆的 UDP 封鎖擋掉。
connected 與 failed 只是這場搜尋有沒有找到座標的結果,而 relay 之所以貴又慢,是因為它是這張平面上優先度最低、卻也是唯一保證有解的那一列。
面試折衷與邊界
把選型壓成可答的判準:什麼時候 WebRTC、什麼時候不要、成本與安全邊界在哪。
| 問題 | 優先選項 | 別做的事 |
|---|---|---|
| 瀏覽器即時音視訊 | WebRTC +(多人)SFU | 用 WebSocket 推 raw frame |
| 聊天/信令/房間狀態 | WebSocket 或 HTTPS | 為每則聊天開 PeerConnection |
| 瀏覽器↔自有後端的不可靠datagram | WebTransport(若棧支援) | 把 WebTransport 說成 WebRTC |
| 企業網/高失敗率 NAT | 預設備妥 TURN,監控 relay % | 假設 STUN 萬能 |
| 錄製、轉碼、統一佈局 | SFU + 旁路錄製/必要時 MCU | 在每個客戶端各自混流當唯一方案 |
媒體加密不代表「信令可信」。SDP、房間權杖、TURN 認證(時間限額憑證)都要當敏感面。另外:螢幕分享與裝置權限是瀏覽器 UX/隱私問題,不是 ICE 能解決的;權限被拒時要有降級路徑(純語音、純 DataChannel)。
信令可控、媒體可丟、TURN 可買、SFU 可擴。 先畫這四塊的責任與帳單,再談 codec 與 Simulcast 細節。