使用RTCDataChannel.send()无数据接收的WebRTC聊天问题排查
WebRTC数据通道无法发送数据排查与修复
我在用WebRTC开发简易聊天应用,WebRTC连接已成功建立,但调用RTCDataChannel.send()无法向对端发送数据。通过chrome://webrtc-internals/调试显示连接状态正常,但接收端的RTCDataChannel始终没有messagesReceived记录。
原始代码
信令服务器代码
import { WebSocketServer } from 'ws'; import express from 'express'; import path from 'path'; const PORT = 3000; const app = express(); app.use(express.static('public')); app.get('/', (req, res) => { res.sendFile("index.html"); }); app.listen(PORT, () => { console.log('Example app listening on port 3000!'); }); function sendTo(connection, message) { connection.send(JSON.stringify(message)); } let wss = new WebSocketServer({ port: 9090 }); // All users connected to the server let users = {}; //when a user connects to our sever wss.on('connection', (connection) => { console.log("user connected"); //when server gets a message from a connected user connection.on('message', (message) => { let data; //accepting only JSON messages try { data = JSON.parse(message); } catch (e) { console.error("Invalid JSON"); data = {}; } //switching type of the user message switch (data.type) { //when a user tries to login case "login": console.log("User logged:", data.name); //if anyone is logged in with this username then refuse if (users[data.name]) {sendTo(connection, {type: "login", success: false}); } else { //save user connection on the server users[data.name] = connection; connection.name = data.name; sendTo(connection, {type: "login", success: true}); } break; case "offer": //for ex. UserA wants to call UserB console.log(`Sending offer: from ${connection.name} to ${data.name}`); //if UserB exists then send him offer details let userOfferConn = users[data.name]; if(userOfferConn != null){ //setting that UserA connected with UserB connection.otherName = data.name; sendTo(userOfferConn, {type: "offer", offer: data.offer, name: connection.name}); } break; case "answer": console.log(`Sending answer: from ${connection.name} to ${data.name}`); //for ex. UserB answers UserA let userAnswerConn = users[data.name]; if(userAnswerConn != null) { connection.otherName = data.name; sendTo(userAnswerConn, {type: "answer", answer: data.answer}); } break; case "candidate": console.log(`Sending candidate from ${connection.name} to ${data.name}`); let userCandidateConn = users[data.name]; if(userCandidateConn != null) { sendTo(userCandidateConn, {type: "candidate", candidate: data.candidate}); } break; case "leave": console.log(`Disconnecting: ${connection.name} & ${data.name}`); let userDiscConn = users[data.name]; userDiscConn.otherName = null; if(conn != null) { // Notify the other user so he can disconnect his peer connection sendTo(userDiscConn, {type: "leave"}); } default: sendTo(connection, {type: "error", message: "Command no found: " + data.type}); break; } connection.on("close", () => { if(connection.name) { delete users[connection.name]; if(connection.otherName) { console.log(`Disconnecting: ${connection.name} & ${connection.otherName}`); let userDiscConn = users[connection.otherName]; userDiscConn.otherName = null; if(userDiscConn != null) { sendTo(userDiscConn, {type: "leave"}); } } } }); }); });
客户端HTML代码
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta http-equiv="X-UA-Compatible" content="IE=edge"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <title>Document</title> </head> <body> <div> <input type="text" id="loginInput" /> <button id="loginBtn">Login</button> </div> <div> <input type="text" id="otherUsernameInput" /> <button id="connectToOtherUsernameBtn">Establish connection</button> </div> <div> <input type="text" id="msgInput" /> <button id="sendMsgBtn">Send text message</button> </div> <script src="client.js"></script> </body> </html>
客户端JavaScript代码
let connection = new WebSocket('ws://localhost:9090'); let username = ""; let loginInput = document.getElementById("loginInput"); let loginBtn = document.getElementById("loginBtn"); let otherUsernameInput = document.getElementById("otherUsernameInput"); let connectToOtherUsernameBtn = document.getElementById("connectToOtherUsernameBtn"); let msgInput = document.getElementById("msgInput"); let sendMsgBtn = document.getElementById("sendMsgBtn"); let connectedUser, myConnection, dataChannel; function send(message) { if (connectedUser) { message.name = connectedUser; } connection.send(JSON.stringify(message)); } // When a user clicks the login button loginBtn.addEventListener("click", (event) => { username = loginInput.value; if (username.length > 0) { send({ type: "login", name: username }); } }); // Setup a peer connection with another user connectToOtherUsernameBtn.addEventListener("click", () => { let otherUsername = otherUsernameInput.value; connectedUser = otherUsername; if (otherUsername.length <= 0) return; // Make an offer myConnection.createOffer((offer) => { console.log("Offer"); send({ type: "offer", offer: offer }); myConnection.setLocalDescription(offer); }, (error) => { alert("An error has occurred."); }); }); // When a user clicks the send message button sendMsgBtn.addEventListener("click", (event) => { var val = msgInput.value; console.log(`Send message: ${val}`); dataChannel.send(val); }); // Handle messages from the server connection.onmessage = (message) => { console.log("Got message: ", message.data); let data = JSON.parse(message.data); switch (data.type) { case "login": onLogin(data.success); break; case "offer": onOffer(data.offer, data.name); break; case "answer": onAnswer(data.answer); break; case "candidate": onCandidate(data.candidate); break; default: break; } }; // When a user logs in function onLogin(success) { if (success === false) { alert("Oops... Try a different username"); return; } // Creating our RTCPeerConnection object var configuration = { "iceServers": [ { "urls": "stun:stun.1.google.com:19302" } ] }; myConnection = new RTCPeerConnection(configuration); console.log("RTCPeerConnection object was created"); console.log(myConnection); // Setup ice handling // When the browser finds an ice candidate we send it to another peer myConnection.onicecandidate = (event) => { if (event.candidate) { send({ type: "candidate", candidate: event.candidate }); } }; openDataChannel(); }; //when somebody wants to call us function onOffer(offer, name) { connectedUser = name; myConnection.setRemoteDescription(new RTCSessionDescription(offer)); myConnection.createAnswer((answer) => { myConnection.setLocalDescription(answer); send({ type: "answer", answer: answer }); }, (error) => { alert("Oops... Error"); }); } // When another user answers to our offer function onAnswer(answer) { myConnection.setRemoteDescription(new RTCSessionDescription(answer)); } // When we got ice candidate from another user function onCandidate(candidate) { myConnection.addIceCandidate(new RTCIceCandidate(candidate)); } connection.onopen = function () { console.log("Connected"); }; connection.onerror = function (err) { console.log("Got error", err); }; // Alias for sending messages in JSON format function send(message) { if (connectedUser) message.name = connectedUser; connection.send(JSON.stringify(message)); }; // Creating data channel function openDataChannel() { dataChannel = myConnection.createDataChannel("myDataChannel"); console.log(dataChannel); dataChannel.onerror = (error) => { console.log("Error:", error); }; dataChannel.onmessage = (event) => { console.log("Got message: ", event.data); }; }
问题根源
- 接收端未监听
datachannel事件:发起端调用createDataChannel创建通道后,接收端需通过RTCPeerConnection的datachannel事件获取远端通道,当前代码未处理该事件,导致接收端无法绑定消息监听。 - 数据通道状态未校验:发送消息前未确认通道处于
open状态,直接调用send可能导致消息丢失。 - 信令服务器
leave分支存在变量错误:if(conn != null)中的conn未定义,会触发报错并影响后续逻辑。 leave分支缺少break:会执行到default分支,返回错误信息。
修复方案
1. 接收端添加datachannel事件监听
在onLogin函数中创建myConnection后,添加远端数据通道监听:
// 监听远端创建的数据通道 myConnection.ondatachannel = (event) => { dataChannel = event.channel; dataChannel.onerror = (error) => { console.log("Error:", error); }; dataChannel.onmessage = (event) => { console.log("Got message: ", event.data); }; };
2. 校验数据通道状态后发送消息
修改发送按钮点击事件,确保通道就绪:
sendMsgBtn.addEventListener("click", (event) => { var val = msgInput.value; if (!val || !dataChannel || dataChannel.readyState !== "open") { console.log("Cannot send message: invalid input or channel not open"); return; } console.log(`Send message: ${val}`); dataChannel.send(val); });
3. 修复信令服务器leave分支错误
修正未定义变量并添加break:
case "leave": console.log(`Disconnecting: ${connection.name} & ${data.name}`); let userDiscConn = users[data.name]; if(userDiscConn != null) { userDiscConn.otherName = null; sendTo(userDiscConn, {type: "leave"}); } break;
4. 优化createOffer回调(可选)
改用Promise风格替代旧回调,提升代码可读性:
connectToOtherUsernameBtn.addEventListener("click", async () => { let otherUsername = otherUsernameInput.value; connectedUser = otherUsername; if (otherUsername.length <= 0) return; try { const offer = await myConnection.createOffer(); console.log("Offer"); send({ type: "offer", offer: offer }); await myConnection.setLocalDescription(offer); } catch (error) { alert("An error has occurred."); console.error(error); } });
内容的提问来源于stack exchange,提问作者Prankster2k
相关产品推荐
相关产品推荐

