在本文中,我们将从零开始,使用 Rust 和 WebSockets 创建一个 WebRTC 视频聊天应用程序。在此过程中,我们将解释重要的 WebRTC 概念,并将 Rust 服务器代码和 HTML 客户端代码分解成易于理解的代码块。
什么是 WebRTC?
WebRTC (Web Real-Time Communication)是一种无需插件即可在浏览器之间实现直接点对点通信的技术。它支持视频、语音和数据共享,是视频通话等实时通信应用的理想选择。
WebRTC 包含几个关键组件,有助于建立点对点连接:
WebRTC 的关键概念
- ICE(交互式连接建立): ICE 是 WebRTC 用来在对等方之间寻找最佳路径以建立连接的框架。它有助于确定最佳候选连接(IP 地址和端口)。
- STUN(用于 NAT 的会话穿越实用程序): STUN 是 WebRTC 使用的一种协议,用于发现 NAT(网络地址转换)路由器后客户端的公共 IP 地址和端口。它允许对等方找到其面向公众的 IP 地址。
- TURN(使用 NAT 附近的中继进行遍历): TURN 是 WebRTC 使用的一种协议,是点对点直接通信失败时的备用协议。它通过 TURN 服务器中继流量,即使双方都在限制性的 NAT 后面,也能让对等方进行连接。
- SDP(会话描述协议): SDP 是一种用于描述多媒体会话的格式。它包含两个对等方之间建立媒体会话所需的编解码器、格式和其他参数信息。WebRTC 在 offer-answer 过程中使用 SDP。
WebRTC 点对点通信
WebRTC 通信采用 offer-answer 模式启动:
- Offer:第一个对等方(peer)通过信令(通常通过 WebSockets 或 HTTP 完成)向第二个对等方发送 SDP 要约(对其媒体功能的描述)。
- Answer: 第二个对等方用包含其媒体功能的 SDP 应答进行回应。
- ICE 候选(ICE candidates): 两个对等方交换 ICE 候选路由(可能的网络路由),用于连接它们。

