简介
Trickle ICE(Interactive Connectivity Establishment)是WebRTC的一种流程,它允许WebRTC应用程序在建立对等连接时逐步收集和交换候选地址。
在Trickle ICE流程中,每个对等端都可以向对等端发送候选地址,而不必等待它们收集所有地址,这可以加快连接建立的过程。相比之下,非逐步收集地址的方法被称为完全收集ICE。
时序图
Offer
v=0
o=- 8532455066844539463 2 IN IP4 127.0.0.1
s=-
t=0 0
a=group:BUNDLE 0
a=extmap-allow-mixed
a=msid-semantic: WMS
m=application 9 UDP/DTLS/SCTP webrtc-datachannel
c=IN IP4 0.0.0.0
a=ice-ufrag:VBzV
a=ice-pwd:nTT/XKze1wW96yfd+BGhtMZ+
a=ice-options:trickle
a=fingerprint:sha-256 A6:2E:0A:C4:C8:18:EC:6F:7B:EB:44:E1:C8:FA:1C:DE:AB:30:40:16:E4:AF:28:2F:D2:D9:EF:DD:1C:38:3B:D1
a=setup:actpass
a=mid:0
a=sctp-port:5000
a=max-message-size:262144
Answer
v=0
o=- 4045597616857155182 2 IN IP4 127.0.0.1
s=-
t=0 0
a=group:BUNDLE 0
a=extmap-allow-mixed
a=msid-semantic: WMS
m=application 9 UDP/DTLS/SCTP webrtc-datachannel
c=IN IP4 0.0.0.0
a=ice-ufrag:UagN
a=ice-pwd:+vdRuG5iYxr9R6qkeC5XlvDf
a=ice-options:trickle
a=fingerprint:sha-256 8A:76:A2:2B:6D:CD:4C:78:83:C9:89:05:17:A9:2B:E4:7B:05:61:CD:40:82:F9:B3:45:AA:F0:84:EB:25:2D:A9
a=setup:active
a=mid:0
a=sctp-port:5000
a=max-message-size:262144
offer candidate
{
"candidate": "candidate:3173800074 1 udp 2113937151 1eb6cf6d-138c-4aa9-8278-17bb8ca982ab.local 63728 typ host generation 0 ufrag VBzV network-cost 999",
"sdpMLineIndex": 0,
"sdpMid": "0"
}
answer candidate
{
"candidate": "candidate:3335541002 1 udp 2113937151 fffaa1d8-ea20-49f7-a65b-bd9508d87268.local 50242 typ host generation 0 ufrag UagN network-cost 999",
"sdpMLineIndex": 0,
"sdpMid": "0"
}
伪代码
主动方
第一个阶段 创建Offer和搜集candidate
// 1. 创建peerconnecttion
const peerConnection = new RTCPeerConnection(config);
// 创建datachannel,必须要这个或者用media,否则后面不会搜集candidate
const sendChannel = peerConnection.createDataChannel('sendDataChannel',dcConfig);
// sendChannel.binaryType = 'arraybuffer'; // 可选
sendChannel.addEventListener('open', onSendChannelStateChange);
sendChannel.addEventListener('close', onSendChannelStateChange);
sendChannel.addEventListener('error', onError);
sendChannel.onmessage = onReceiveMessageCallback;
// 2. 创建offer获取des SDP
peerConnection .createOffer().then(
gotDes, // 获取描述
onCreateSessionDescriptionError // 异常处理
);
function gotDes(desc) {
// 3. 把获取到的des的sdp 发送给远端 类型type = 'offer'
websocket.send({
type: 'offer', sdp: offer.sdp})
// 4. 在获取DEC SDP后,开始搜集 candidate
peerConnection.setLocalDescription(desc);
}
// 4. 监听搜集的candidate
peerConnection.onicecandidate = e => {
// 搜集一条/全部 完成后通过信令服务器发送给远端。
if (event.candidate) {
console.log('iceCandidate', JSON.stringify(event.candidate));
websocket.send((JSON.stringify(
{
type: 'candidate',
candidate: {
candidate: event.candidate.candidate,
sdpMLineIndex: event.candidate.sdpMLineIndex,
sdpMid: event.candidate.sdpMid
}
}));
}
};
第二阶段 验证answerSDP和candidate
function handleAnswer(data) {
if (data.candidate) {
pc.addIceCandidate(data.candidate)
.then(() => {
console.log("addIceCandidate success")
})
.catch(err => {
console.log("addIceCandidate error", err)
})
}
if (data.sdp) {
pc.setRemoteDescription(data)
.then(() => {
console.log('setRemoteDescription success')
})
.catch(err => {
console.log('setRemoteDescription error')
})
}
}
被动方
处理OfferSDP和candidate
// 1.创建peerconnection
const peerConnection = new RTCPeerConnection(config);
// 监听datachannel事件
let receiveChannel ;
peerConnection.addEventListener('datachannel', receiveChannelCallback);
// 2.处理OfferSDP和candidate
function handleOffer(data) {
if (data.candidate) {
pc.addIceCandidate(data.candidate)
.then(() => {
console.log("addIceCandidate success")
})
.catch(err => {
console.log("addIceCandidate error", err)
})
}
if (data.sdp) {
pc.setRemoteDescription(data)
.then(() => {
// 3.创建answer
createAnswer()
})
.catch(err => {
console.log('createAnswer error', err)
})
}
}
// 4. 监听搜集的candidate
peerConnection.onicecandidate = e => {
// 搜集一条/全部 完成后通过信令服务器发送给远端。
if (event.candidate) {
console.log('iceCandidate', JSON.stringify(event.candidate));
websocket.send((JSON.stringify(
{
type: 'candidate',
candidate: {
candidate: event.candidate.candidate,
sdpMLineIndex: event.candidate.sdpMLineIndex,
sdpMid: event.candidate.sdpMid
}
}));
}
};
function createAnswer() {
console.log('createAnswer start')
pc.createAnswer(this.answerOptions)
.then(answer => {
console.log('answer sdp: ', JSON.stringify(answer))
const sendAnswer = () => {
html(JSON.stringify({
type: answer.type,
sdp: answer.sdp
}))
}
const onSuccess = () => {
console.log('createAnswer success')
sendAnswer();
}
const onError = err => {
console.log('createAnswer error', err)
}
pc.setLocalDescription(answer)
.then(onSuccess)
.catch(onError)
})
.catch(err => {
console.log('createAnswer error', err)
})
}
function receiveChannelCallback(event) {
receiveChannel = event.channel;
receiveChannel.binaryType = 'arraybuffer'; // 可选
receiveChannel.onmessage = onReceiveMessageCallback;
receiveChannel.onopen = onReceiveChannelStateChange;
receiveChannel.onclose = onReceiveChannelStateChange;
}