引言:为什么 Rust 上的 WebSocket 是一对强大的组合
现代 Web 应用越来越需要即时数据传输:聊天、在线游戏、股票行情、通知。传统的 HTTP 及其请求-响应模型在此效率低下——它会带来额外的负载和延迟。WebSocket 协议应运而生,它在客户端和服务器之间建立持久的双向连接。
Rust 语言以其对性能、内存安全和零成本抽象的重视,非常适合构建高负载的实时系统。在本文中,我们将探讨如何使用 tokio-tungstenite 库在 Rust 中构建一个 WebSocket 服务器,并研究实时应用的关键模式。
WebSocket 基础与环境设置
什么是 WebSocket?
WebSocket 是一种基于 TCP 工作的协议。它允许服务器和客户端实时交换消息,无需重复的 HTTP 握手。连接以 HTTP 请求开始,并升级为 WebSocket,之后数据以帧的形式传输。
安装依赖项
为了我们的工作,我们需要 tokio(异步运行时)和 tokio-tungstenite(WebSocket 实现)。创建一个新项目:
cargo new websocket_democd websocket_demo将依赖项添加到 Cargo.toml:
[dependencies]tokio = { version = "1", features = ["full"] }tokio-tungstenite = "0.21"futures-util = "0.3"创建一个简单的 WebSocket 服务器
基本的回声服务器
让我们从一个简单的服务器开始,它接受连接并返回收到的消息(回声模式)。这是 WebSocket 的经典 "Hello, World"。
use tokio::net::TcpListener;use tokio_tungstenite::accept_async;use tokio_tungstenite::tungstenite::Message;use futures_util::StreamExt;
#[tokio::main]async fn main() { let addr = "127.0.0.1:8080"; let listener = TcpListener::bind(addr).await.expect("Failed to bind address"); println!("Server started on {}", addr);
while let Ok((stream, peer)) = listener.accept().await { println!("New connection: {}", peer); tokio::spawn(handle_connection(stream)); }}
async fn handle_connection(stream: tokio::net::TcpStream) { let ws_stream = accept_async(stream) .await .expect("Error during WebSocket handshake");
let (_, mut read) = ws_stream.split();
while let Some(msg) = read.next().await { match msg { Ok(Message::Text(text)) => { println!("Received: {}", text); // 这里可以发送响应,但为了简单起见我们跳过 } Ok(Message::Close(_)) => break, _ => {} } }}这段代码在 8080 端口上启动一个 TCP 服务器。对于每个新连接,会创建一个异步任务 handle_connection,它执行 WebSocket 握手并读取传入的消息。
添加消息发送功能
为了制作一个功能完整的回声服务器,我们需要将流拆分为读取和写入部分。我们使用 futures_util::StreamExt 和 SinkExt:
use futures_util::SinkExt;
async fn handle_connection(stream: tokio::net::TcpStream) { let ws_stream = accept_async(stream) .await .expect("Error during WebSocket handshake");
let (mut write, mut read) = ws_stream.split();
while let Some(msg) = read.next().await { match msg { Ok(Message::Text(text)) => { let echo = format!("Echo: {}", text); write.send(Message::Text(echo.into())).await.unwrap(); } Ok(Message::Close(_)) => break, _ => {} } }}