使用 Rust 和 WebSockets 构建 WebRTC 视频聊天应用程序

在本文中,我们将从零开始,使用 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 候选路由(可能的网络路由),用于连接它们。
使用 Rust 和 WebSockets 构建 WebRTC 视频聊天应用程序

步骤 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 序列化/解序列化的 serdeserde_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

(0)

相关推荐

发表回复

登录后才能评论