步骤 1:设置 Rust 项目
让我们使用 cargo 命令从头开始创建一个新的 Rust 项目。
1、创建一个新的 Rust 项目
打开终端,运行以下命令创建一个新的 Rust 项目:
cargo new webrtc_video_chat --bin
cd webrtc_video_chat
这将创建一个名为 webrtc_video_chat 的新目录,其中包含必要的 Rust 项目结构。
2、添加依赖项
我们需要为 WebSocket 服务器添加依赖项,打开 Cargo.toml,添加以下依赖项:
[dependencies]
warp = "0.3"
tokio = { version = "1", features = ["full"] }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0"
uuid = "1.0"
futures-util = "0.3"
这些依赖项包括
- 用于 HTTP/WebSocket 服务器的
warp
。 - 用于异步运行时的
tokio
。 - 用于 JSON 序列化/解序列化的
serde
和serde_json
。 - 用于生成唯一用户 ID 的
uuid
。 - 用于处理异步流的
futures-util
。
3、安装 WebSocket 客户端依赖项
Rust 的 WebSocket 客户端-服务器模型非常适合我们的信令服务器。我们将使用 warp 来处理 WebSocket 和异步操作。
步骤 2:创建 WebSocket 服务器(Rust 代码)
现在我们来构建 WebSocket 服务器,以处理 WebRTC 客户端之间的通信。我们的目标是构建一个用于交换 SDP 和 ICE 候选者的信令服务器。
1、定义信令结构
创建一个结构来表示在对等方之间发送的信令消息(offer、answer、ICE candidates):
use serde::{Serialize, Deserialize};
#[derive(Serialize, Deserialize)]
struct Signal {
event: String,
data: String,
target: Option<String>,
}
该结构有助于序列化和反序列化对等体之间的消息。event
表示消息的类型(offer、answer、ice),data
包含实际的 SDP 或 ICE 候选,target
是直接消息的目标对等体。
2、WebSocket 服务器设置
main.rs
文件将包含 WebSocket 服务器,用于监听客户端连接。我们将其分成几个部分。
设置 WebSocket 服务器和路由
use std::sync::{Arc, Mutex};
use warp::ws::{Message, WebSocket};
use warp::Filter;
use tokio::sync::mpsc;
use std::collections::HashMap;
use tokio_stream::wrappers::UnboundedReceiverStream;
use futures_util::stream::StreamExt;
type Clients = Arc<Mutex<HashMap<String, mpsc::UnboundedSender<Message>>>>;
#[tokio::main]
async fn main() {
let clients: Clients = Arc::new(Mutex::new(HashMap::new()));
let ws_route = warp::path("ws")
.and(warp::ws())
.and(with_clients(clients.clone()))
.map(move |ws: warp::ws::Ws, clients| {
ws.on_upgrade(move |socket| handle_connection(socket, clients))
});
let routes = ws_route;
warp::serve(routes).run(([127, 0, 0, 1], 8080)).await;
}
fn with_clients(clients: Clients) -> impl Filter<Extract = (Clients,), Error = std::convert::Infallible> + Clone {
warp::any().map(move || clients.clone())
}
- 将
Clients
类型定义为共享Mutex<HashMap>
,它包含所有连接的 WebSocket 客户端。 - 使用
warp::ws()
处理 WebSocket 升级并定义ws_route
用于接受/ws
上的WebSocket 连接。
处理 WebSocket 连接
async fn handle_connection(ws: WebSocket, clients: Clients) {
let (user_ws_tx, mut user_ws_rx) = ws.split();
let (tx, rx) = mpsc::unbounded_channel();
tokio::task::spawn(async move {
let rx_stream = UnboundedReceiverStream::new(rx);
let _ = rx_stream.map(Ok).forward(user_ws_tx).await;
});
let user_id = uuid::Uuid::new_v4().to_string();
clients.lock().unwrap().insert(user_id.clone(), tx);
while let Some(Ok(message)) = user_ws_rx.next().await {
if let Ok(msg_text) = message.to_str() {
if let Ok(signal) = serde_json::from_str::<Signal>(msg_text) {
let clients_locked = clients.lock().unwrap();
if let Some(target) = &signal.target {
if let Some(target_tx) = clients_locked.get(target) {
let _ = target_tx.send(Message::text(msg_text));
}
} else {
for (id, tx) in clients_locked.iter() {
let _ = tx.send(Message::text(msg_text));
}
}
}
}
}
clients.lock().unwrap().remove(&user_id);
}
在此函数中:
- 我们通过将连接分成发送流和接收流来处理 WebSocket 通信。
- 我们将每个客户端的发件人存储在
clients
地图中。 - 我们处理传入的消息,将其解析为
Signal
对象,然后将其发送给适当的接收者(或将其广播给所有客户端)。
步骤 3:创建 WebRTC 客户端 (HTML + JavaScript)
HTML/JavaScript 中的客户端代码将建立 WebRTC 连接并与 Rust WebSocket 信令服务器交互。
1、HTML结构
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>WebRTC Video Chat</title>
<style>
video {
width: 400px;
height: 300px;
margin: 10px;
border: 1px solid black;
background: #333;
}
</style>
</head>
<body>
<h1>WebRTC Video Chat</h1>
<video id="localVideo" autoplay playsinline></video>
<video id="remoteVideo" autoplay playsinline></video>
<button id="startCall">Start Call</button>
<button id="endCall">End Call</button>
<script src="client.js"></script>
</body>
</html>
2、JavaScript WebRTC 代码
let localStream;
let peerConnection;
const ws = new WebSocket("ws://127.0.0.1:8080/ws");
ws.onmessage = (message) => {
const signal = JSON.parse(message.data);
if (signal.event === "offer") {
handleOffer(signal.data);
} else if (signal.event === "answer") {
handleAnswer(signal.data);
} else if (signal.event === "ice") {
handleIceCandidate(signal.data);
}
};
async function startCall() {
localStream = await navigator.mediaDevices.getUserMedia({ video: true, audio: true });
document.getElementById("localVideo").srcObject = localStream;
peerConnection = new RTCPeerConnection();
peerConnection.onicecandidate = (event) => {
if (event.candidate) {
ws.send(JSON.stringify({
event: "ice",
data: JSON.stringify(event.candidate)
}));
}
};
peerConnection.ontrack = (event) => {
const remoteVideo = document.getElementById("remoteVideo");
remoteVideo.srcObject = event.streams[0];
};
localStream.getTracks().forEach(track => {
peerConnection.addTrack(track, localStream);
});
const offer = await peerConnection.createOffer();
await peerConnection.setLocalDescription(offer);
ws.send(JSON.stringify({
event: "offer",
data: JSON.stringify(offer)
}));
}
async function handleOffer(offer) {
await peerConnection.setRemoteDescription(new RTCSessionDescription(JSON.parse(offer)));
const answer = await peerConnection.createAnswer();
await peerConnection.setLocalDescription(answer);
ws.send(JSON.stringify({
event: "answer",
data: JSON.stringify(answer)
}));
}
async function handleAnswer(answer) {
await peerConnection.setRemoteDescription(new RTCSessionDescription(JSON.parse(answer)));
}
function handleIceCandidate(candidate) {
peerConnection.addIceCandidate(new RTCIceCandidate(JSON.parse(candidate)));
}
document.getElementById("startCall").addEventListener("click", startCall);
document.getElementById("endCall").addEventListener("click", () => {
if (peerConnection) {
peerConnection.close();
peerConnection = null;
}
if (localStream) {
localStream.getTracks().forEach(track => track.stop());
localStream = null;
}
});
结论
在本指南中,我们使用 Rust 制作信令,使用 HTML/JavaScript 制作前端,构建了一个 WebRTC 视频聊天应用程序。我们解释了 WebRTC 的关键概念,如 ICE、STUN、TURN、SDP,以及如何建立点对点通信。然后,我们将 Rust 代码和 HTML/JavaScript 代码划分为易于管理的部分,帮助您理解流程的每个部分。
本示例为基于 WebRTC 的简单应用奠定了基础,您还可以对其进行扩展,添加群组调用、安全性和错误处理等功能。
详细代码:https://github.com/Jenifer-TheCoder/Video-Chat-App-With-WebRTC.git
作者:Jenifer@CodingLover
本文来自作者投稿,版权归原作者所有。如需转载,请注明出处:https://www.nxrte.com/jishu/webrtc/56697.html