- 支持试读
Axum与Websocket
我们将通过几个案例由浅入深地带你掌握Websocket及其应用场景 - 支持试读
简单ECHO服务
本章我们将使用 AXUM 和 Websocket 实现一个简单的 Echo 服务。 - 支持试读
并发读写的ECHO服务
本章我们将实现并发读写的 ECHO 服务 - 支持试读
Javscript实现WebSocket客户端
本章我们将给我们的 ECHO 服务实现一个客户端。你要明确的是,很多语言都能写 WebSocket 客户端,包括 Rust 在内。但基于我们当前的环境,我们使用 JavaScript(TypeScript) 配合 React(Next.js)来实现。 用户在线检测
本章我们将实现用户在线检测功能:用户登录之后,前端通过 WebSocket 来检测 JWT Token 是否依然有效。
Javscript实现WebSocket客户端
本章我们将给我们的 ECHO 服务实现一个客户端。你要明确的是,很多语言都能写 WebSocket 客户端,包括 Rust 在内。但基于我们当前的环境,我们使用 JavaScript(TypeScript) 配合 React(Next.js)来实现。
现代浏览器提供了对 WebSocket 的原生支持,一下是常用的API。
设置事件监听函数
有两种方法,设置监听函数:
- 使用
addEventListener(事件名, 监听函数)
,比如:
ws.addEventListener('message', (ev)=> {
console.log(ev);
});
ws.onmessage = (ev)=>{
console.log(ev);
};
封装成 React 组件
"use client";
import Box from "@/components/Box";
import MessageItem from "@/components/MessageItem";
import { newInfoMsg, newMsg } from "@/utils/message";
import React, { useCallback, useEffect, useState } from "react";
export default function HomeView() {
const [isConnected, setConnected] = useState(false);
const [url, setUrl] = useState("ws://127.0.0.1:56789/ws");
const [msgs, setMsgs] = useState<Message[]>([]);
const [ws, setWs] = useState<WebSocket | undefined>(undefined);
const [lastMsg, setLastMsg] = useState<Message | undefined>(undefined);
const [msgForSend, setMsgForSend] = useState("");
const [isKeep, setKeep] = useState(true);
useEffect(() => {
if (lastMsg) {
setMsgs((prev) => prev?.concat({ ...lastMsg }));
}
}, [lastMsg]);
const connectHandler = () => {
const _ws = new WebSocket(url);
_ws.addEventListener("message", (ev: MessageEvent<any>) => {
setLastMsg(newMsg("RECV", ev.data as string));
});
_ws.addEventListener("open", () => {
setLastMsg(newInfoMsg("已连接"));
setConnected(true);
});
_ws.addEventListener("close", () => {
setLastMsg(newInfoMsg("已断开连接"));
setConnected(false);
});
setWs(_ws);
};
const sendHandler = (e: React.MouseEvent<HTMLButtonElement, MouseEvent>) => {
if (msgForSend) {
setLastMsg(newMsg("SEND", msgForSend));
ws?.send(msgForSend);
if (!isKeep) {
setMsgForSend("");
}
}
};
return (
<>
<Box>
<h1 className="text-lg font-bold">ECHO</h1>
</Box>
<Box>
<div className="grid grid-cols-12 items-center">
<div className="col-span-1 flex flex-col justify-start items-center">
{isConnected ? (
<>
{/* 已连接 */}
<span className="relative flex h-3 w-3">
<span className="animate-ping absolute inline-flex h-full w-full rounded-full bg-green-400 opacity-75"></span>
<span className="relative inline-flex rounded-full h-3 w-3 bg-green-500"></span>
</span>
</>
) : (
<>
{/* 未连接 */}
<span className="relative flex h-3 w-3">
<span className="relative inline-flex rounded-full h-3 w-3 bg-red-500"></span>
</span>
</>
)}
</div>
<div className="col-span-9">
<input
className="border block w-full rounded px-3 py-1"
readOnly={isConnected}
value={url}
onChange={(e) => {
useCallback(() => setUrl(e.target.value), []);
}}
/>
</div>
<div className="col-span-2 flex flex-col justify-start items-center">
{isConnected ? (
<button
className="border bg-amber-600 text-white px-3 py-1 rounded"
onClick={(e) => ws?.close()}
>
断开
</button>
) : (
<button
className="border bg-cyan-600 text-white px-3 py-1 rounded"
onClick={connectHandler}
>
连接
</button>
)}
</div>
</div>
</Box>
<Box>
<ul className="h-[30rem] overflow-y-auto">
{msgs.map((m) => (
<MessageItem key={m.id} msg={m} />
))}
</ul>
</Box>
<Box>
<div className="flex flex-col gap-y-2">
<div>
<textarea
className="border block w-full p-4 rounded"
rows={1}
value={msgForSend}
onChange={(e) => {
setMsgForSend(e.target.value.trim());
}}
></textarea>
</div>
<div className="flex justify-end items-center gap-x-2">
<label>
<input
type="checkbox"
checked={isKeep}
onChange={(e) => setKeep(e.target.checked)}
/>{" "}
保留上次发送的内容
</label>
<button
className="border bg-blue-600 text-white px-3 py-2 rounded disabled:bg-blue-600/50"
disabled={!(isConnected && msgForSend)}
onClick={sendHandler}
>
发送
</button>
</div>
</div>
</Box>
<footer className="text-center text-xs text-gray-400">
© 2024{" "}
<a href="https://axum.rs" target="_blank">
AXUM.RS
</a>
</footer>
</>
);
}
代码比较简单,这里对几个需要注意的地方进行讲解,其它部分请自行阅读。
"use client";
:由于 WebSocket
(以及其它交互)需要浏览器环境,所以,需要使用客户端组件
注意,我们并不是直接修改 msgs
数组,而是通过 lastMsg
的变动来修改。如果直接修改 msgs
数组的话,在 message
等事件监听函数中,会清空历史信息:
useEffect(() => {
if (lastMsg) {
setMsgs((prev) => prev?.concat({ ...lastMsg }));
}
}, [lastMsg]);
我们是在点击“连接”时,才创建WebSocket连接
const connectHandler = () => {
const _ws = new WebSocket(url);
_ws.addEventListener("message", (ev: MessageEvent<any>) => {
setLastMsg(newMsg("RECV", ev.data as string));
});
_ws.addEventListener("open", () => {
setLastMsg(newInfoMsg("已连接"));
setConnected(true);
});
_ws.addEventListener("close", () => {
setLastMsg(newInfoMsg("已断开连接"));
setConnected(false);
});
setWs(_ws);
};
本章代码位于03/websocket-js-client
分支。