WebRTC 01:初步认识 WebRTC 与信令服务器
WebRTC 01:初步认识 WebRTC 与信令服务器
1. 是什么
- WebRTC 协议:两端安全实时通信的一组规则集合,涵盖协商、连通、加密与媒体/数据传输。
- WebRTC API:浏览器提供的 JavaScript 接口,驱动上述协议在终端侧运行。
核心对象与名词
- RTCPeerConnection:端侧连接对象。
- SDP:会话描述,文本格式,承载编解码与网络参数。
- Offer/Answer:协商模型,一端发起 Offer,对端返回 Answer。
- ICE:交互式连通建立,收集候选并选择最佳路径。
- STUN/TURN:获取公网可达地址与中继转发。
- DTLS/SRTP/SCTP:加密握手、媒体加密与数据通道承载。
- DataChannel:可靠/不可靠、有序/无序的数据传输。
2. 为什么
- 开放标准,多实现互通。
- 强制加密,默认安全。
- NAT 穿透,面向真实网络。
- 拥塞控制与自适应,追求亚秒级延迟。
3. 四步法总览
- 信令 Signaling:交换 SDP 与候选所需的元数据。发生在 WebRTC 之外。
- 连接 Connecting:通过 ICE 完成候选收敛与连通性检测。
- 加密 Securing:通过 DTLS 建立密钥,派生 SRTP/SCTP。
- 通信 Communicating:使用 SRTP 传媒体,SCTP 传数据。
示意图:信令与建链时序(Mermaid)
sequenceDiagram
participant A as Peer A
participant S as Signaling Server
participant B as Peer B
participant T as STUN/TURN
A->>S: join(room)
B->>S: join(room)
A->>S: offer (SDP)
S-->>B: offer
B->>S: answer (SDP)
S-->>A: answer
A->>S: ice-candidate
S-->>B: ice-candidate
B->>S: ice-candidate
S-->>A: ice-candidate
A->>T: STUN binding request
T-->>A: srflx candidate
B->>T: STUN binding request
T-->>B: srflx candidate
A-->>B: ICE connectivity checks
A-->>B: DTLS handshake
A-->>B: SRTP/SCTP secured traffic
4. 信令:不内置,需你自定义
定义
- 信令用于引导呼叫。它不是 WebRTC 自带能力,需用自选通道传递文本消息(如 WebSocket、HTTP)。
SDP 与 Offer/Answer
- SDP 是 RFC 8866 定义的文本格式,常见键:
v/o/s/t/c、m(媒体描述)、a(属性)。 - JSEP 指定了浏览器使用的 SDP 子集与流程。
- Offer/Answer 通过交换 SDP 在能力与参数上达成一致。
消息类型是开发者自定义
join/leave/offer/answer/ice-candidate/renegotiate/bye都是应用层自定义的消息名,用于携带 SDP 或候选及会话控制。
最小消息模型
{ "type": "join", "roomId": "r1", "from": "uA" }
{ "type": "offer", "roomId": "r1", "from": "uA", "to": "uB", "sdp": "..." }
{ "type": "answer", "roomId": "r1", "from": "uB", "to": "uA", "sdp": "..." }
{ "type": "ice-candidate", "roomId": "r1", "from": "uA", "to": "uB", "candidate": { "candidate": "candidate:...", "sdpMid": "0", "sdpMLineIndex": 0 } }
{ "type": "renegotiate", "roomId": "r1", "from": "uA", "to": "uB" }
{ "type": "bye", "roomId": "r1", "from": "uA", "to": "uB" }
{ "type": "leave", "roomId": "r1", "from": "uA" }
示意图:组件关系(Mermaid)
graph TD
A[Peer A] -- signaling --> S[Signaling]
B[Peer B] -- signaling --> S
A -- STUN/TURN --> T[(STUN/TURN)]
B -- STUN/TURN --> T
A == media/data ==> B
5. 连接:ICE 的关键点
- 候选类型:
host、srflx(STUN 反射)、relay(TURN 中继)。 - Trickle ICE:边收集边发送,缩短首帧时间。
- 角色:
controlling/controlled,涉及最终提名。 - 续连:
ICE restart在网络变化后重新收敛。
6. 加密与传输
- DTLS:在已建立的通道上握手并验证证书指纹(来自 SDP)。
- SRTP:从 DTLS 派生密钥,用于音视频加密传输。
- SCTP over DTLS:承载 DataChannel。
7. 最小浏览器侧示例
const ws = new WebSocket("wss://example.com/signal");
const pc = new RTCPeerConnection({
iceServers: [{ urls: "stun:stun.l.google.com:19302" }],
});
function send(m) {
if (ws.readyState === 1) ws.send(JSON.stringify(m));
}
ws.onopen = () => send({ type: "join", roomId: "r1", from: "uA" });
ws.onmessage = async (e) => {
const m = JSON.parse(e.data);
if (m.type === "answer" && m.to === "uA")
await pc.setRemoteDescription({ type: "answer", sdp: m.sdp });
if (m.type === "ice-candidate" && m.to === "uA")
await pc.addIceCandidate(m.candidate);
};
pc.onicecandidate = (e) => {
if (e.candidate)
send({
type: "ice-candidate",
roomId: "r1",
from: "uA",
to: "uB",
candidate: e.candidate,
});
};
(async () => {
const stream = await navigator.mediaDevices.getUserMedia({
audio: true,
video: true,
});
stream.getTracks().forEach((t) => pc.addTrack(t, stream));
const offer = await pc.createOffer();
await pc.setLocalDescription(offer);
send({ type: "offer", roomId: "r1", from: "uA", to: "uB", sdp: offer.sdp });
})();
8. 最小信令服务(Go + WebSocket)
package main
import (
"encoding/json"
"net/http"
"sync"
"github.com/gorilla/websocket"
)
type Message struct{ Type, RoomID, From, To, SDP string; Candidate json.RawMessage }
type Client struct{ id string; conn *websocket.Conn }
var rooms = struct{ sync.RWMutex; m map[string]map[*Client]struct{} }{ m: make(map[string]map[*Client]struct{}) }
func join(c *Client, room string){ rooms.Lock(); if rooms.m[room]==nil{ rooms.m[room]=make(map[*Client]struct{}) }; rooms.m[room][c]=struct{}{}; rooms.Unlock() }
func leave(c *Client, room string){ rooms.Lock(); if set:=rooms.m[room]; set!=nil{ delete(set,c); if len(set)==0{ delete(rooms.m,room) } }; rooms.Unlock() }
func route(room string, from *Client, raw []byte, to string){ rooms.RLock(); for cli := range rooms.m[room]{ if cli!=from && (to=="" || cli.id==to){ cli.conn.WriteMessage(websocket.TextMessage, raw) } }; rooms.RUnlock() }
var up = websocket.Upgrader{ CheckOrigin: func(r *http.Request) bool { return true } }
func ws(w http.ResponseWriter, r *http.Request){ id := r.URL.Query().Get("uid"); c, _ := up.Upgrade(w,r,nil); cli := &Client{id:id, conn:c}; defer c.Close()
for { _, raw, err := c.ReadMessage(); if err!=nil{ break }; var m Message; if json.Unmarshal(raw,&m)!=nil{ continue }
switch m.Type{ case "join": join(cli,m.RoomID); case "leave": leave(cli,m.RoomID); default: route(m.RoomID, cli, raw, m.To) }
}
}
func main(){ http.HandleFunc("/signal", ws); http.ListenAndServe(":8080", nil) }
9. 开发者操作清单
- 选择信令通道与消息模型,明确字段与路由。
- 配置
iceServers,优先 STUN,生产补 TURN(支持 TCP/TLS)。 - 启用 Trickle ICE,缩短建链。
- 使用
chrome://webrtc-internals与pc.getStats()观测端到端指标。
10. 常见排障
- 只有 host 候选:检查 STUN 可达性与代理设置。
- ICE failed:补 TURN,确认 3478/5349 策略,必要时
iceTransportPolicy: 'relay'验证。 - DTLS failed:校验证书指纹与系统时间。
- 无声画:设备权限、轨添加与渲染绑定。
术语索引
- SDP:RFC 8866 文本会话描述。
- JSEP:RFC 8829,浏览器如何使用 SDP。
- ICE:候选收集与连通性检测。
- STUN/TURN:地址反射与中继。
- DTLS/SRTP/SCTP:加密与承载层。