You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

使用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);
   };
 }

问题根源

  1. 接收端未监听datachannel事件:发起端调用createDataChannel创建通道后,接收端需通过RTCPeerConnection的datachannel事件获取远端通道,当前代码未处理该事件,导致接收端无法绑定消息监听。
  2. 数据通道状态未校验:发送消息前未确认通道处于open状态,直接调用send可能导致消息丢失。
  3. 信令服务器leave分支存在变量错误:if(conn != null)中的conn未定义,会触发报错并影响后续逻辑。
  4. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.15 22:05:21