WebRTC 数据通道连接建立但消息不来
WebRTC Data Channel connection established but messages do not come
我在接收两个对等方之间的 WebRTC 数据通道消息时遇到问题。
下面是一个 MVP,其中两个对等点在同一个页面上,并且信令通道被替换为普通 JavaScript 对象交换。
我没有选择使用 trickle ICE,我知道不使用它的缺点,我认为它不会以任何方式干扰我下面的 MVP。
代码的行为完全符合预期,所有日志消息都通过了。唯一缺少的是用于数据通道消息处理的那些,就好像消息从未从任何一个点传到另一个点一样。这是尝试发送 Test 的事件处理程序。 send 方法调用失败并出现空引用错误,我无法追踪其根本原因。
我关注了Untangling the WebRTC Flow to get where I am with this MVP. You can see it live here so you don't have to run it yourself.
class Peer {
constructor(name) {
this.name = name;
}
offer() {
const peerConnection = new webkitRTCPeerConnection({ iceServers: [ { url: "stun:stun.l.google.com:19302" } ] });
peerConnection.onnegotiationneeded = event => console.debug(this.name, "onnegotiationneeded");
peerConnection.onsignalingstatechange = event => console.debug(this.name, "onsignalingstatechange", peerConnection.signalingState);
peerConnection.onicegatheringstatechange = event => console.debug(this.name, "onicegatheringstatechange", peerConnection.iceGatheringState);
peerConnection.oniceconnectionstatechange = event => console.debug(this.name, "oniceconnectionstatechange", peerConnection.iceConnectionState);
peerConnection.onconnectionstatechange = event => console.debug(this.name, "onconnectionstatechange", peerConnection.connectionState);
peerConnection.ondatachannel = event => {
const dataChannel = event.channel;
dataChannel.onopen = event => {
console.debug(this.name, "onopen");
dataChannel.send("TEST");
};
dataChannel.onclose = event => console.debug(this.name, "onclose");
dataChannel.onerror = event => console.debug(this.name, "onerror");
dataChannel.onmessage = event => console.debug(this.name, "onmessage");
console.debug(this.name, "ondatachannelO");
this.dataChannel = dataChannel;
};
return new Promise((resolve, reject) => {
peerConnection.onicecandidate = event => {
if (!event.candidate) {
peerConnection.createOffer()
.then(offer => {
console.debug(this.name, "created an offer with candidates.");
this.peerConnection = peerConnection;
resolve(peerConnection.localDescription);
})
.catch(reject);
}
};
peerConnection.createDataChannel("datachannel");
peerConnection.createOffer()
.then(offer => {
console.debug(this.name, "created an offer without candidates.");
peerConnection.setLocalDescription(offer)
.then(() => {
console.debug(this.name, "set local description. Collecting candidates…");
})
.catch(reject);
})
.catch(reject);
});
}
answer(offer) {
const peerConnection = new webkitRTCPeerConnection({ iceServers: [ { url: "stun:stun.l.google.com:19302" } ] });
peerConnection.onnegotiationneeded = event => console.debug(this.name, "onnegotiationneeded");
peerConnection.onsignalingstatechange = event => console.debug(this.name, "onsignalingstatechange", peerConnection.signalingState);
peerConnection.onicegatheringstatechange = event => console.debug(this.name, "onicegatheringstatechange", peerConnection.iceGatheringState);
peerConnection.oniceconnectionstatechange = event => console.debug(this.name, "oniceconnectionstatechange", peerConnection.iceConnectionState);
peerConnection.onconnectionstatechange = event => console.debug(this.name, "onconnectionstatechange", peerConnection.connectionState);
peerConnection.ondatachannel = event => {
const dataChannel = event.channel;
dataChannel.onopen = event => {
console.debug(this.name, "onopen");
dataChannel.send("TEST");
};
dataChannel.onclose = event => console.debug(this.name, "onclose");
dataChannel.onerror = event => console.debug(this.name, "onerror");
dataChannel.onmessage = event => console.debug(this.name, "onmessage");
console.debug(this.name, "ondatachannelA");
this.dataChannel = dataChannel;
};
return new Promise((resolve, reject) => {
peerConnection.onicecandidate = event => {
if (!event.candidate) {
peerConnection.createAnswer()
.then(answer => {
console.debug(this.name, "created an answer with candidates.");
resolve(peerConnection.localDescription);
})
.catch(reject);
}
};
peerConnection.setRemoteDescription(offer)
.then(() => {
console.debug(this.name, "set remote description.");
peerConnection.createAnswer()
.then(answer => {
console.debug(this.name, "created an answer without candidates.");
peerConnection.setLocalDescription(answer)
.then(() => {
console.debug(this.name, "set local description.");
})
.catch(reject);
})
.catch(reject);
})
.catch(reject);
});
}
sealTheDeal(proffer) {
return new Promise((resolve, reject) => {
this.peerConnection.setRemoteDescription(proffer)
.then(() => {
console.debug(this.name, "set remote description.");
resolve();
})
.catch(console.e);
});
}
send() {
this.dataChannel.send("TEST");
}
}
function flow() {
const peerA = new Peer("Alice");
const peerB = new Peer("Bob");
peerA.offer()
.then(offer => {
console.debug("Signal transfering offer from Alice to Bob.");
peerB.answer(offer)
.then(proffer => {
console.debug("Signal transfering proffer from Bob to Alice.");
peerA.sealTheDeal(proffer)
.then(() => {
peerB.offer()
.then(offer => {
console.debug("Signal transfering offer from Bob to Alice.");
peerA.answer(offer)
.then(proffer => {
console.debug("Signal transfering proffer from Alice to Bob.");
peerB.sealTheDeal(proffer)
.then(() => {
console.debug("HYPE");
peerA.send("From Alice to Bob.");
peerB.send("From Bob to Alice.");
})
.catch(console.error);
})
.catch(console.error);
})
.catch(console.error);
})
.catch(console.error);
})
.catch(console.error);
})
.catch(console.error);
window.peerA = peerA;
window.peerB = peerB;
}
flow();
peerConnection.ondatachannel
仅在应答者上触发。
offerer获取数据通道如下:
this.dataChannel = peerConnection.createDataChannel("datachannel");
其他评论:
webkitRTCPeerConnection
仅适用于 Chrome/Opera。应该填充还是使用 adapter.js.
- 厄运金字塔不需要承诺。
- 避免 promise constructor anti-pattern.
示例:
var pc1 = new RTCPeerConnection(), pc2 = new RTCPeerConnection();
pc1.onicecandidate = e => pc2.addIceCandidate(e.candidate);
pc2.onicecandidate = e => pc1.addIceCandidate(e.candidate);
pc1.oniceconnectionstatechange = e => console.log(pc1.iceConnectionState);
pc1.onnegotiationneeded = e =>
pc1.createOffer().then(d => pc1.setLocalDescription(d))
.then(() => pc2.setRemoteDescription(pc1.localDescription))
.then(() => pc2.createAnswer()).then(d => pc2.setLocalDescription(d))
.then(() => pc1.setRemoteDescription(pc2.localDescription))
.catch(e => console.log(e));
var dc1, dc2;
pc2.ondatachannel = e => {
dc2 = e.channel;
dc2.onopen = () => console.log("Chat!");
dc2.onmessage = e => console.log("> " + e.data);
};
dc1 = pc1.createDataChannel("chat");
dc1.onopen = () => (chat.disabled = false, chat.select());
chat.onkeypress = e => {
if (e.keyCode != 13) return;
dc1.send(chat.value);
chat.value = "";
};
Chat: <input id="chat" disabled></input>
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
我在接收两个对等方之间的 WebRTC 数据通道消息时遇到问题。
下面是一个 MVP,其中两个对等点在同一个页面上,并且信令通道被替换为普通 JavaScript 对象交换。
我没有选择使用 trickle ICE,我知道不使用它的缺点,我认为它不会以任何方式干扰我下面的 MVP。
代码的行为完全符合预期,所有日志消息都通过了。唯一缺少的是用于数据通道消息处理的那些,就好像消息从未从任何一个点传到另一个点一样。这是尝试发送 Test 的事件处理程序。 send 方法调用失败并出现空引用错误,我无法追踪其根本原因。
我关注了Untangling the WebRTC Flow to get where I am with this MVP. You can see it live here so you don't have to run it yourself.
class Peer {
constructor(name) {
this.name = name;
}
offer() {
const peerConnection = new webkitRTCPeerConnection({ iceServers: [ { url: "stun:stun.l.google.com:19302" } ] });
peerConnection.onnegotiationneeded = event => console.debug(this.name, "onnegotiationneeded");
peerConnection.onsignalingstatechange = event => console.debug(this.name, "onsignalingstatechange", peerConnection.signalingState);
peerConnection.onicegatheringstatechange = event => console.debug(this.name, "onicegatheringstatechange", peerConnection.iceGatheringState);
peerConnection.oniceconnectionstatechange = event => console.debug(this.name, "oniceconnectionstatechange", peerConnection.iceConnectionState);
peerConnection.onconnectionstatechange = event => console.debug(this.name, "onconnectionstatechange", peerConnection.connectionState);
peerConnection.ondatachannel = event => {
const dataChannel = event.channel;
dataChannel.onopen = event => {
console.debug(this.name, "onopen");
dataChannel.send("TEST");
};
dataChannel.onclose = event => console.debug(this.name, "onclose");
dataChannel.onerror = event => console.debug(this.name, "onerror");
dataChannel.onmessage = event => console.debug(this.name, "onmessage");
console.debug(this.name, "ondatachannelO");
this.dataChannel = dataChannel;
};
return new Promise((resolve, reject) => {
peerConnection.onicecandidate = event => {
if (!event.candidate) {
peerConnection.createOffer()
.then(offer => {
console.debug(this.name, "created an offer with candidates.");
this.peerConnection = peerConnection;
resolve(peerConnection.localDescription);
})
.catch(reject);
}
};
peerConnection.createDataChannel("datachannel");
peerConnection.createOffer()
.then(offer => {
console.debug(this.name, "created an offer without candidates.");
peerConnection.setLocalDescription(offer)
.then(() => {
console.debug(this.name, "set local description. Collecting candidates…");
})
.catch(reject);
})
.catch(reject);
});
}
answer(offer) {
const peerConnection = new webkitRTCPeerConnection({ iceServers: [ { url: "stun:stun.l.google.com:19302" } ] });
peerConnection.onnegotiationneeded = event => console.debug(this.name, "onnegotiationneeded");
peerConnection.onsignalingstatechange = event => console.debug(this.name, "onsignalingstatechange", peerConnection.signalingState);
peerConnection.onicegatheringstatechange = event => console.debug(this.name, "onicegatheringstatechange", peerConnection.iceGatheringState);
peerConnection.oniceconnectionstatechange = event => console.debug(this.name, "oniceconnectionstatechange", peerConnection.iceConnectionState);
peerConnection.onconnectionstatechange = event => console.debug(this.name, "onconnectionstatechange", peerConnection.connectionState);
peerConnection.ondatachannel = event => {
const dataChannel = event.channel;
dataChannel.onopen = event => {
console.debug(this.name, "onopen");
dataChannel.send("TEST");
};
dataChannel.onclose = event => console.debug(this.name, "onclose");
dataChannel.onerror = event => console.debug(this.name, "onerror");
dataChannel.onmessage = event => console.debug(this.name, "onmessage");
console.debug(this.name, "ondatachannelA");
this.dataChannel = dataChannel;
};
return new Promise((resolve, reject) => {
peerConnection.onicecandidate = event => {
if (!event.candidate) {
peerConnection.createAnswer()
.then(answer => {
console.debug(this.name, "created an answer with candidates.");
resolve(peerConnection.localDescription);
})
.catch(reject);
}
};
peerConnection.setRemoteDescription(offer)
.then(() => {
console.debug(this.name, "set remote description.");
peerConnection.createAnswer()
.then(answer => {
console.debug(this.name, "created an answer without candidates.");
peerConnection.setLocalDescription(answer)
.then(() => {
console.debug(this.name, "set local description.");
})
.catch(reject);
})
.catch(reject);
})
.catch(reject);
});
}
sealTheDeal(proffer) {
return new Promise((resolve, reject) => {
this.peerConnection.setRemoteDescription(proffer)
.then(() => {
console.debug(this.name, "set remote description.");
resolve();
})
.catch(console.e);
});
}
send() {
this.dataChannel.send("TEST");
}
}
function flow() {
const peerA = new Peer("Alice");
const peerB = new Peer("Bob");
peerA.offer()
.then(offer => {
console.debug("Signal transfering offer from Alice to Bob.");
peerB.answer(offer)
.then(proffer => {
console.debug("Signal transfering proffer from Bob to Alice.");
peerA.sealTheDeal(proffer)
.then(() => {
peerB.offer()
.then(offer => {
console.debug("Signal transfering offer from Bob to Alice.");
peerA.answer(offer)
.then(proffer => {
console.debug("Signal transfering proffer from Alice to Bob.");
peerB.sealTheDeal(proffer)
.then(() => {
console.debug("HYPE");
peerA.send("From Alice to Bob.");
peerB.send("From Bob to Alice.");
})
.catch(console.error);
})
.catch(console.error);
})
.catch(console.error);
})
.catch(console.error);
})
.catch(console.error);
})
.catch(console.error);
window.peerA = peerA;
window.peerB = peerB;
}
flow();
peerConnection.ondatachannel
仅在应答者上触发。
offerer获取数据通道如下:
this.dataChannel = peerConnection.createDataChannel("datachannel");
其他评论:
webkitRTCPeerConnection
仅适用于 Chrome/Opera。应该填充还是使用 adapter.js.- 厄运金字塔不需要承诺。
- 避免 promise constructor anti-pattern.
示例:
var pc1 = new RTCPeerConnection(), pc2 = new RTCPeerConnection();
pc1.onicecandidate = e => pc2.addIceCandidate(e.candidate);
pc2.onicecandidate = e => pc1.addIceCandidate(e.candidate);
pc1.oniceconnectionstatechange = e => console.log(pc1.iceConnectionState);
pc1.onnegotiationneeded = e =>
pc1.createOffer().then(d => pc1.setLocalDescription(d))
.then(() => pc2.setRemoteDescription(pc1.localDescription))
.then(() => pc2.createAnswer()).then(d => pc2.setLocalDescription(d))
.then(() => pc1.setRemoteDescription(pc2.localDescription))
.catch(e => console.log(e));
var dc1, dc2;
pc2.ondatachannel = e => {
dc2 = e.channel;
dc2.onopen = () => console.log("Chat!");
dc2.onmessage = e => console.log("> " + e.data);
};
dc1 = pc1.createDataChannel("chat");
dc1.onopen = () => (chat.disabled = false, chat.select());
chat.onkeypress = e => {
if (e.keyCode != 13) return;
dc1.send(chat.value);
chat.value = "";
};
Chat: <input id="chat" disabled></input>
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>