探索 WebRTC:如何實現即時通訊
透過這篇初學者友善的 WebRTC 教學,學習即時通訊技術!跟隨 Alice 和 Bob 的視訊通話旅程,了解 SDP、ICE、STUN/TURN 及 NAT 穿越原理。探索 WebRTC 如何實現即時通訊,簡單逐步指南帶你入門!
引子:Alice 和 Bob 的視訊聊天之旅
假設 Alice 和 Bob 正在使用一個 WebRTC 視頻聊天應用,Alice 想給 Bob 打電話,開啟一場即時對話。他們的設備位於不同的網絡,彼此隔著 NAT(Network Address Translation,網絡地址轉換)的屏障。Alice 的瀏覽器如何與 Bob 的瀏覽器建立連接? 讓我們跟隨他們的旅程,探索 WebRTC 的運作過程。
Alice 的瀏覽器首先生成一個 SDP(Session Description Protocol)Offer,包含她支持的媒體格式(例如音頻或視頻編解碼器)和可能的網絡地址(ICE Candidates)。為了收集這些地址,Alice 的瀏覽器向 STUN 服務器發送請求,獲取她的公網地址,並將其添加到 ICE Candidates 列表中。完成後,Alice 通過一個信令通道(Signaling Channel)將 SDP Offer 發送給 Bob。
Bob 收到 SDP Offer 後,他的瀏覽器同樣生成一個 SDP Answer,並收集自己的 ICE Candidates,然後通過信令通道回傳給 Alice。雙方交換 SDP 後,ICE 協議開始工作:它測試雙方的 ICE Candidates,找到可行的網絡路徑。如果 P2P 路徑失敗,ICE 會向 TURN 服務器請求一個中繼地址,通過中繼傳輸數據。最終,Alice 和 Bob 的視頻通話順利開始,媒體數據在他們之間流動。
這個過程看似簡單,但背後卻涉及多個元件的協作。Alice 和 Bob 是如何一步步實現這場通話的? 讓我們用逐步拆解過程,探索 WebRTC 的每個環節。
如果您想在瀏覽器中實現即時通信,該怎麼做?
即時通信(Real-Time Communication)是現代應用的核心需求,例如視頻通話、語音聊天或直播。如果您想讓兩台設備(Peer A 和 Peer B)直接傳輸音視頻數據,該如何開始? WebRTC(Web Real-Time Communication)提供了一個開源解決方案,讓瀏覽器和應用程式能實現點對點(P2P)通信,無需額外插件。它主要用於傳輸語音、影像和文字,並基於 UDP 協議以實現低延遲和高效率。目前由 Google、Mozilla、Opera 等各種大型組織開發。從某種意義上說,WebRTC 是標準化 API 的集合,並且在開發人員可用的實作架構方面具有多功能性。
以 Alice 和 Bob 的場景為例,WebRTC 的運作依賴多個元件,包括 SDP、ICE 協議、STUN/TURN 服務器和信令機制。那麼,Alice 和 Bob 如何交換通信規則呢?讓我們來看看 SDP 的作用。
使用 SDP 交換通信規則
如果 Alice 和 Bob 想「認識彼此」,如何交換基本信息?在 Alice 和 Bob 的故事中,Alice 的瀏覽器先生成了一個 SDP Offer,發送給 Bob。如果 Alice 想告訴 Bob 自己的能力,該用什麼方式? 這時,SDP(Session Description Protocol)登場了。
SDP 是一種描述會話的協議,包含媒體類型、編解碼器、傳輸協議等信息,但它本身不負責傳輸數據。
Alice 會生成一個 SDP Offer,發送給 Bob;Bob 根據自己的能力回傳一個 SDP Answer。這個過程就像兩個人初次見面,交換名片,說清楚「我能提供什麼」。具體來說,Alice 通過 RTCPeerConnection.createOffer() 創建 SDP Offer,並設置為本地描述(setLocalDescription);Bob 收到 Offer 後,通過 createAnswer() 創建 SDP Answer,並同樣設置本地描述。
以下圖表展示了 Alice 和 Bob 的 SDP 交換過程:
上圖展示了 Alice 和 Bob 如何通過 SDP 協商通信規則:
- Alice 創建 SDP Offer 並設置本地描述,狀態變為
have-local-offer
。 - Bob 接收 Offer 後,設置遠端描述,狀態變為
have-remote-offer
。 - Bob 創建 SDP Answer 並發送給 Alice,雙方最終回到
stable
狀態,為後續的 ICE 交換奠定基礎。
Alice 和 Bob 通過 SDP 完成了媒體能力的協商,例如使用相同的編解碼器,從而為後續的數據傳輸奠定基礎,但他們位於不同的網絡,尚未建立直接連接,他們該如何交換這些 SDP 消息呢?讓我們來看看信令機制的角色。
通過信令機制交換 SDP
如果 Alice 和 Bob 位於不同的網絡,無法直接通信,該如何交換 SDP?這時需要一個「信令機制」(Signaling Mechanism)來幫忙。信令的目的是在雙方建立 P2P 連接前,負責交換元數據(SDP 和後續的 ICE Candidates)。在 Alice 和 Bob 的故事中,他們通過一個信令通道交換 SDP。
最常見的信令機制是使用一個中心化的 Signal Server(信令服務器),通過 WebSocket 或 HTTP 傳遞信息。但信令機制不一定是服務器,您也可以用電子郵件、聊天應用,甚至手動複製 SDP 來交換。信令機制解決了 Alice 和 Bob 還未連通時的「介紹問題」,讓他們能交換必要的協商信息,為後續的網絡路徑選擇做好準備。
有了信令機制,Alice 和 Bob 成功交換了 SDP,但他們的設備位於 NAT 後,彼此的地址被隱藏,如何才能找到對方呢?接下來我們來探索 NAT 的挑戰以及解決方案。
穿透 NAT 障礙:ICE 協議與 STUN/TURN 的協作
NAT 的挑戰:地址隱藏問題
如果 NAT 將 Alice 和 Bob 的地址隱藏起來,讓他們無法直接找到對方,該怎麼辦? 在 Alice 和 Bob 的場景中,他們的設備位於不同的網絡,彼此隔著 NAT 的屏障。NAT(Network Address Translation,網絡地址轉換)通過路由器將私有 IP 轉換為公網 IP,管理流量,這雖然節省了 IP 地址資源,但也讓外部設備無法直接訪問內部地址(詳細介紹請見附錄:什麼是 NAT?)。
這種地址隱藏機制讓 Alice 和 Bob 無法直接建立連接,因為他們不知道對方的公網地址,也無法穿透 NAT 的限制。這時,WebRTC 引入了 ICE(Interactive Connectivity Establishment)協議來解決問題。ICE 協議通過收集可能的網絡地址並測試路徑,幫 Alice 和 Bob 找到彼此,穿透 NAT 的障礙。
了解了 NAT 的挑戰後,ICE 協議如何知道 Alice 和 Bob 在公網上的位置呢?讓我們看看 STUN 的作用。
使用 STUN 獲取公網地址
如果 Alice 想告訴 Bob 自己的位置,但不知道自己在公網上是什麼樣子,該怎麼做? 這時需要 STUN(Session Traversal Utilities for NAT)服務器。在 Alice 和 Bob 的故事中,Alice 的瀏覽器向 STUN 服務器發送請求,獲取她的公網地址。
STUN 的作用是幫用戶回答「我是誰」的問題。
Alice 向 STUN 服務器發送一個 Binding Request,STUN 服務器回傳 Alice 的公共 IP 和端口(例如 203.0.113.10:45678)。這個地址被稱為 Server-Reflexive Candidate,是 Alice 在公網上的「身份」。STUN 提供了 Alice 的公網地址,讓她能告訴 Bob 如何找到自己,為後續的 ICE 路徑選擇提供候選地址。
STUN 幫 Alice 和 Bob 獲取了公網地址,但如果 STUN 無法穿透 NAT,該怎麼辦?這時 TURN 服務器成為了後備方案。
使用 TURN 作為中繼備案
如果 STUN 無法幫 Alice 和 Bob 建立直接連接,該怎麼辦? STUN 並非萬能,例如當 Alice 位於對稱 NAT(Symmetric NAT)時,對稱 NAT 會為每個外部目標分配不同端口,這使得 STUN 提供的地址無法通用。這時,TURN(Traversal Using Relays around NAT)服務器成為備案。在 Alice 和 Bob 的故事中,如果 P2P 路徑失敗,ICE 協議會向 TURN 服務器請求中繼地址。
TURN 服務器提供一個中繼地址(Relayed Candidate),讓 Alice 和 Bob 的數據通過它轉發。雖然中繼會增加延遲和服務器負載,但它保證了通信的成功。
TURN 就像一個「代理人」,幫他們繞過 NAT 的限制。
您可能會問:既然已經有 Signal Server 可以傳遞資訊,為什麼還需要 TURN?Signal Server 的作用是交換元數據(SDP 和 ICE Candidates),但它不參與實際的音視頻數據傳輸。TURN 專為數據中繼設計,支持 UDP 協議,確保低延遲的即時通信,而 Signal Server 通常基於 TCP(例如 WebSocket),不適合處理高吞吐量的數據流。簡單來說,Signal Server 是「介紹人」,幫雙方交換聯繫方式;TURN 是「物流中轉站」,幫雙方傳遞實際數據。
TURN 作為 STUN 的後備方案,確保即使在最複雜的網絡環境中,Alice 和 Bob 也能連通。
STUN 和 TURN 為 Alice 和 Bob 提供了可能的網絡地址,但如何從這些地址中選擇最佳路徑呢?讓我們來看看 ICE 協議的具體運作。
ICE 協議:選擇最佳路徑
如果 Alice 和 Bob 有本地地址、公網地址和中繼地址,該如何選擇最適合的路徑? 這是 ICE(Interactive Connectivity Establishment)協議的核心功能。ICE 協議是一個框架,負責發現和選擇最佳的網絡路徑,讓 Alice 和 Bob 能建立點對點(P2P)連接。
ICE 協議首先收集所有可能的網絡地址,這些地址被稱為 ICE Candidates(ICE 候選地址)。每個 ICE Candidate 包含了 IP 地址、端口、協議(UDP 或 TCP)等信息,表示一種可能的通信方式。ICE Candidates 根據其來源,分為以下幾種類型:
- Host Candidate:本地地址,例如 Alice 設備的私有 IP 和端口(192.168.0.42:44323),適用於同一局域網內的直接通信。
- Server-Reflexive Candidate:通過 STUN 服務器獲取的公網地址,例如 203.0.113.10:45678,適用於簡單 NAT 環境。
- Relayed Candidate:通過 TURN 服務器分配的中繼地址,例如 TURN_SERVER_IP:3478,用於無法直接連通的情況。
ICE 協議的運作流程如下:
- 收集候選地址:Alice 和 Bob 的瀏覽器分別收集自己的 ICE Candidates,包括本地地址(Host)、STUN 提供的公網地址(Server-Reflexive)、TURN 分配的中繼地址(Relayed)。
- 交換候選地址:Alice 和 Bob 通過信令機制交換這些 ICE Candidates(詳見下一段)。
- 連接性測試:ICE 協議對每一對候選地址(例如 Alice 的本地地址與 Bob 的公網地址)進行 STUN 請求,檢查哪些路徑可行。
- 路徑選擇:ICE 根據優先級(例如 UDP 優先於 TCP,本地地址優先於中繼地址)和測試結果,選擇最佳路徑,通常優先 P2P,後備中繼。
ICE 協議統籌 STUN 和 TURN 的結果,動態選擇最佳路徑,確保 Alice 和 Bob 能在各種網絡環境中連通。(更多 ICE 協議和 ICE Candidates 細節請見附錄:深入了解 ICE 協議與 ICE Candidates)
ICE 協議需要 Alice 和 Bob 交換 ICE Candidates,但他們是如何做到這一點的呢?這又回到了信令機制的應用。
通過信令機制交換 ICE Candidates
在 Alice 和 Bob 的故事中,他們通過信令通道交換了 ICE Candidates。ICE Candidates 與 SDP 一樣,需要通過信令機制交換。例如,Alice 通過 Signal Server 發送自己的 ICE Candidates 給 Bob,Bob 回傳自己的候選地址。
在程式碼中,ICE Candidates 是由 RTCPeerConnection 自動收集的,並以事件形式觸發。以下是具體實現:
- Alice 的瀏覽器監聽 icecandidate 事件,當收集到新候選地址時,將其發送給 Bob:
#監聽並收集 ICE Candidates
peerConnection.addEventListener('icecandidate', event => {
if (event.candidate) {
console.log('收集到 ICE Candidate:', event.candidate);
// 通過信令機制發送
sendToSignalServer({ type: 'candidate', candidate: event.candidate });
}
});
- Bob 收到 Alice 的 ICE Candidate 後,通過 addIceCandidate 方法將其添加到自己的 RTCPeerConnection:
#接收並添加遠端 ICE Candidates
const candidate = getCandidateFromSignalingServer();
await peerConnection.addIceCandidate(candidate);
- Alice 也同樣接收 Bob 的候選地址,並添加到自己的 RTCPeerConnection:
const candidate = getCandidateFromSignalingServer();
await peerConnection.addIceCandidate(candidate);
信令機制確保 ICE Candidates 能順利交換,讓 ICE 協議有足夠的資訊來選擇路徑。(更多 ICE Candidates 交換範例請見附錄:深入了解 ICE 協議與 ICE Candidates)
總結:WebRTC 的條件式旅程
以 Alice 和 Bob 的視頻通話為例,他們從交換 SDP 到最終建立連接,只需幾毫秒,背後卻是多個元件的協作。讓我們回顧 WebRTC 的條件式旅程:
- WebRTC 提供了即時通信的基礎,讓 Alice 和 Bob 能直接傳輸數據。
- SDP 幫 Alice 和 Bob 協商通信規則,確保媒體兼容。
- 信令機制讓 Alice 和 Bob 在未連通前交換必要資訊。
- ICE 協議聯合 STUN 和 TURN,幫 Alice 和 Bob 穿透 NAT,選擇最佳路徑。
通過這種條件式的拆解,WebRTC 的每個元件都顯得「水到渠成」,它們的存在都是為了解決特定的問題。希望這篇文章能幫助您和讀者更清晰地理解 WebRTC,並激發更多思考!
參考資料:
- https://www.yasssssblog.com/tag/webrtc/
- MDN: https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/Connectivity
- WebRTC Tutorials 充滿完整前端介紹:https://getstream.io/resources/projects/webrtc/basics/sdp-messages/
- WebRTC建立连接之NAT/ICE/STUN/TURN知识点:https://www.nxrte.com/jishu/webrtc/8529.html
- WebRTC 互联网通信工作原理(客户端之间如何建立连接):https://www.nxrte.com/jishu/webrtc/49186.html
- [webrtc] 交互式连接建立(ICE):http://www.libsdl.cn/bbs/forum.php?mod=viewthread&tid=81
附錄一:什麼是網路位置轉換(NAT; Network Address Translation)?
您用於連接網際網路的任何裝置都有一個私有 IP 位址,而它可能連接的路由器則有一個公用 IP 位址。為了在兩個設備之間建立連接,重要的是一個設備能夠識別並聯繫另一個設備。為了實現這一點,我們使用 NAT — — 一種將私有 IP 位址轉換為公用 IP 位址的方法。
這些設備不會直接暴露在網路上。相反,所有流量都透過路由器與外界通訊。當您從遠端伺服器請求資源時,路由器負責將請求從本機電腦「路由」到該伺服器,並將回應從伺服器路由回本機。這些請求從裝置的私有 IP 位址轉換為具有唯一連接埠的路由器的公用 IP,然後將其儲存在 NAT 表中。這樣,就不需要為本地網路上的每個設備配備唯一的公共 IP。
對於網路上的世界來說,NAT 後面的所有裝置看起來都像是單一裝置。本地位址對外界來說是未知的,因此一些 IP 子網被保留給本地網絡,即:
- 10.0.0.0–10.255.255.255
- 172.16.0.0–172.31.255.255
- 192.168.0.0–192.168.255.255
這些位址不應該在互聯網上路由,而是保留供本地使用,因此可以重複使用。在這些私有 IP 位址前面將有一個 NAT 設備,該設備具有可全域路由的公共 IP 位址,並可為其網路執行從公用位址到私有位址的轉換,反之亦然。
如果我們想要在兩個或多個客戶端之間建立直接連接,就會出現問題,因為使用 NAT 時所有客戶端都會位於 NAT 後面。用戶端無法使用其內部 IP 位址與外部對等點通信,因為連線將會失敗。因此,應用程式必須發現其公共 IP 位址,然後與其網路外的對等方共用。
然而,僅僅知道公網 IP 位址並不能解決問題。到達 NAT 設備公用 IP 的任何封包還必須具有目標連接埠和 NAT 表中的項目,該條目可以將其轉換為內部 IP 到內部目標主機 IP 和連接埠。如果此條目不存在(如果用戶端直接與外界的對等方進行通信,則很可能發生這種情況),則 NAT 會丟棄資料包,因為 NAT 無法知道將該資料包傳送到何處(即該資料包需要傳送到哪個裝置)。
為了解決這種不匹配的問題,人們發明了各種遍歷技術,即 STUN、TURN 和 ICE。它們用於在網路上的對等點之間建立端到端的連接。
參考資料:
附錄二:深入了解 SDP 結構
SDP 結構概覽
SDP(Session Description Protocol,會話描述協議)是 WebRTC 中用於描述多媒體通信會話的標準格式。它包含建立點對點連接所需的關鍵資訊,例如編解碼器、源地址、音頻和視頻的媒體類型,以及其他相關屬性。以下是一個典型的 SDP 範例:
v=0
o=- 5168502270746789779 2 IN IP4 127.0.0.1
s=
c=IN IP4 0.0.0.0
t=0 0
m=audio 49170 RTP/AVP 0
a=rtpmap:0 PCMU/8000
m=video 51372 RTP/AVP 31
a=rtpmap:31 H261/90000
m=video 53000 RTP/AVP 32
a=rtpmap:32 MPV/90000
- v=:協議版本,這裡是 0。
- o=:會話的來源資訊,包括唯一識別碼、版本號、網絡類型(IN 表示 Internet)、地址類型(IP4)和地址。
- s=:會話名稱,這裡為空。
- c=:連接資訊,指定網絡地址(這裡是 0.0.0.0,實際應用中會替換為真實地址)。
- t=:會話的時間範圍,這裡是 0 0(無時間限制)。
- m=:媒體描述,例如 m=audio 表示音頻流,後面跟著端口(49170)、協議(RTP/AVP)和編解碼器格式(0 表示 PCMU)。
- a=rtpmap:編解碼器的映射,例如 a=rtpmap:0 PCMU/8000 表示 PCMU 編解碼器,採樣率為 8000 Hz。
通過交換 SDP 消息,Alice 和 Bob 可以了解彼此支持的編解碼器和媒體類型,確保通信兼容。
創建 SDP Offer
Alice 通過 RTCPeerConnection.createOffer() 方法創建 SDP Offer,該方法生成一個包含媒體資訊的 SDP 消息。以下是程式碼範例:
const offerOptions = {
iceRestart: true,
offerToReceiveAudio: true,
offerToReceiveVideo: true
};
const offer = await localPeerConnection.createOffer(offerOptions);
await localPeerConnection.setLocalDescription(offer);
- offerOptions 提供了額外控制,例如 offerToReceiveAudio 和 offerToReceiveVideo 決定是否接收音頻和視頻。
- setLocalDescription 將 Offer 設置為本地描述,表示 Alice 準備發起連接。
Bob 收到 Offer 後,通過 setRemoteDescription 將其設置為遠端描述:
await remotePeerConnection.setRemoteDescription(offer);
創建 SDP Answer
Bob 通過 RTCPeerConnection.createAnswer() 方法創建 SDP Answer,回應 Alice 的 Offer:
const answer = await remotePeerConnection.createAnswer();
await remotePeerConnection.setLocalDescription(answer);
Alice 收到 Answer 後,通過 setRemoteDescription 完成協商:
await localPeerConnection.setRemoteDescription(answer);
交換 SDP 消息
以下是一個完整的程式碼範例,展示如何交換 SDP 消息(這裡假設無信令服務器,直接在同一頁面交換):
let localStream, pc1, pc2;
async function attachLocalMedia() {
try {
const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
localVideo.srcObject = stream;
localStream = stream;
} catch (e) {
console.error(e);
}
}
async function peerConnection() {
pc1 = new RTCPeerConnection();
pc2 = new RTCPeerConnection();
localStream.getTracks().forEach(track => pc1.addTrack(track, localStream));
try {
const offer = await pc1.createOffer({
iceRestart: true,
offerToReceiveAudio: true,
offerToReceiveVideo: true
});
await pc1.setLocalDescription(offer);
await pc2.setRemoteDescription(offer);
const answer = await pc2.createAnswer();
await pc2.setLocalDescription(answer);
await pc1.setRemoteDescription(answer);
} catch (e) {
console.error(e);
}
}
這段程式碼模擬了 Alice 和 Bob 的 SDP 交換過程。實際應用中,SDP 交換通常通過信令服務器完成,後續還需交換 ICE Candidates 以穿透 NAT(詳見主文)。
附錄三:深入了解 ICE 協議與 ICE Candidates
ICE Candidates 的結構與類型
ICE Candidates(ICE 候選地址)是 WebRTC 中用於表示 Peer 的網絡位置的候選資訊。每個 ICE Candidate 包含了 IP 地址、端口、協議(UDP 或 TCP)等信息,表示一種可能的通信方式。以下是一個 ICE Candidate 的範例:
a=candidate:7344997325 1 udp 2043216322 192.168.0.42 44323 typ host
- 7344997325:候選地址的唯一標識。
- 1:組件 ID(通常 1 表示 RTP,2 表示 RTCP)。
- udp:使用的協議(WebRTC 通常優先 UDP,因為它更快,且媒體流能較好地恢復中斷)。
- 2043216322:優先級,ICE 用來排序候選地址。
- 192.168.0.42 44323:IP 地址和端口。
- typ host:類型,這裡是 Host Candidate(本地地址)。
ICE Candidates 根據其來源和生成方式,分為以下幾種類型:
- Host Candidate:本地地址,例如 Alice 設備的私有 IP 和端口(192.168.0.42:44323)。
- Server-Reflexive Candidate:通過 STUN 服務器獲取的公網地址(srflx),例如 203.0.113.10:45678。
- Peer-Reflexive Candidate:在 ICE 交換過程中,通過對稱 NAT 動態生成的地址(prflx),通常出現在 Trickle ICE(逐步交換候選地址)過程中。
- Relayed Candidate:通過 TURN 服務器分配的中繼地址(relay),例如 TURN_SERVER_IP:3478。
ICE Candidates 還可以基於不同協議生成:
- UDP 候選地址:WebRTC 通常優先 UDP,因為它速度快,適合即時通信。
- TCP 候選地址:當 UDP 不可用時,ICE 可能使用 TCP(支援 active、passive 或 so 模式),但 TCP 候選地址的支持因瀏覽器而異,且延遲較高。
ICE 協議的運作機制
ICE 協議的目標是選擇一對最佳的候選地址(Candidate Pair),讓 Alice 和 Bob 能順利通信。具體運作流程如下:
- 收集候選地址:Alice 和 Bob 的瀏覽器分別收集自己的 ICE Candidates。
- 交換候選地址:通過信令機制交換這些 ICE Candidates。
- 連接性測試:ICE 協議對每一對候選地址進行 STUN 請求,檢查哪些路徑可行。
- 角色分配與路徑選擇:ICE 協議選擇一個 Peer 作為「控制角色」(Controlling Agent),另一個作為「被控角色」(Controlled Agent)。控制角色負責最終決定使用哪對候選地址,並通過信令通知被控角色。選擇時,ICE 優先考慮延遲最低的路徑(通常是 P2P),並根據優先級排序(例如 UDP 優先於 TCP,本地地址優先於中繼地址)。
- 動態調整:在 ICE 會話期間,控制角色可能選擇多對候選地址,並通過信令更新配置。如果路徑失效,ICE 可以重新選擇(例如切換到 TURN 中繼)。
ICE 協議的進階機制
- ICE 回滾(Rollback):如果協商失敗(例如 SDP Offer 被拒絕),ICE 可以回滾到上一次穩定的配置(signalingState 為 stable 時的狀態),避免通話中斷。例如,Alice 和 Bob 正在通話,想升級視頻格式,但新 Offer 不兼容,ICE 會回滾到之前的配置。
- ICE 重啟(Restart):如果網絡環境變化(例如設備切換網絡),ICE 可以重啟,重新收集候選地址並進行協商。
- Trickle ICE:Alice 和 Bob 可以在交換 SDP 的同時,逐步交換新收集的 ICE Candidates(而不是等待所有候選地址收集完畢),加快連接建立。
結束通知
當某一類候選地址收集完成時,ICE 會發送一個「候選地址結束」通知(RTCIceCandidate 的 candidate 屬性為空字符串,表示該類型候選地址已收集完)。當所有候選地址都收集完畢時,ICE 會發送最終結束通知(iceGatheringState 變為 complete),開發者可以通過監聽 icegatheringstatechange 事件檢測這一狀態。
完整交換範例
以下是一個完整的程式碼範例,展示 Alice 和 Bob 如何交換 ICE Candidates(假設無信令服務器,在同一頁面交換):
let pc1, pc2;
const offerOptions = {
offerToReceiveAudio: 1,
offerToReceiveVideo: 1
};
function getName(pc) {
return (pc === pc1) ? 'pc1' : 'pc2';
}
function getOtherPc(pc) {
return (pc === pc1) ? pc2 : pc1;
}
async function call() {
pc1 = new RTCPeerConnection();
pc1.addEventListener('icecandidate', e => onIceCandidate(pc1, e));
pc2 = new RTCPeerConnection();
pc2.addEventListener('icecandidate', e => onIceCandidate(pc2, e));
const offer = await pc1.createOffer(offerOptions);
await pc1.setLocalDescription(offer);
await pc2.setRemoteDescription(offer);
const answer = await pc2.createAnswer();
await pc2.setLocalDescription(answer);
await pc1.setRemoteDescription(answer);
}
async function onIceCandidate(pc, event) {
if (event.candidate) {
await getOtherPc(pc).addIceCandidate(event.candidate);
console.log(`${getName(pc)} addIceCandidate success`);
}
}