WebRTC 完全指南:原理、教程与应用场景
目录
- [什么是 WebRTC](#什么是 WebRTC)
- [为什么需要 WebRTC](#为什么需要 WebRTC)
- [WebRTC 的核心概念](#WebRTC 的核心概念)
- [WebRTC 应用场景](#WebRTC 应用场景)
- [WebRTC 教程与代码实现](#WebRTC 教程与代码实现)
- 最佳实践与注意事项
什么是 WebRTC
WebRTC (Web Real-Time Communication) 是一个开源项目,它为浏览器和移动应用程序提供实时通信(RTC)能力,通过简单的 API 即可实现音频、视频和数据在浏览器之间的点对点(P2P)传输。
WebRTC 由 Google 于 2011 年发起,现已成为 W3C 和 IETF 的标准,被所有主流浏览器支持,包括 Chrome、Firefox、Safari 和 Edge。
WebRTC 的主要特性
- 实时通信:低延迟的音视频传输
- 点对点连接:直接在浏览器之间建立连接,无需中间服务器中转媒体流
- 跨平台:支持 Web、iOS、Android 等多种平台
- 无需插件:原生浏览器支持,无需安装任何插件
- 安全性:强制使用加密传输
- 免费开源:完全免费的开源技术
为什么需要 WebRTC
传统通信方式的局限性
在 WebRTC 出现之前,实现浏览器间的实时通信面临诸多挑战:
- 依赖插件:需要安装 Flash、Silverlight 等插件
- 延迟高:传统 HTTP 请求-响应模型不适合实时通信
- 服务器负载大:所有媒体流都需要通过服务器中转
- NAT 穿透困难:不同网络环境下的设备难以直接通信
- 安全风险:缺乏统一的安全标准
WebRTC 的优势
- 降低延迟:点对点直接传输,延迟可低至几十毫秒
- 节省带宽:媒体流不需要经过服务器,大幅降低带宽成本
- 更好的用户体验:无需安装插件,打开浏览器即可使用
- 安全性保障:强制使用 DTLS 和 SRTP 加密
- 跨平台兼容:统一的标准,各平台实现一致
典型场景对比
| 场景 | 传统方案 | WebRTC 方案 |
|---|---|---|
| 视频会议 | 需要下载客户端 | 浏览器直接访问 |
| 在线教育 | 插件兼容性问题 | 跨平台统一体验 |
| 客服系统 | 高昂的服务器成本 | P2P 降低成本 |
| 游戏直播 | 延迟较高 | 低延迟互动 |
WebRTC 的核心概念
1. 媒体流 (MediaStream)
MediaStream 表示媒体内容的流,包含音频轨道和视频轨道。
javascript
// 获取用户媒体流
navigator.mediaDevices
.getUserMedia({ video: true, audio: true })
.then((stream) => {
// 使用媒体流
})
.catch((error) => {
console.error("获取媒体流失败:", error);
});
2. RTCPeerConnection
RTCPeerConnection 是 WebRTC 的核心 API,用于处理点对点连接。
javascript
const peerConnection = new RTCPeerConnection(configuration);
3. 信令 (Signaling)
信令是建立 WebRTC 连接的过程,包括:
- 交换会话描述协议 (SDP)
- 交换网络候选者 (ICE candidates)
4. ICE (Interactive Connectivity Establishment)
ICE 是一种框架,用于找到最佳的连接路径,包括:
- 主机候选者:本地 IP 地址
- 反射候选者:通过 STUN 服务器获取的公网 IP
- 中继候选者:通过 TURN 服务器中转的地址
5. STUN 和 TURN 服务器
- STUN (Session Traversal Utilities for NAT):帮助设备发现其公网地址
- TURN (Traversal Using Relays around NAT):在直接连接失败时提供中继服务
6. 数据通道 (Data Channel)
数据通道是 WebRTC 提供的用于在点对点连接中传输任意数据的机制,具有以下特点:
- 支持多种数据类型:文本消息、二进制数据、JSON 等
- 灵活的传输模式:可靠传输(类似TCP)和不可靠传输(类似UDP)
- 低延迟:直接点对点传输,无需服务器中转
- 安全性:使用 DTLS 加密传输
- 双向通信:支持双向实时数据交换
数据通道应用场景:
- 实时聊天
- 文件传输
- 游戏状态同步
- 协同编辑
- 传感器数据传输
javascript
// 创建数据通道
const dataChannel = peerConnection.createDataChannel("chat", {
ordered: true, // 保证数据顺序
maxRetransmits: 0, // 不重传,降低延迟
});
// 发送数据
dataChannel.send("Hello, WebRTC!");
// 接收数据
dataChannel.onmessage = (event) => {
console.log("收到消息:", event.data);
};
WebRTC 应用场景
1. 视频会议
场景描述:多人实时视频会议,支持屏幕共享、白板协作等功能。
技术要点:
- SFU (Selective Forwarding Unit) 架构处理多路媒体流
- 自适应码率调整网络状况
- 丢包恢复和错误隐藏技术
2. 在线教育
场景描述:远程教学、互动课堂、在线辅导。
技术要点:
- 低延迟音视频传输
- 屏幕共享和文档标注
- 实时聊天和互动工具
3. 客服系统
场景描述:网站集成视频客服,提供面对面服务。
技术要点:
- 快速建立连接
- 与 CRM 系统集成
- 录制和质检功能
4. 游戏直播
场景描述:实时游戏画面分享,观众互动。
技术要点:
- 低延迟传输
- 高质量视频编码
- 多路流混合
5. 物联网 (IoT)
场景描述:远程监控设备、实时数据传输。
技术要点:
- 数据通道传输传感器数据
- 低功耗实现
- 设备间直接通信
6. 文件共享
场景描述:点对点文件传输,无需服务器中转。
技术要点:
- 使用数据通道传输文件
- 断点续传
- 传输进度显示
WebRTC 教程与代码实现
基础示例:获取本地媒体流
html
<!DOCTYPE html>
<html>
<head>
<title>WebRTC 基础示例</title>
</head>
<body>
<h1>获取本地媒体流</h1>
<video id="localVideo" autoplay playsinline></video>
<script>
const localVideo = document.getElementById("localVideo");
async function startLocalStream() {
try {
const stream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true,
});
localVideo.srcObject = stream;
} catch (error) {
console.error("获取媒体流失败:", error);
}
}
startLocalStream();
</script>
</body>
</html>
进阶示例:点对点视频通话
HTML 结构
html
<!DOCTYPE html>
<html>
<head>
<title>WebRTC 视频通话</title>
<style>
.video-container {
display: flex;
gap: 20px;
}
video {
width: 400px;
height: 300px;
}
</style>
</head>
<body>
<h1>WebRTC 视频通话</h1>
<div class="video-container">
<div>
<h2>本地视频</h2>
<video id="localVideo" autoplay playsinline></video>
</div>
<div>
<h2>远程视频</h2>
<video id="remoteVideo" autoplay playsinline></video>
</div>
</div>
<div>
<button id="callButton">发起通话</button>
<button id="hangupButton">挂断</button>
</div>
<script src="webrtc.js"></script>
</body>
</html>
JavaScript 实现 (webrtc.js)
javascript
// WebRTC 配置
const configuration = {
iceServers: [
{ urls: "stun:stun.l.google.com:19302" },
{ urls: "stun:stun1.l.google.com:19302" },
],
};
// DOM 元素
const localVideo = document.getElementById("localVideo");
const remoteVideo = document.getElementById("remoteVideo");
const callButton = document.getElementById("callButton");
const hangupButton = document.getElementById("hangupButton");
// WebRTC 变量
let localStream;
let remoteStream;
let peerConnection;
// 创建 RTCPeerConnection
function createPeerConnection() {
peerConnection = new RTCPeerConnection(configuration);
// 添加本地流到连接
localStream.getTracks().forEach((track) => {
peerConnection.addTrack(track, localStream);
});
// 监听远程流
peerConnection.ontrack = (event) => {
remoteVideo.srcObject = event.streams[0];
};
// 监听 ICE 候选者
peerConnection.onicecandidate = (event) => {
if (event.candidate) {
// 在实际应用中,这里需要将 candidate 发送给对方
console.log("ICE candidate:", event.candidate);
}
};
// 监听连接状态变化
peerConnection.onconnectionstatechange = () => {
console.log("连接状态:", peerConnection.connectionState);
};
}
// 发起通话
async function startCall() {
try {
// 获取本地媒体流
localStream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true,
});
localVideo.srcObject = localStream;
// 创建 PeerConnection
createPeerConnection();
// 创建 Offer
const offer = await peerConnection.createOffer();
await peerConnection.setLocalDescription(offer);
// 在实际应用中,这里需要将 offer 发送给对方
console.log("Offer:", offer);
} catch (error) {
console.error("发起通话失败:", error);
}
}
// 挂断通话
function hangup() {
if (peerConnection) {
peerConnection.close();
peerConnection = null;
}
if (localStream) {
localStream.getTracks().forEach((track) => track.stop());
localStream = null;
}
localVideo.srcObject = null;
remoteVideo.srcObject = null;
}
// 事件监听
callButton.addEventListener("click", startCall);
hangupButton.addEventListener("click", hangup);
数据通道示例:点对点数据传输
WebRTC 不仅支持音视频传输,还提供了数据通道(DataChannel)功能,可以在点对点连接中传输任意数据,如文本消息、文件、游戏状态等。
数据通道基础概念
数据通道特点:
- 支持可靠和不可靠传输
- 低延迟的点对点数据传输
- 支持二进制数据
- 可配置传输顺序
数据通道类型:
- 可靠数据通道:确保数据按顺序到达,类似TCP
- 不可靠数据通道:不保证数据到达,类似UDP
创建和使用数据通道
javascript
// 创建数据通道
const dataChannel = peerConnection.createDataChannel("chat", {
ordered: true, // 保证数据顺序
maxRetransmits: 0, // 不重传,降低延迟
});
// 监听数据通道打开事件
dataChannel.onopen = () => {
console.log("数据通道已打开");
// 发送消息
dataChannel.send("Hello, WebRTC!");
// 发送JSON数据
dataChannel.send(
JSON.stringify({
type: "message",
content: "这是一条JSON消息",
}),
);
// 发送二进制数据
const arrayBuffer = new Uint8Array([1, 2, 3, 4, 5]);
dataChannel.send(arrayBuffer);
};
// 监听接收到的消息
dataChannel.onmessage = (event) => {
console.log("收到消息:", event.data);
// 处理JSON数据
try {
const message = JSON.parse(event.data);
if (message.type === "message") {
console.log("内容:", message.content);
}
} catch (e) {
// 非JSON数据处理
}
};
// 监听数据通道关闭事件
dataChannel.onclose = () => {
console.log("数据通道已关闭");
};
// 监听错误事件
dataChannel.onerror = (error) => {
console.error("数据通道错误:", error);
};
接收端数据通道处理
javascript
// 在接收端,监听数据通道事件
peerConnection.ondatachannel = (event) => {
const dataChannel = event.channel;
dataChannel.onopen = () => {
console.log("数据通道已打开");
// 可以开始发送数据
};
dataChannel.onmessage = (event) => {
console.log("收到消息:", event.data);
// 处理接收到的数据
};
dataChannel.onclose = () => {
console.log("数据通道已关闭");
};
dataChannel.onerror = (error) => {
console.error("数据通道错误:", error);
};
};
完整示例:基于数据通道的聊天应用
HTML 结构
html
<!DOCTYPE html>
<html>
<head>
<title>WebRTC 数据通道聊天</title>
<style>
.container {
max-width: 800px;
margin: 0 auto;
padding: 20px;
}
.chat-box {
height: 400px;
border: 1px solid #ccc;
overflow-y: scroll;
padding: 10px;
margin-bottom: 10px;
}
.message {
margin-bottom: 10px;
padding: 5px 10px;
border-radius: 5px;
}
.local {
background-color: #e3f2fd;
text-align: right;
}
.remote {
background-color: #f5f5f5;
}
.input-area {
display: flex;
gap: 10px;
}
#messageInput {
flex: 1;
padding: 8px;
}
button {
padding: 8px 16px;
cursor: pointer;
}
</style>
</head>
<body>
<div class="container">
<h1>WebRTC 数据通道聊天</h1>
<div>
<button id="connectButton">连接</button>
<button id="disconnectButton" disabled>断开连接</button>
</div>
<div class="chat-box" id="chatBox"></div>
<div class="input-area">
<input type="text" id="messageInput" placeholder="输入消息..." />
<button id="sendButton" disabled>发送</button>
</div>
</div>
<script src="datachannel-chat.js"></script>
</body>
</html>
JavaScript 实现 (datachannel-chat.js)
javascript
// WebRTC 配置
const configuration = {
iceServers: [
{ urls: "stun:stun.l.google.com:19302" },
{ urls: "stun:stun1.l.google.com:19302" },
],
};
// DOM 元素
const connectButton = document.getElementById("connectButton");
const disconnectButton = document.getElementById("disconnectButton");
const sendButton = document.getElementById("sendButton");
const messageInput = document.getElementById("messageInput");
const chatBox = document.getElementById("chatBox");
// WebRTC 变量
let peerConnection;
let dataChannel;
// 初始化连接
async function connect() {
try {
// 创建 PeerConnection
peerConnection = new RTCPeerConnection(configuration);
// 创建数据通道
dataChannel = peerConnection.createDataChannel("chat", {
ordered: true,
});
// 设置数据通道事件处理
setupDataChannel(dataChannel);
// 监听 ICE 候选者
peerConnection.onicecandidate = (event) => {
if (event.candidate) {
// 在实际应用中,这里需要将 candidate 发送给对方
console.log("ICE candidate:", event.candidate);
}
};
// 监听连接状态变化
peerConnection.onconnectionstatechange = () => {
console.log("连接状态:", peerConnection.connectionState);
if (peerConnection.connectionState === "connected") {
connectButton.disabled = true;
disconnectButton.disabled = false;
sendButton.disabled = false;
addMessage("系统", "连接已建立");
} else if (
peerConnection.connectionState === "disconnected" ||
peerConnection.connectionState === "closed"
) {
connectButton.disabled = false;
disconnectButton.disabled = true;
sendButton.disabled = true;
addMessage("系统", "连接已断开");
}
};
// 创建 Offer
const offer = await peerConnection.createOffer();
await peerConnection.setLocalDescription(offer);
// 在实际应用中,这里需要将 offer 发送给对方
console.log("Offer:", offer);
} catch (error) {
console.error("连接失败:", error);
addMessage("系统", "连接失败: " + error.message);
}
}
// 设置数据通道事件处理
function setupDataChannel(channel) {
channel.onopen = () => {
console.log("数据通道已打开");
addMessage("系统", "数据通道已打开");
};
channel.onmessage = (event) => {
console.log("收到消息:", event.data);
addMessage("对方", event.data);
};
channel.onclose = () => {
console.log("数据通道已关闭");
addMessage("系统", "数据通道已关闭");
};
channel.onerror = (error) => {
console.error("数据通道错误:", error);
addMessage("系统", "数据通道错误: " + error);
};
}
// 断开连接
function disconnect() {
if (dataChannel) {
dataChannel.close();
dataChannel = null;
}
if (peerConnection) {
peerConnection.close();
peerConnection = null;
}
connectButton.disabled = false;
disconnectButton.disabled = true;
sendButton.disabled = true;
}
// 发送消息
function sendMessage() {
const message = messageInput.value.trim();
if (message && dataChannel && dataChannel.readyState === "open") {
dataChannel.send(message);
addMessage("我", message);
messageInput.value = "";
}
}
// 添加消息到聊天框
function addMessage(sender, message) {
const messageDiv = document.createElement("div");
messageDiv.className = `message ${sender === "我" ? "local" : "remote"}`;
messageDiv.textContent = `${sender}: ${message}`;
chatBox.appendChild(messageDiv);
chatBox.scrollTop = chatBox.scrollHeight;
}
// 事件监听
connectButton.addEventListener("click", connect);
disconnectButton.addEventListener("click", disconnect);
sendButton.addEventListener("click", sendMessage);
messageInput.addEventListener("keypress", (event) => {
if (event.key === "Enter") {
sendMessage();
}
});
数据通道高级应用:文件传输
javascript
// 发送文件
async function sendFile(file) {
if (!dataChannel || dataChannel.readyState !== "open") {
console.error("数据通道未打开");
return;
}
// 发送文件元数据
const metadata = {
type: "file-metadata",
name: file.name,
size: file.size,
mimeType: file.type,
};
dataChannel.send(JSON.stringify(metadata));
// 分块发送文件内容
const chunkSize = 16384; // 16KB
const fileReader = new FileReader();
let offset = 0;
fileReader.onload = (event) => {
dataChannel.send(event.target.result);
offset += event.target.result.byteLength;
if (offset < file.size) {
readSlice(offset);
} else {
console.log("文件发送完成");
}
};
const readSlice = (o) => {
const slice = file.slice(offset, o + chunkSize);
fileReader.readAsArrayBuffer(slice);
};
readSlice(0);
}
// 接收文件
let receivedFile = null;
let receivedSize = 0;
function handleDataChannelMessage(event) {
const data = event.data;
// 处理文件元数据
if (typeof data === "string") {
try {
const message = JSON.parse(data);
if (message.type === "file-metadata") {
receivedFile = {
name: message.name,
size: message.size,
mimeType: message.mimeType,
chunks: [],
};
receivedSize = 0;
}
} catch (e) {
// 处理普通文本消息
console.log("收到消息:", data);
}
}
// 处理文件数据块
else if (receivedFile && data instanceof ArrayBuffer) {
receivedFile.chunks.push(data);
receivedSize += data.byteLength;
// 检查是否接收完成
if (receivedSize >= receivedFile.size) {
// 合并所有数据块
const blob = new Blob(receivedFile.chunks, {
type: receivedFile.mimeType,
});
// 创建下载链接
const url = URL.createObjectURL(blob);
const a = document.createElement("a");
a.href = url;
a.download = receivedFile.name;
a.click();
// 清理
URL.revokeObjectURL(url);
receivedFile = null;
receivedSize = 0;
}
}
}
// 在数据通道的 onmessage 事件中使用
dataChannel.onmessage = handleDataChannelMessage;
完整示例:带信令服务的视频通话
服务端代码 (Node.js + Socket.io)
javascript
// server.js
const express = require("express");
const http = require("http");
const socketIo = require("socket.io");
const app = express();
const server = http.createServer(app);
const io = socketIo(server);
app.use(express.static("public"));
io.on("connection", (socket) => {
console.log("用户连接:", socket.id);
// 加入房间
socket.on("join", (roomId) => {
socket.join(roomId);
socket.to(roomId).emit("user-joined", socket.id);
});
// 信令消息转发
socket.on("offer", (data) => {
socket.to(data.roomId).emit("offer", {
offer: data.offer,
senderId: socket.id,
});
});
socket.on("answer", (data) => {
socket.to(data.roomId).emit("answer", {
answer: data.answer,
senderId: socket.id,
});
});
socket.on("ice-candidate", (data) => {
socket.to(data.roomId).emit("ice-candidate", {
candidate: data.candidate,
senderId: socket.id,
});
});
socket.on("disconnect", () => {
console.log("用户断开连接:", socket.id);
});
});
const PORT = process.env.PORT || 3000;
server.listen(PORT, () => {
console.log(`服务器运行在端口 ${PORT}`);
});
客户端代码
javascript
// client.js
const socket = io();
const configuration = {
iceServers: [{ urls: "stun:stun.l.google.com:19302" }],
};
let localStream;
let peerConnection;
const roomId = "room-123"; // 可以动态生成
// 获取本地媒体流
async function getLocalStream() {
try {
localStream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true,
});
document.getElementById("localVideo").srcObject = localStream;
return localStream;
} catch (error) {
console.error("获取媒体流失败:", error);
}
}
// 创建 PeerConnection
function createPeerConnection() {
peerConnection = new RTCPeerConnection(configuration);
// 添加本地流
localStream.getTracks().forEach((track) => {
peerConnection.addTrack(track, localStream);
});
// 监听远程流
peerConnection.ontrack = (event) => {
document.getElementById("remoteVideo").srcObject = event.streams[0];
};
// 监听 ICE 候选者
peerConnection.onicecandidate = (event) => {
if (event.candidate) {
socket.emit("ice-candidate", {
candidate: event.candidate,
roomId: roomId,
});
}
};
return peerConnection;
}
// 发起通话
async function startCall() {
await getLocalStream();
createPeerConnection();
const offer = await peerConnection.createOffer();
await peerConnection.setLocalDescription(offer);
socket.emit("offer", {
offer: offer,
roomId: roomId,
});
}
// 处理 Offer
socket.on("offer", async (data) => {
if (!peerConnection) {
await getLocalStream();
createPeerConnection();
}
await peerConnection.setRemoteDescription(
new RTCSessionDescription(data.offer),
);
const answer = await peerConnection.createAnswer();
await peerConnection.setLocalDescription(answer);
socket.emit("answer", {
answer: answer,
roomId: roomId,
});
});
// 处理 Answer
socket.on("answer", async (data) => {
await peerConnection.setRemoteDescription(
new RTCSessionDescription(data.answer),
);
});
// 处理 ICE 候选者
socket.on("ice-candidate", async (data) => {
if (peerConnection) {
try {
await peerConnection.addIceCandidate(new RTCIceCandidate(data.candidate));
} catch (error) {
console.error("添加 ICE 候选者失败:", error);
}
}
});
// 加入房间
socket.emit("join", roomId);
// 绑定按钮事件
document.getElementById("callButton").addEventListener("click", startCall);
最佳实践与注意事项
1. 错误处理
javascript
try {
const stream = await navigator.mediaDevices.getUserMedia(constraints);
} catch (error) {
if (error.name === "NotAllowedError") {
console.error("用户拒绝了媒体权限");
} else if (error.name === "NotFoundError") {
console.error("未找到媒体设备");
} else {
console.error("获取媒体流失败:", error);
}
}
2. 网络适应
javascript
// 监听网络状态
peerConnection.onconnectionstatechange = () => {
switch (peerConnection.connectionState) {
case "connected":
console.log("连接已建立");
break;
case "disconnected":
console.log("连接已断开");
break;
case "failed":
console.log("连接失败");
break;
case "closed":
console.log("连接已关闭");
break;
}
};
// 监听 ICE 连接状态
peerConnection.oniceconnectionstatechange = () => {
switch (peerConnection.iceConnectionState) {
case "connected":
case "completed":
console.log("ICE 连接成功");
break;
case "disconnected":
console.log("ICE 连接断开");
break;
case "failed":
console.log("ICE 连接失败");
break;
}
};
3. 性能优化
- 使用合适的视频分辨率和帧率
- 实现自适应码率
- 优化编解码器选择
- 使用硬件加速
4. 安全考虑
- 使用 HTTPS
- 验证用户身份
- 实现访问控制
- 记录和审计日志
5. 兼容性处理
javascript
// 检查浏览器支持
function isWebRTCSupported() {
return !!(
navigator.mediaDevices &&
navigator.mediaDevices.getUserMedia &&
window.RTCPeerConnection
);
}
if (!isWebRTCSupported()) {
alert("您的浏览器不支持 WebRTC,请使用最新版本的 Chrome、Firefox 或 Safari");
}
6. 数据通道最佳实践
数据通道配置
javascript
// 创建数据通道时根据应用场景选择合适的配置
const reliableChannel = peerConnection.createDataChannel("reliable", {
ordered: true, // 保证数据顺序
protocol: "json", // 指定协议
});
const unreliableChannel = peerConnection.createDataChannel("unreliable", {
ordered: false, // 不保证数据顺序
maxRetransmits: 0, // 不重传,降低延迟
maxPacketLifeTime: 1000, // 数据包最大存活时间(毫秒)
});
数据传输优化
javascript
// 批量发送数据,减少网络开销
function sendBatchData(dataArray) {
if (dataChannel.readyState !== "open") {
console.error("数据通道未打开");
return;
}
// 将多个数据项合并为一个 JSON 对象
const batch = {
type: "batch",
items: dataArray,
timestamp: Date.now(),
};
dataChannel.send(JSON.stringify(batch));
}
// 大文件分块传输
async function sendLargeFile(file) {
const chunkSize = 16384; // 16KB
const totalChunks = Math.ceil(file.size / chunkSize);
// 发送文件元数据
dataChannel.send(
JSON.stringify({
type: "file-start",
name: file.name,
size: file.size,
totalChunks: totalChunks,
mimeType: file.type,
}),
);
// 分块发送文件内容
for (let i = 0; i < totalChunks; i++) {
const start = i * chunkSize;
const end = Math.min(start + chunkSize, file.size);
const chunk = file.slice(start, end);
// 读取并发送数据块
const arrayBuffer = await chunk.arrayBuffer();
dataChannel.send(arrayBuffer);
// 更新进度
const progress = (((i + 1) / totalChunks) * 100).toFixed(2);
console.log(`传输进度: ${progress}%`);
}
// 发送文件结束标记
dataChannel.send(
JSON.stringify({
type: "file-end",
name: file.name,
}),
);
}
数据通道状态管理
javascript
// 监听数据通道状态变化
function setupDataChannelHandlers(dataChannel) {
dataChannel.onopen = () => {
console.log("数据通道已打开");
// 可以开始发送数据
startSendingData();
};
dataChannel.onmessage = (event) => {
handleIncomingData(event.data);
};
dataChannel.onclose = () => {
console.log("数据通道已关闭");
// 清理资源,尝试重新连接
cleanupResources();
attemptReconnect();
};
dataChannel.onerror = (error) => {
console.error("数据通道错误:", error);
// 处理错误,可能需要重新建立连接
handleDataChannelError(error);
};
}
// 检查数据通道状态
function sendDataSafely(data) {
if (!dataChannel) {
console.error("数据通道不存在");
return false;
}
if (dataChannel.readyState !== "open") {
console.error("数据通道未打开,当前状态:", dataChannel.readyState);
return false;
}
try {
dataChannel.send(data);
return true;
} catch (error) {
console.error("发送数据失败:", error);
return false;
}
}
数据通道错误处理
javascript
// 处理数据通道错误
function handleDataChannelError(error) {
console.error("数据通道错误:", error);
// 根据错误类型采取不同措施
if (error.errorDetail === "sctp-failure") {
console.error("SCTP 传输失败,可能需要重新建立连接");
reconnectPeerConnection();
} else if (error.errorDetail === "network-error") {
console.error("网络错误,检查网络连接");
showNetworkErrorToUser();
} else {
console.error("未知错误");
reportError(error);
}
}
// 实现自动重连机制
let reconnectAttempts = 0;
const maxReconnectAttempts = 3;
function attemptReconnect() {
if (reconnectAttempts >= maxReconnectAttempts) {
console.error("达到最大重连次数,停止尝试");
showReconnectFailedMessage();
return;
}
reconnectAttempts++;
const delay = Math.pow(2, reconnectAttempts) * 1000; // 指数退避
console.log(`${delay / 1000}秒后尝试第${reconnectAttempts}次重连...`);
setTimeout(() => {
try {
establishNewConnection();
} catch (error) {
console.error("重连失败:", error);
attemptReconnect();
}
}, delay);
}
总结
WebRTC 是一个强大的实时通信技术,它让浏览器之间的音视频通信变得简单而高效。通过本文的学习,您应该已经掌握了:
- WebRTC 的基本概念和原理
- 为什么需要 WebRTC 以及它的优势
- WebRTC 的主要应用场景
- 如何实现基础的 WebRTC 应用
- 数据通道的使用方法和最佳实践
- 最佳实践和注意事项
希望这篇教程对您有所帮助!如果您有任何问题或建议,欢迎在评论区留言讨论。