本文将带你一步步构建一个实时聊天应用,使用 WebSocket 进行实时通信, Rust 构建后端服务器, React 构建前端界面。WebSocket 提供了一种高效的双向通信通道,非常适合实时交互应用,比如聊天应用。
为什么选择 WebSocket?
WebSocket 是实时应用的理想选择,比如聊天服务、在线游戏、金融交易平台等等,这些应用都需要即时的数据交换。与依赖请求-响应循环的 REST API 不同,WebSocket 提供 全双工 通信,这意味着服务器和客户端可以在任何时候发送消息。
使用 REST API 时,我们通常需要依靠 轮询 ,即客户端频繁发送请求以检查更新。这会导致网络使用效率低下,并造成不必要的延迟。另一方面,WebSocket 保持一个开放的连接,允许 实时数据交换 ,无需不断发送请求,从而优化性能和响应能力。
第一部分:在 Rust 中设置 WebSocket
让我们从创建一个简单的 Rust WebSocket 回声服务器开始。我们将使用
tokio-tungstenite
,一个用于 Tokio 运行时的流行 WebSocket 库。
依赖项
首先,我们将把必要的依赖项添加到我们的
Cargo.toml
文件中:
[dependencies]
tokio = { version = "1", features = ["full"] }
tungstenite = "0.17"
tokio-tungstenite = "0.17"
futures-util = "0.3"
步骤 1:构建回声服务器
现在让我们深入代码,构建一个回声服务器,它将简单地返回它接收到的任何消息:
use futures_util::{StreamExt, SinkExt};
use tokio::net::TcpListener;
use tokio_tungstenite::tungstenite::Message;
use tokio_tungstenite::accept_async;
#[tokio::main]
asyncfnmain() {
let addr = "127.0.0.1:8080";
let listener = TcpListener::bind(&addr).await.expect("Failed to bind");
println!("WebSocket server running at {}", addr);
whileletOk((stream, _)) = listener.accept().await {
tokio::spawn(asyncmove {
let ws_stream = accept_async(stream)
.await
.expect("Error during the WebSocket handshake");
let (mut write,mut read) = ws_stream.split();
whileletSome(message) = read.next().await {
match message {
Ok(Message::Text(text)) => {
write.send(Message::Text(format!(" {}",text))).await.unwrap();
}
Ok(_) => {
println!("Received a non-text message");
}
Err(e) => {
println!("Error: {}", e);
}
}
};
});
}
}
代码解释
Tokio
: 我们使用 Tokio 的异步运行时,并使用
#[tokio::main]
宏来驱动我们的异步代码。
TcpListener : 它监听指定地址上的传入 TCP 连接。
WebSocket 握手
: 当客户端连接时,我们使用
accept_async
将连接升级到 WebSocket。
流拆分
: 我们将 WebSocket 流拆分为
read
(接收)和
write
(发送)部分,用于异步通信。
回声消息
: 服务器将接收到的任何文本消息回送,并在前面加上
>
。
测试回声服务器
为了测试服务器,我们可以使用 WebSocket 客户端,比如
wscat
:
% wscat -c ws://127.0.0.1:8080
Connected (press CTRL+C to quit)
> hello
< >hello
> Hey dude
< >Hey dude
在这个阶段,我们已经构建了一个基本的回声服务器。让我们通过添加广播功能来提升它。
步骤 2:添加广播功能
在聊天应用程序中,我们希望来自一个用户的消息广播到所有连接的用户,而不仅仅是回声给发送者。让我们修改我们的代码来实现这种行为,使用 Tokio 的 广播 通道。
use futures_util::{StreamExt, SinkExt};
use tokio::net::TcpListener;
use tokio_tungstenite::tungstenite::Message;
use tokio_tungstenite::accept_async;
use tokio::sync::broadcast;
#[tokio::main]
asyncfnmain() {
let addr = "127.0.0.1:8080";
let listener = TcpListener::bind(&addr).await.expect("Failed to bind");
let (tx, rx) = broadcast::channel::(10);
println!("WebSocket server running at {}", addr);
whileletOk((stream, _)) = listener.accept().await {
let tx = tx.clone();
letmut rx = rx.resubscribe();
tokio::spawn(asyncmove {
let ws_stream = accept_async(stream)
.await
.expect("Error during the WebSocket handshake");
let (mut write,mut read) = ws_stream.split();
loop {
tokio::select! {
received_message = rx.recv() => {
ifletOk(received_message) = received_message {
write.send(Message::Text(received_message)).await.unwrap();
}
}
read_message = read.next() => {
ifletSome(Ok(Message::Text(text))) = read_message {
tx.send(text).unwrap();
}
}
}
}
});
}
}
代码解释
广播通道
: 我们使用 Tokio 的
broadcast
通道将消息发送给所有连接的客户端。
tx
被克隆到每个新连接中,客户端使用
tx.subscribe()
来监听消息。
tokio::select! : 这个强大的宏允许我们同时等待多个异步事件。在我们的例子中,我们要么等待来自 WebSocket 的新消息,要么等待来自广播通道的消息。
测试广播功能
打开多个终端来查看广播是如何工作的:
终端 1:
% wscat -c ws://127.0.0.1:8080
Connected (press CTRL+C to quit)
< Hey man
< how is it going
> fine
< fine
> how are you mate?
< how are you mate?
>
终端 2:
% wscat -c ws://127.0.0.1:8080
Connected (press CTRL+C to quit)
> Hey man
< Hey man
> how is it going
< how is it going
< fine
< how are you mate?
现在,来自一个终端的消息被广播到所有其他连接的客户端,为多用户聊天应用程序奠定了基础。
第二部分:React 前端
我们将分两步构建我们的前端。首先,我们将构建一个简单的 React UI,然后添加 WebSocket 代码。
步骤 1:构建一个简单的 UI
在这一步中,我们将只关注用户界面 (UI)。我们将创建一个基本的聊天布局,包含用户名和消息的输入框,以及一个显示聊天消息的区域。
import React, { useState } from'react';
functionApp() {
const [messages, setMessages] = useState([]);
const [input, setInput] = useState('');
const [username, setUsername] = useState('');
const handleSendMessage = () => {
const message = `${username}: ${input}`;
setMessages((prevMessages) => [...prevMessages, message]);
setInput('');
};
return (
Simple Chat UI
setUsername(e.target.value)}
style={{ marginBottom: '10px' }}
/>
setInput(e.target.value)}
style={{ width: '70%', marginRight: '10px' }}
/>
Messages
{messages.map((msg, index) => (
{msg}
))}
);
}
exportdefault App;
代码解释
这段代码只是创建了一个基本的表单,用户可以在其中输入用户名和消息。
messages
状态将保存消息,并在点击「发送」按钮时将它们显示在屏幕上。
测试简单的聊天 UI
步骤 2:添加 WebSocket
接下来,我们将连接 WebSocket 到聊天 UI,并让它通过 WebSocket 发送和接收消息。我们将使用库:
react-use-websocket
import React, { useState } from'react';
import useWebSocket from'react-use-websocket';
functionApp() {
const [socketUrl] = useState('ws://127.0.0.1:8080');
const [messages, setMessages] = useState([]);
const [input, setInput] = useState('');
const [username, setUsername] = useState('');
const { sendMessage, lastMessage } = useWebSocket(socketUrl);
React.useEffect(() => {
if (lastMessage !== null) {
setMessages((prevMessages) => [...prevMessages, lastMessage.data]);
}
}, [lastMessage]);
const handleSendMessage = () => {
if (input && username) {
const message = `${username}: ${input}`;
sendMessage(message);
setInput('');
}
};
return (
WebSocket Chat
setUsername(e.target.value)}
style={{ marginBottom: '10px' }}
/>
setInput(e.target.value)}
style={{ width: '70%', marginRight: '10px' }}
/>
Messages
{messages.map((msg, index) => (
{msg}
))}
);
}
exportdefault App;
代码解释
我们添加了使用
useWebSocket
钩子的 WebSocket 集成。当收到新消息时,它会被添加到
messages
数组中,当你发送消息时,它会通过 WebSocket 传输。
测试 WebSocket 聊天
不错。我们可以在两个不同的聊天窗口之间进行通信。
我们介绍了如何使用 Rust 设置 WebSocket,以及如何制作一个简单的 React 前端。它演示了使用 Tokio 来维护活动连接。一个潜在的改进是向 React 前端添加验证,以管理 WebSocket 不可用时的场景。
文章精选