Flutter中WebRTC多端对等连接数据通道问题求助
Flutter WebRTC 多用户数据通道实现方案
核心思路
每个参与者必须为其他每一位在线用户创建独立的RTCPeerConnection和RTCDataChannel实例,绝对不能复用同一个连接对象——这就是你遇到「新连接建立时旧连接断开」的核心原因。同时需要通过信令服务器(比如WebSocket)处理SDP交换、ICE候选转发,完成多端的连接协商。
关键实现步骤
1. 定义连接信息模型
用模型统一管理每个对应用户的连接资源:
class PeerConnectionInfo { final String userId; final RTCPeerConnection peerConnection; final RTCDataChannel? dataChannel; PeerConnectionInfo({ required this.userId, required this.peerConnection, this.dataChannel, }); }
2. 初始化基础配置
提前准备WebRTC连接和数据通道的通用配置:
// STUN服务器配置,有需要可添加TURN服务器 final Map<String, dynamic> rtcConfig = { 'iceServers': [ {'urls': 'stun:stun.l.google.com:19302'}, ] }; // 数据通道配置:保证消息顺序,限制重传次数 final RTCDataChannelInit dataChannelConfig = RTCDataChannelInit( ordered: true, maxRetransmits: 3, );
3. 为新用户创建独立连接
当信令服务器通知有新用户加入时,立即为该用户创建专属连接:
// 存储所有活跃连接,key为对方用户ID final Map<String, PeerConnectionInfo> _activePeers = {}; // 处理新用户加入逻辑 Future<void> setupConnectionToUser(String remoteUserId) async { // 创建新的PeerConnection实例 final pc = await createPeerConnection(rtcConfig); // 监听ICE候选,通过信令转发给对方 pc.onIceCandidate = (candidate) { if (candidate != null) { _sendSignalingMsg({ 'type': 'ice_candidate', 'target': remoteUserId, 'data': candidate.toMap(), }); } }; // 创建数据通道并绑定监听 final dataChannel = await pc.createDataChannel('group_chat', dataChannelConfig); _bindDataChannelEvents(dataChannel, remoteUserId); // 生成Offer并发送给对方 final offer = await pc.createOffer(); await pc.setLocalDescription(offer); _sendSignalingMsg({ 'type': 'offer', 'target': remoteUserId, 'data': offer.toMap(), }); // 保存连接信息 _activePeers[remoteUserId] = PeerConnectionInfo( userId: remoteUserId, peerConnection: pc, dataChannel: dataChannel, ); }
4. 处理信令消息(Offer/Answer/ICE)
对接信令服务器,处理来自其他用户的协商请求:
void handleSignalingMsg(Map<String, dynamic> msg) { final senderId = msg['sender']; switch (msg['type']) { case 'offer': _processOffer(senderId, msg['data']); break; case 'answer': _processAnswer(senderId, msg['data']); break; case 'ice_candidate': _processIceCandidate(senderId, msg['data']); break; } } // 处理收到的Offer Future<void> _processOffer(String senderId, Map<String, dynamic> sdp) async { final pc = await createPeerConnection(rtcConfig); pc.onIceCandidate = (candidate) { if (candidate != null) { _sendSignalingMsg({ 'type': 'ice_candidate', 'target': senderId, 'data': candidate.toMap(), }); } }; // 监听对方创建的数据通道 pc.onDataChannel = (channel) { _bindDataChannelEvents(channel, senderId); _activePeers[senderId] = PeerConnectionInfo( userId: senderId, peerConnection: pc, dataChannel: channel, ); }; // 设置远程SDP并生成Answer await pc.setRemoteDescription(RTCSessionDescription(sdp['sdp'], sdp['type'])); final answer = await pc.createAnswer(); await pc.setLocalDescription(answer); _sendSignalingMsg({ 'type': 'answer', 'target': senderId, 'data': answer.toMap(), }); } // 处理收到的Answer Future<void> _processAnswer(String senderId, Map<String, dynamic> sdp) async { final peerInfo = _activePeers[senderId]; if (peerInfo != null) { await peerInfo.peerConnection.setRemoteDescription( RTCSessionDescription(sdp['sdp'], sdp['type']) ); } } // 处理收到的ICE候选 Future<void> _processIceCandidate(String senderId, Map<String, dynamic> candidate) async { final peerInfo = _activePeers[senderId]; if (peerInfo != null) { await peerInfo.peerConnection.addIceCandidate( RTCIceCandidate(candidate['candidate'], candidate['sdpMid'], candidate['sdpMLineIndex']) ); } }
5. 绑定数据通道事件
统一处理消息接收、通道状态变更:
void _bindDataChannelEvents(RTCDataChannel channel, String senderId) { // 接收消息 channel.onMessage = (msg) { final content = String.fromCharCodes(msg.data); // 这里可以添加UI更新逻辑,比如展示消息 print('收到来自$senderId的消息:$content'); }; // 通道打开通知 channel.onOpen = () { print('与$senderId的数据通道已建立'); }; // 通道关闭时清理连接 channel.onClose = () { print('与$senderId的数据通道已断开'); _activePeers.remove(senderId); }; }
6. 发送消息给所有用户
遍历所有活跃数据通道,实现全员消息推送:
void broadcastMessage(String content) { _activePeers.values.forEach((peerInfo) { final channel = peerInfo.dataChannel; if (channel != null && channel.readyState == RTCDataChannelState.RTCDataChannelOpen) { channel.send(RTCDataChannelMessage(content)); } }); }
常见问题排查
- 旧连接被断开:检查是否复用了
RTCPeerConnection实例,必须为每个用户单独创建。 - 消息无法接收:确认数据通道的
onMessage监听已绑定,且通道状态为Open。 - 连接协商失败:检查STUN/TURN服务器配置是否有效,信令服务器是否正确转发ICE候选。
内容的提问来源于stack exchange,提问作者Ravi Taak
相关产品推荐
相关产品推荐

