10-4. WebRTC 基礎
深入理解點對點即時通訊、NAT 穿透與視訊通話原理
📹 WebRTC 基礎
🎯 什麼是 WebRTC?
💡 比喻:直接打電話 vs 透過總機
傳統方式(Client-Server):
Alice → Server → Bob
就像打電話要透過總機轉接,會有延遲
WebRTC(Peer-to-Peer):
Alice ←→ Bob(直連)
就像直接撥對方號碼,速度更快WebRTC(Web Real-Time Communication) 是一個開放框架,讓瀏覽器和行動應用可以進行即時的語音、視訊和資料傳輸,而且是點對點(P2P) 連線,不需要透過伺服器中轉。
為什麼需要 WebRTC?
問題場景:視訊通話
❌ 傳統方式(經由伺服器):
Alice's 攝影機 → Server → Bob's 螢幕
問題:
1. 延遲高(需經過伺服器)
2. 伺服器頻寬成本高(所有影片都要中轉)
3. 伺服器成為瓶頸(同時 100 人通話 = 100 倍流量)✅ WebRTC(點對點):
Alice's 攝影機 ←→ Bob's 螢幕(直連)
優點:
1. 延遲極低(直接連線)
2. 伺服器只需處理訊號(不傳輸影音)
3. 可擴展性高(P2P 分散負載)🏗️ WebRTC 架構
核心組件
┌──────────────────────────────────────┐
│ WebRTC 完整流程 │
└──────────────────────────────────────┘
1. Signaling(訊號交換)
Alice ←→ Signaling Server ←→ Bob
交換 SDP(會話描述)和 ICE Candidates(網路路徑)
2. NAT Traversal(NAT 穿透)
使用 STUN/TURN 找到彼此的真實 IP
3. Peer Connection(點對點連線)
Alice ←→ Bob(直連!)
傳輸音訊、視訊、資料三大 API
- getUserMedia:取得攝影機/麥克風
- RTCPeerConnection:建立 P2P 連線
- RTCDataChannel:傳輸任意資料(非音視訊)
📹 getUserMedia(取得媒體)
💡 比喻:借用攝影機
向瀏覽器請求使用攝影機和麥克風的權限基本用法:
// 取得視訊和音訊
const stream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true
});
// 顯示在 <video> 元素
const videoElement = document.querySelector('#localVideo');
videoElement.srcObject = stream;進階設定:
// 指定解析度和幀率
const constraints = {
video: {
width: { ideal: 1920 },
height: { ideal: 1080 },
frameRate: { ideal: 30 }
},
audio: {
echoCancellation: true, // 回音消除
noiseSuppression: true, // 降噪
autoGainControl: true // 自動增益
}
};
const stream = await navigator.mediaDevices.getUserMedia(constraints);螢幕分享:
// 取得螢幕畫面(用於螢幕分享)
const screenStream = await navigator.mediaDevices.getDisplayMedia({
video: {
cursor: "always" // 顯示滑鼠游標
},
audio: true // 包含系統音訊
});錯誤處理:
try {
const stream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true
});
console.log('取得媒體成功');
return stream;
} catch (error) {
if (error.name === 'NotAllowedError') {
console.error('使用者拒絕權限');
} else if (error.name === 'NotFoundError') {
console.error('找不到攝影機/麥克風');
} else {
console.error('取得媒體失敗:', error);
}
}🔗 RTCPeerConnection(P2P 連線)
💡 比喻:建立電話線路
需要先「撥號」(交換網路資訊)才能通話完整連線流程
Alice Bob
│ │
├─ 1. createOffer() ──────────>│
│ (SDP Offer) │
│ │
│<──────── 2. createAnswer() ──┤
│ (SDP Answer) │
│ │
├─ 3. ICE Candidates ─────────>│
│<─────── ICE Candidates ───────┤
│ │
│<═══════ 4. P2P Connection ══>│
│ (直連成功!) │實作範例
Alice(發起方):
// 建立 RTCPeerConnection
const config = {
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' } // Google 的 STUN 伺服器
]
};
const peerConnection = new RTCPeerConnection(config);
// 1. 加入本地媒體流
const localStream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true
});
localStream.getTracks().forEach(track => {
peerConnection.addTrack(track, localStream);
});
// 2. 監聽 ICE Candidate
peerConnection.onicecandidate = (event) => {
if (event.candidate) {
// 透過 Signaling Server 發送給 Bob
sendToSignalingServer({
type: 'ice-candidate',
candidate: event.candidate
});
}
};
// 3. 監聽遠端媒體流
peerConnection.ontrack = (event) => {
const remoteVideo = document.querySelector('#remoteVideo');
remoteVideo.srcObject = event.streams[0];
};
// 4. 建立 Offer
const offer = await peerConnection.createOffer();
await peerConnection.setLocalDescription(offer);
// 5. 發送 Offer 給 Bob
sendToSignalingServer({
type: 'offer',
sdp: offer
});Bob(接收方):
// 建立 RTCPeerConnection(配置相同)
const peerConnection = new RTCPeerConnection(config);
// 加入本地媒體流
const localStream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true
});
localStream.getTracks().forEach(track => {
peerConnection.addTrack(track, localStream);
});
// 監聽 ICE Candidate
peerConnection.onicecandidate = (event) => {
if (event.candidate) {
sendToSignalingServer({
type: 'ice-candidate',
candidate: event.candidate
});
}
};
// 監聽遠端媒體流
peerConnection.ontrack = (event) => {
const remoteVideo = document.querySelector('#remoteVideo');
remoteVideo.srcObject = event.streams[0];
};
// 收到 Alice 的 Offer
signalingServer.on('offer', async (offer) => {
await peerConnection.setRemoteDescription(offer);
// 建立 Answer
const answer = await peerConnection.createAnswer();
await peerConnection.setLocalDescription(answer);
// 發送 Answer 給 Alice
sendToSignalingServer({
type: 'answer',
sdp: answer
});
});
// 收到 ICE Candidate
signalingServer.on('ice-candidate', async (candidate) => {
await peerConnection.addIceCandidate(candidate);
});🌐 Signaling(訊號交換)
💡 比喻:紅娘
負責撮合雙方,但不參與之後的通訊
Alice 和 Bob 透過紅娘交換電話號碼,之後就直接聯繫WebRTC 本身不定義 Signaling 機制,你可以使用任何方式:
- WebSocket
- Socket.IO
- HTTP Long Polling
- XMPP
- SIP
使用 Socket.IO 實作 Signaling Server
伺服器端(Node.js):
const express = require('express');
const http = require('http');
const socketIO = require('socket.io');
const app = express();
const server = http.createServer(app);
const io = socketIO(server);
// 儲存房間資訊
const rooms = new Map();
io.on('connection', (socket) => {
console.log('使用者連線:', socket.id);
// 加入房間
socket.on('join-room', (roomId) => {
socket.join(roomId);
// 通知房間內的其他人
socket.to(roomId).emit('user-joined', socket.id);
console.log(`${socket.id} 加入房間 ${roomId}`);
});
// 轉發 Offer
socket.on('offer', (data) => {
socket.to(data.to).emit('offer', {
from: socket.id,
sdp: data.sdp
});
});
// 轉發 Answer
socket.on('answer', (data) => {
socket.to(data.to).emit('answer', {
from: socket.id,
sdp: data.sdp
});
});
// 轉發 ICE Candidate
socket.on('ice-candidate', (data) => {
socket.to(data.to).emit('ice-candidate', {
from: socket.id,
candidate: data.candidate
});
});
// 離開房間
socket.on('disconnect', () => {
console.log('使用者離線:', socket.id);
socket.broadcast.emit('user-left', socket.id);
});
});
server.listen(3000, () => {
console.log('Signaling Server 啟動於 port 3000');
});客戶端:
const socket = io('http://localhost:3000');
const roomId = 'room123';
// 加入房間
socket.emit('join-room', roomId);
// 有人加入房間
socket.on('user-joined', async (userId) => {
console.log('新使用者加入:', userId);
// 建立 Offer
const offer = await peerConnection.createOffer();
await peerConnection.setLocalDescription(offer);
// 發送 Offer
socket.emit('offer', {
to: userId,
sdp: offer
});
});
// 收到 Offer
socket.on('offer', async (data) => {
await peerConnection.setRemoteDescription(data.sdp);
// 建立 Answer
const answer = await peerConnection.createAnswer();
await peerConnection.setLocalDescription(answer);
// 發送 Answer
socket.emit('answer', {
to: data.from,
sdp: answer
});
});
// 收到 Answer
socket.on('answer', async (data) => {
await peerConnection.setRemoteDescription(data.sdp);
});
// 收到 ICE Candidate
socket.on('ice-candidate', async (data) => {
await peerConnection.addIceCandidate(data.candidate);
});🧭 NAT 穿透(STUN/TURN)
💡 比喻:找到對方的真實地址
你的電腦在公司內網(192.168.1.100)
Bob 的電腦在家裡內網(192.168.0.50)
雙方都不知道對方的「真實門牌號碼」(公網 IP)
STUN:詢問郵局「我的真實地址是什麼?」
TURN:如果直連失敗,郵局幫忙轉信NAT 問題
Alice 的內網:192.168.1.100
↓
NAT 路由器
↓
公網 IP:203.0.113.5
↓
Internet
↓
公網 IP:198.51.100.8
↓
NAT 路由器
↓
Bob 的內網:192.168.0.50Alice 和 Bob 都不知道對方的公網 IP,無法直接連線。
STUN(Session Traversal Utilities for NAT)
💡 功能:查詢自己的公網 IP 和 PortAlice → STUN Server:我的公網 IP 是什麼?
STUN Server → Alice:你的公網 IP 是 203.0.113.5:54321
Alice 把這個資訊告訴 Bob
Bob 就可以連線到 203.0.113.5:54321配置 STUN:
const config = {
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{ urls: 'stun:stun1.l.google.com:19302' }
]
};
const peerConnection = new RTCPeerConnection(config);TURN(Traversal Using Relays around NAT)
💡 功能:當 P2P 失敗時,透過中繼伺服器轉發某些嚴格的 NAT(如對稱型 NAT)無法穿透,這時需要 TURN 中繼。
Alice ──> TURN Server ──> Bob
優點:保證連線成功
缺點:佔用伺服器頻寬(與傳統方式類似)配置 TURN:
const config = {
iceServers: [
{ urls: 'stun:stun.l.google.com:19302' },
{
urls: 'turn:turn.example.com:3478',
username: 'user',
credential: 'password'
}
]
};
const peerConnection = new RTCPeerConnection(config);自建 TURN 伺服器(coturn):
# 安裝 coturn
sudo apt-get install coturn
# 設定 /etc/turnserver.conf
listening-port=3478
external-ip=YOUR_PUBLIC_IP
realm=example.com
user=username:password
# 啟動
sudo systemctl start coturn🧊 ICE(Interactive Connectivity Establishment)
💡 比喻:嘗試所有可能的路徑
就像導航 App 會找出多條路線,選最快的那條ICE 會嘗試多種連線方式,按優先順序排列:
- Host Candidate:本地 IP(內網直連)
- Server Reflexive:STUN 查到的公網 IP
- Relay Candidate:TURN 中繼
peerConnection.onicecandidate = (event) => {
if (event.candidate) {
console.log('找到 ICE Candidate:', event.candidate);
// 候選者類型
const type = event.candidate.type;
// "host" | "srflx" (Server Reflexive) | "relay"
// 發送給對方
sendToSignalingServer({
type: 'ice-candidate',
candidate: event.candidate
});
} else {
console.log('ICE 收集完成');
}
};📊 SDP(Session Description Protocol)
💡 比喻:通話規格書
就像買手機要看規格(支援 5G?記憶體多大?)
SDP 描述這次連線支援哪些音視訊編碼、解析度等SDP 範例:
v=0
o=- 1234567890 1234567890 IN IP4 127.0.0.1
s=-
t=0 0
m=audio 9 UDP/TLS/RTP/SAVPF 111 103 104
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=ice-ufrag:abcd
a=ice-pwd:1234567890abcdefghij
a=rtpmap:111 opus/48000/2
a=fmtp:111 minptime=10;useinbandfec=1
m=video 9 UDP/TLS/RTP/SAVPF 96 97 98
c=IN IP4 0.0.0.0
a=rtcp:9 IN IP4 0.0.0.0
a=rtpmap:96 VP8/90000
a=rtpmap:97 H264/90000解析:
m=audio:音訊支援 Opus 編碼m=video:視訊支援 VP8、H264 編碼a=ice-ufrag/pwd:ICE 認證資訊
檢視 SDP:
const offer = await peerConnection.createOffer();
console.log('Offer SDP:', offer.sdp);
await peerConnection.setLocalDescription(offer);💾 RTCDataChannel(資料通道)
💡 比喻:除了語音通話,還能傳簡訊
除了音視訊,還能傳任意資料(文字、檔案、遊戲狀態)建立 Data Channel:
// Alice 建立 Data Channel
const dataChannel = peerConnection.createDataChannel('chat');
dataChannel.onopen = () => {
console.log('Data Channel 已開啟');
dataChannel.send('Hello, Bob!');
};
dataChannel.onmessage = (event) => {
console.log('收到訊息:', event.data);
};
dataChannel.onerror = (error) => {
console.error('Data Channel 錯誤:', error);
};Bob 接收 Data Channel:
peerConnection.ondatachannel = (event) => {
const dataChannel = event.channel;
dataChannel.onopen = () => {
console.log('Data Channel 已開啟');
};
dataChannel.onmessage = (event) => {
console.log('收到訊息:', event.data);
// 回覆
dataChannel.send('Hi, Alice!');
};
};傳輸檔案:
// 讀取檔案
const fileInput = document.querySelector('#fileInput');
fileInput.addEventListener('change', async (event) => {
const file = event.target.files[0];
// 分塊傳輸(避免超過 buffer 大小)
const chunkSize = 16384; // 16 KB
const fileReader = new FileReader();
let offset = 0;
fileReader.onload = (e) => {
dataChannel.send(e.target.result);
offset += chunkSize;
if (offset < file.size) {
readSlice(offset);
} else {
console.log('檔案傳輸完成');
}
};
const readSlice = (offset) => {
const slice = file.slice(offset, offset + chunkSize);
fileReader.readAsArrayBuffer(slice);
};
readSlice(0);
});應用場景:
- 💬 文字聊天(不需要伺服器中轉)
- 📁 檔案分享(P2P 傳輸)
- 🎮 遊戲同步(低延遲)
- 📊 協作編輯(共享狀態)
🎓 常見面試題
Q1:WebRTC 的連線流程是什麼?
答案:
完整流程(7 步驟):
1. Alice 取得本地媒體(getUserMedia)
2. Alice 建立 RTCPeerConnection
3. Alice 建立 Offer(createOffer)→ 發送給 Bob
4. Bob 收到 Offer,建立 Answer(createAnswer)→ 發送給 Alice
5. 雙方交換 ICE Candidates(透過 Signaling Server)
6. ICE 建立 P2P 連線(嘗試 Host → STUN → TURN)
7. 連線成功,開始傳輸音視訊!記憶口訣:「媒建議答冰連傳」
- 媒(媒體)
- 建(建立連線)
- 議(Offer)
- 答(Answer)
- 冰(ICE)
- 連(P2P 連線)
- 傳(傳輸)
程式碼濃縮版:
// 1-2. 取得媒體 + 建立連線
const stream = await getUserMedia({video: true, audio: true});
const pc = new RTCPeerConnection(config);
stream.getTracks().forEach(t => pc.addTrack(t, stream));
// 3-4. Offer/Answer
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
sendToSignalingServer(offer); // → Bob
// Bob:
await pc.setRemoteDescription(offer);
const answer = await pc.createAnswer();
await pc.setLocalDescription(answer);
sendToSignalingServer(answer); // → Alice
// 5-6. ICE
pc.onicecandidate = e => sendToSignalingServer(e.candidate);
// 7. 傳輸
pc.ontrack = e => remoteVideo.srcObject = e.streams[0];Q2:STUN 和 TURN 的差異?
答案:
比喻:
STUN = 問路人「我家地址是什麼?」(查詢服務)
TURN = 請快遞幫忙轉交(中繼服務)| 特性 | STUN | TURN |
|---|---|---|
| 功能 | 查詢公網 IP/Port | 中繼轉發流量 |
| 流量 | 不經過 STUN(只查詢) | 所有流量經過 TURN |
| 成本 | 低(幾乎不用頻寬) | 高(需要大頻寬) |
| 成功率 | ~80-90% | 100% |
| 延遲 | 低 | 較高(多一跳) |
何時使用:
// ICE 自動選擇最佳路徑:
1. 嘗試直連(Host)
↓ 失敗
2. 嘗試 STUN(Server Reflexive)
↓ 失敗
3. 使用 TURN(Relay)配置建議:
const config = {
iceServers: [
// 免費 STUN(Google 提供)
{ urls: 'stun:stun.l.google.com:19302' },
// 自建 TURN(需要認證,避免濫用)
{
urls: 'turn:turn.myserver.com:3478',
username: 'user',
credential: 'pass',
credentialType: 'password'
}
],
// ICE 策略
iceTransportPolicy: 'all' // 'all' | 'relay'(強制使用 TURN)
};成本估算:
STUN 伺服器:
- 流量:幾乎為 0
- 費用:免費(使用 Google STUN)
TURN 伺服器:
- 流量:與視訊通話相當(每小時約 500 MB - 1 GB)
- 費用:需要自建或租用
- 使用率:約 10-20% 的連線需要 TURNQ3:如何優化 WebRTC 的視訊品質?
答案:
多種優化策略:
1. 自適應位元率(Adaptive Bitrate):
const sender = peerConnection.getSenders().find(s => s.track.kind === 'video');
const parameters = sender.getParameters();
// 設定最大位元率(1 Mbps)
if (!parameters.encodings) {
parameters.encodings = [{}];
}
parameters.encodings[0].maxBitrate = 1000000; // 1 Mbps
await sender.setParameters(parameters);2. 根據網路狀況調整解析度:
peerConnection.getStats().then(stats => {
stats.forEach(report => {
if (report.type === 'inbound-rtp' && report.kind === 'video') {
const packetsLost = report.packetsLost;
const packetsReceived = report.packetsReceived;
const lossRate = packetsLost / (packetsLost + packetsReceived);
if (lossRate > 0.1) { // 丟包率 > 10%
// 降低解析度
adjustVideoQuality('low');
}
}
});
});
function adjustVideoQuality(quality) {
const constraints = {
low: { width: 640, height: 360, frameRate: 15 },
medium: { width: 1280, height: 720, frameRate: 24 },
high: { width: 1920, height: 1080, frameRate: 30 }
};
const videoTrack = localStream.getVideoTracks()[0];
videoTrack.applyConstraints(constraints[quality]);
}3. 使用 Simulcast(同時多碼率):
// 發送者同時傳輸多種解析度,接收者選擇適合的
const sender = peerConnection.addTransceiver('video', {
direction: 'sendonly',
sendEncodings: [
{ rid: 'h', maxBitrate: 900000 }, // High
{ rid: 'm', maxBitrate: 300000, scaleResolutionDownBy: 2 }, // Medium
{ rid: 'l', maxBitrate: 100000, scaleResolutionDownBy: 4 } // Low
]
});4. 選擇合適的編碼器:
// 檢查支援的編碼器
const capabilities = RTCRtpSender.getCapabilities('video');
console.log('支援的編碼器:', capabilities.codecs);
// 優先使用 VP9(比 VP8 更高效)
const preferredCodec = capabilities.codecs.find(codec =>
codec.mimeType === 'video/VP9'
);
if (preferredCodec) {
const transceivers = peerConnection.getTransceivers();
transceivers.forEach(transceiver => {
if (transceiver.sender.track?.kind === 'video') {
const params = transceiver.sender.getParameters();
params.codecs = [preferredCodec];
transceiver.sender.setParameters(params);
}
});
}Q4:WebRTC 如何處理多人通話?
答案:
有兩種架構:
1. Mesh(網狀)- 每個人連線到所有人:
3 人通話:
Alice ←→ Bob
↓ ╲ ↗
↓ Charlie
↓ ↗
↓↗
每個人需要 (n-1) 個連線
3 人 = 2 個連線
10 人 = 9 個連線(頻寬吃不消!)優點:
- 延遲低(P2P)
- 不需要伺服器
缺點:
- 頻寬需求高(上傳 n-1 份影片)
- 只適合 2-4 人
實作:
// 維護多個 PeerConnection
const peerConnections = new Map();
function createPeerConnection(userId) {
const pc = new RTCPeerConnection(config);
// 加入本地媒體
localStream.getTracks().forEach(track => {
pc.addTrack(track, localStream);
});
// 監聽遠端媒體
pc.ontrack = (event) => {
displayRemoteVideo(userId, event.streams[0]);
};
peerConnections.set(userId, pc);
return pc;
}
// 有新使用者加入
signalingServer.on('user-joined', (userId) => {
const pc = createPeerConnection(userId);
// 建立 Offer...
});2. SFU(Selective Forwarding Unit)- 伺服器轉發:
10 人通話:
Alice ──┐
Bob ────┼──> SFU Server
Charlie─┤ (只轉發,不編碼)
... │
10th ───┘
每個人只需 1 個上傳連線(傳給 SFU)
SFU 轉發給其他 9 人優點:
- 頻寬需求低(只上傳 1 份)
- 支援大規模通話(100+ 人)
缺點:
- 需要 SFU 伺服器(mediasoup、Janus、Kurento)
使用 mediasoup(SFU):
// 客戶端
import { Device } from 'mediasoup-client';
const device = new Device();
// 載入伺服器的 RTP capabilities
await device.load({ routerRtpCapabilities });
// 建立 Producer(發送)
const transport = await device.createSendTransport(transportOptions);
const producer = await transport.produce({
track: localStream.getVideoTracks()[0]
});
// 建立 Consumer(接收)
const consumer = await device.createRecvTransport(transportOptions).consume({
id: consumerId,
producerId: producerId,
kind: 'video',
rtpParameters: rtpParameters
});3. MCU(Multipoint Control Unit)- 伺服器混流:
SFU 轉發 9 個影片(客戶端需解碼 9 次)
MCU 混合成 1 個影片(客戶端只解碼 1 次)
Alice ──┐
Bob ────┼──> MCU Server
Charlie─┘ (混合成一個畫面)
↓
[九宮格合成影片]
↓
傳給所有人(1 個流)優點:
- 客戶端負擔最低(只收 1 個流)
缺點:
- 伺服器運算量高(需要編碼/解碼)
- 彈性較低(無法自由調整布局)
Q5:WebRTC 的安全性如何?
答案:
WebRTC 內建強制加密,非常安全!
1. DTLS(Datagram TLS):
WebRTC 強制使用 DTLS 加密所有資料
就像 HTTPS,但用於 UDP2. SRTP(Secure RTP):
音視訊流使用 SRTP 加密
每個封包都加密,無法竊聽3. 瀏覽器安全限制:
// 只能在 HTTPS 頁面使用 getUserMedia
// HTTP 會被拒絕(Localhost 除外)
if (location.protocol !== 'https:') {
console.error('WebRTC 需要 HTTPS');
}4. 權限控制:
// 使用者必須明確允許存取攝影機/麥克風
// 瀏覽器會顯示權限提示
const stream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true
});
// 彈出權限提示:「example.com 想要使用您的攝影機和麥克風」
潛在風險:
⚠️ Signaling Server 可能被竊聽
解決:使用 WSS(WebSocket Secure)
⚠️ TURN 伺服器可能記錄資料
解決:使用可信任的 TURN 伺服器
⚠️ 惡意網站可能竊取媒體
解決:只在可信任的網站允許權限📝 總結
WebRTC 是現代即時通訊的基石:
- P2P:點對點連線,延遲極低 ⚡
- 無插件:瀏覽器原生支援 🌐
- 加密:強制 DTLS/SRTP 加密 🔐
- 免費:開放標準,無需授權 💰
記憶口訣:「點(P2P)、原(原生)、密(加密)、免(免費)」
適用場景:
- 📞 視訊通話(Zoom、Google Meet)
- 🎮 雲端遊戲(Google Stadia)
- 📡 直播(Twitch、YouTube Live)
- 🏥 遠端醫療
- 🎓 線上教育
完整堆疊:
應用層:getUserMedia, RTCPeerConnection, RTCDataChannel
訊號層:WebSocket, Socket.IO(自訂)
媒體層:SRTP(加密音視訊)
傳輸層:UDP(即時傳輸)
穿透層:ICE, STUN, TURN🔗 延伸閱讀
- 上一篇:05-3. XMPP 協定
- 下一章:06-1. SIP 協定基礎
- MDN 文件:https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API