ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Refine 实战:用 Node.js 与 React 构建基于 WebSocket 的实时协作应用

Refine 实战:用 Node.js 与 React 构建基于 WebSocket 的实时协作应用 Refine 实战用 Node.js 与 React 构建基于 WebSocket 的实时协作应用【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine导读在构建管理后台、协作工具等现代 Web 应用时实时正在从加分项变成刚需订单状态变化要立刻呈现、多人协同编辑要秒级同步、监控面板要持续刷新。传统 HTTP 的请求-响应模型在面对这类全双工、双向、低延迟通信时力不从心而WebSocket提供了一条基于单条长连接 TCP 的全双工实时通道成为业界主流方案。本文将完整剖析 WebSocket 协议原理与 HTTP 轮询、HTTP 流、SSE 等传统方案的局限并以一个多人实时协作文本编辑器为例手把手演示如何用 Node.jsws库搭建 WebSocket 服务端、用 Reactreact-use-websocket搭建客户端同时深入讲解 WSS 加密、JWT 认证、防跨站劫持等安全实践与 STOMP/MQTT/WAMP 子协议选型。读完本文你将掌握一套可复制的 WebSocket 实时通信落地能力并了解 Refine 框架中 Live Provider 对实时数据订阅的封装思路。为什么需要 WebSocketHTTP 是一个遵循请求-响应、单向通信模式的应用层协议绝大多数 Web 应用都基于它构建。但当业务进入即时更新 双向通信场景如即时消息、协同办公、实时数据看板时HTTP 的原生模型就会出现明显短板。开发者历史上用以下变体来硬凑实时能力但各有缺陷。HTTP 轮询HTTP Polling客户端用setInterval或setTimeout按预设间隔向服务器发请求服务器在出现事件、更新或超时前一直挂起请求长轮询 Long Polling有更新后再响应客户端收到响应后继续发起新一轮请求——这个请求-响应-再请求的循环被称为HTTP Polling lifecycle客户端向服务器发送请求并等待响应服务器在出现事件/更新/超时之前挂起该请求有更新时服务器向客户端发送响应客户端收到响应后发起新请求循环继续。HTTP 轮询的固有缺陷包括缓存问题、超时问题、头部开销header overhead和网络延迟。每一次轮询都重复携带大量 HTTP 头部且实时性受轮询间隔限制本质上仍不是推送。HTTP 流HTTP StreamHTTP 轮询中服务器每次响应后都会关闭连接通道客户端每次都要重建连接这是网络延迟的主要来源。HTTP 流的改进在于初始请求保持打开即使服务器已返回数据也不关闭连接通道这样服务器可以在有新数据时持续向客户端推送响应。它降低了延迟、实现了近实时更新但对客户端资源占用更高且客户端必须先发起请求才能建立连接这一约束仍然存在。服务器推送事件SSESSE 是一种标准化的 HTTP 流式协议内置浏览器 API提供服务器到客户端单向的实时/近实时数据流。它非常适合无需客户端向服务器发数据的场景例如社交媒体信息流更新、实时仪表盘。需要注意Firefox 不支持在 Service Worker 中使用 SSE。方案通信方向连接模型典型短板HTTP Polling单向客户端主动短连接、反复重建缓存/超时/头部开销/延迟HTTP Stream单向服务器持续推长连接但需客户端先发起资源占用高、仍有延迟SSE单向服务器→客户端HTTP 长连接无法从客户端回传数据WebSocket双向全双工单条长连接 TCP需处理握手与断线重连WebSocket 是什么WebSocket 是一种数据交换协议它让浏览器客户端与服务器之间能够通过一条长生命周期的 TCP 连接进行双向、实时、全双工、交互式通信。借助 WebSocket API客户端无需轮询即可向服务器发送请求并接收事件驱动的响应。现代主流浏览器均支持 WebSocket API。WebSocket 连接建立遵循以下顺序请求握手Request Handshake客户端向服务器发送初始请求即 WebSocket 握手校验请求Validate Request服务器校验请求合法性校验通过后建立连接通信Communication连接建立后服务器与客户端即可双向传输数据。之所以推荐 WebSocket它提供低延迟、双向、实时的通信通道协议本身轻量、可扩展、支持全双工且被绝大多数现代浏览器支持是构建双向实时通信应用的更优解。实战Node.js React 构建实时协作文本编辑器为了直观演示 WebSocket 的能力本文采用一个多人实时协作文本编辑器作为实例多人可同时编辑同一份文本任何一个人的修改会立刻同步给其他所有用户。该示例原型WebSockets-Demo仓库的代码结构如下Server 目录Node.js WebSocket 服务器负责文本编辑器核心逻辑Client 目录React 应用通过 WebSocket 与服务器通信承载应用的实时交互。依次在服务端与客户端目录安装依赖并启动后可以在两个浏览器窗口同时打开应用并协同编辑文本直观体验 WebSocket 的实时能力。第一步搭建 WebSocket 服务端与握手服务端同时开启 HTTP 服务器和 WebSocket 服务器两者共用一个端口。HTTP 服务器就绪后WebSocket 服务器挂载到该端口上——这正是 WebSocket 连接建立的起点如同客户端与服务器之间的虚拟握手// 引入所需模块 const { WebSocket, WebSocketServer } require(ws); const http require(http); const uuidv4 require(uuid).v4; // 创建 HTTP 服务器与 WebSocket 服务器 const server http.createServer(); const wsServer new WebSocketServer({ server }); const port 8000; // 启动 WebSocket 服务器 server.listen(port, () { console.log(WebSocket server is running on port ${port}); });这里的关键点new WebSocketServer({ server })让ws库复用现有 HTTP 服务器客户端后续通过ws://127.0.0.1:8000发起连接时先完成 HTTP 升级握手再升级为 WebSocket 长连接。第二步管理客户端连接任何实时应用的核心都是连接管理。示例中每个客户端都会获得一个由uuid包生成的唯一标识所有活跃连接以对象形式被服务器追踪// 维护活跃连接与用户 const clients {}; const users {}; let editorContent null; let userActivity []; // 处理新客户端连接 wsServer.on(connection, function handleNewConnection(connection) { const userId uuidv4(); console.log(Received a new connection); clients[userId] connection; console.log(${userId} connected.); connection.on(message, (message) processReceivedMessage(message, userId), ); connection.on(close, () handleClientDisconnection(userId)); });每次新连接到来用uuidv4()生成唯一userIduserId作为clients对象的键保存连接实例通过日志输出标记新连接同时监听message与close事件分别路由到消息处理与断连处理函数。第三步消息解析与广播客户端发来的消息由processReceivedMessage统一处理先解析 JSON、判定消息类型再按类型执行对应动作如用户加入、内容编辑最后将结果广播给所有已连接客户端// 处理客户端发来的消息 function processReceivedMessage(message, userId) { const dataFromClient JSON.parse(message.toString()); const json { type: dataFromClient.type }; if (dataFromClient.type eventTypes.USER_EVENT) { users[userId] dataFromClient; userActivity.push(${dataFromClient.username} joined to collaborate); json.data { users, userActivity }; } else if (dataFromClient.type eventTypes.CONTENT_CHANGE) { editorContent dataFromClient.content; json.data { editorContent, userActivity }; } sendMessageToAllClients(json); }这种JSON 消息 类型字段type 广播的模式是 WebSocket 应用最基础也最实用的消息协议设计客户端与服务端约定好消息结构如userevent表示用户事件、contentchange表示内容变更服务端据此分发处理。第四步客户端建立连接客户端使用react-use-websocket包提供的useWebSocketHook让 React 函数组件可以方便地控制 WebSocket 连接。理解onopen、onclose、onmessage这几个事件类型就能快速上手 WebSocket 客户端开发import React, { useEffect, useState } from react; import { Navbar, NavbarBrand } from react-bootstrap; import useWebSocket, { ReadyState } from react-use-websocket; import { Tooltip as ReactTooltip } from react-tooltip; import { DefaultEditor } from react-simple-wysiwyg; import Avatar from react-avatar; import ./App.css; const WS_URL ws://127.0.0.1:8000; function isUserEvent(message) { const parsedMessage JSON.parse(message.data); return parsedMessage.type userevent; } function isDocumentEvent(message) { const parsedMessage JSON.parse(message.data); return parsedMessage.type contentchange; } function App() { const [username, setUsername] useState(); const { sendJsonMessage, readyState } useWebSocket(WS_URL, { onOpen: () { console.log(WebSocket connection established.); }, share: true, filter: () false, retryOnError: true, shouldReconnect: () true, }); // 组件其余逻辑 }几个值得注意的配置项share: true表示多个组件共享同一个 WebSocket 连接filter用于按消息类型过滤是否触发组件更新retryOnError与shouldReconnect提供错误重试与自动重连策略。第五步加入会话并实时传输消息连接建立后用户输入用户名并点击加入时客户端发送userevent消息通知所有已连接用户有人加入协作function App() { const [username, setUsername] useState(); const { sendJsonMessage, readyState } useWebSocket(WS_URL, { onOpen: () { console.log(WebSocket connection established.); }, share: true, filter: () false, retryOnError: true, shouldReconnect: () true, }); useEffect(() { if (username readyState ReadyState.OPEN) { sendJsonMessage({ username, type: userevent, }); } }, [username, sendJsonMessage, readyState]); return ( Navbar classNamenavbar colorlight light NavbarBrand href/Real-time Collaborative Text Editor/NavbarBrand /Navbar div classNamecontainer-fluid {username ? EditorSection / : LoginSection onLogin{setUsername} /} /div / ); }编辑器主体由Document组件承载。它订阅 WebSocket 消息流通过lastJsonMessage读取服务端广播的最新内容并在用户编辑时通过sendJsonMessage发送contentchange消息function Document() { const { lastJsonMessage, sendJsonMessage } useWebSocket(WS_URL, { share: true, filter: isDocumentEvent, }); let html lastJsonMessage?.data.editorContent || ; function handleHtmlChange(e) { sendJsonMessage({ type: contentchange, content: e.target.value, }); } return DefaultEditor value{html} onChange{handleHtmlChange} /; }filter: isDocumentEvent让该组件只响应contentchange类型消息避免无关消息触发重渲染。这样每个用户的编辑操作会实时广播到所有连接者实现真正的协同编辑。第六步处理断连管理断连与管理连接同样重要。当用户关闭浏览器或刷新页面时WebSocket 触发close事件服务器需要清理该连接并向其余用户广播某某已离开// 处理客户端断连 function handleClientDisconnection(userId) { console.log(${userId} disconnected.); const json { type: eventTypes.USER_EVENT }; const username users[userId]?.username || userId; userActivity.push(${username} left the editor); json.data { users, userActivity }; delete clients[userId]; delete users[userId]; sendMessageToAllClients(json); }这里做了三件关键的事记录离开活动、从clients/users对象中删除该用户防止内存泄漏与脏数据、向剩余用户广播更新。WebSocket 安全实践WebSocket 连接是长寿命的一旦建立可能长期保持因此安全设计比普通 HTTP 请求更需要谨慎。以下实践来自实际项目经验建议全部落实。使用 WSS 安全连接生产环境必须使用wss://安全连接相当于 HTTPS 之于 HTTP。WSS 会对传输数据进行加密防止被中间人攻击窃取或篡改// 客户端使用 wss:// const ws new WebSocket(wss://example.com/socket);// 服务端基于 HTTPS 服务器创建 WSS const https require(https); const WebSocket require(ws); const server https.createServer({ /* SSL options */ }); const wss new WebSocket.Server({ server }); server.listen(8080, () { console.log(Secure WebSocket server is running on port 8080); });落实认证与授权与 HTTP 请求不同WebSocket 连接长期打开必须确保只有已认证用户才能建立连接。常见做法是在握手阶段校验 JWT 等令牌随后再叠加授权检查确保用户只能访问被允许的数据// 客户端在连接 URL 中携带 token const token your_jwt_token; const ws new WebSocket(wss://example.com/socket?token${token});// 服务端握手时校验 token const url require(url); const jwt require(jsonwebtoken); wss.on(connection, (ws, req) { const queryParams url.parse(req.url, true).query; const token queryParams.token; try { const decoded jwt.verify(token, your_secret_key); ws.user decoded.user; // 将用户信息挂到 WebSocket 实例上 console.log(User authenticated:, ws.user); } catch (err) { ws.close(1008, Unauthorized); // 以 Unauthorized 状态码关闭连接 } });防御跨站 WebSocket 劫持**跨站 WebSocket 劫持Cross-Site WebSocket Hijacking**是指攻击者诱导用户浏览器借助用户的凭据向服务器发起 WebSocket 连接。缓解手段是不仅握手时校验认证令牌每条 WebSocket 消息也要在服务端做校验// 客户端消息中携带 token ws.onmessage (event) { const message JSON.parse(event.data); if (message.token ! validToken) { ws.close(1008, Unauthorized); } else { // 处理消息 } };// 服务端逐条消息校验 token ws.on(message, (message) { const data JSON.parse(message); try { const decoded jwt.verify(data.token, your_secret_key); // 继续处理消息 } catch (err) { ws.close(1008, Unauthorized); } });限流与消息校验WebSocket 允许实时高频交换数据因此需要限流以防止客户端恶意刷消息淹没服务器同时要对消息内容做结构校验防范注入类攻击const rateLimit require(express-rate-limit); const limiter rateLimit({ windowMs: 60 * 1000, // 1 分钟窗口 max: 100, // 每个 IP 在 windowMs 内最多 100 次请求 }); app.use(/socket, limiter); wss.on(connection, (ws) { ws.on(message, (message) { try { const data JSON.parse(message); // 校验消息结构 if (typeof data ! object || !data.action) { throw new Error(Invalid message format); } // 处理消息 } catch (err) { ws.send(JSON.stringify({ error: Invalid message })); } }); });优雅关闭连接在高并发、大用户量的应用中优雅关闭连接可以避免资源泄漏并缩小攻击面——只在确实不再需要时关闭连接并在进程退出时统一清理ws.on(close, () { console.log(Connection closed); // 清理资源例如从活跃用户列表中移除 }); process.on(SIGINT, () { wss.clients.forEach((client) { client.close(1000, Server shutting down); }); server.close(() { console.log(Server shut down gracefully); process.exit(0); }); });补充WebSocket 协议与子协议协议本质WebSocket 是面向全双工、单条 TCP 连接的协议与 HTTP按需开连接不同WebSocket 在客户端与服务器之间始终保持一条打开的连接双方持续通信。这对聊天应用、实时通知、共享协作工具等场景非常有利。子协议Subprotocol是什么子协议是 WebSocket 协议的扩展用于约定连接内部消息的具体格式与结构。客户端与服务器在握手时协商选定一种子协议从而保证双方能正确理解和处理消息。可以把 WebSocket 比作通信的媒介而子协议是双方约定使用的语言。常见子协议STOMPSimple Text Oriented Messaging Protocol基于文本的简单消息协议易于理解和快速实现主要用于消息类应用MQTTMessage Queuing Telemetry Transport轻量级消息协议专为低带宽、高延迟网络设计是 IoT 场景最常用的协议WAMPWeb Application Messaging Protocol支持 RPC远程过程调用与发布/订阅通信模式适合消息结构更复杂的应用。在连接中指定子协议客户端在创建连接时传入子协议数组服务器在握手后通过ws.protocol获知双方选定的子协议// 客户端指定希望使用的子协议 const ws new WebSocket(wss://example.com/socket, [mqtt]); ws.onopen () { console.log(WebSocket connection established with MQTT subprotocol.); }; ws.onmessage (message) { // 按 MQTT 协议处理消息 console.log(Received message:, message.data); };// 服务端Node.js ws 库 const WebSocket require(ws); const wss new WebSocket.Server({ port: 8080 }); wss.on(connection, (ws, req) { const subprotocol ws.protocol; console.log(Client connected using subprotocol: ${subprotocol}); ws.on(message, (message) { // 按选定子协议处理消息 console.log(Received:, message); }); ws.send(Welcome to the WebSocket server!); });自定义子协议当标准子协议无法满足业务需求时可以定义自己的子协议与消息格式客户端与服务器按约定结构解析即可// 自定义子协议myCustomProtocol // 消息采用特定结构的 JSON 对象 const ws new WebSocket(wss://example.com/socket, [myCustomProtocol]); ws.onmessage (message) { const data JSON.parse(message.data); if (data.type greeting) { console.log(Received greeting:, data.content); } };服务端同样按该自定义结构解析消息保证应用逻辑正确解读。子协议选型原则STOMP 适合需要轻量、可读协议的即时消息应用MQTT 适合带宽受限的 IoT 设备WAMP 适合基于结构化 RPC 或发布/订阅模式的应用。进阶在 Refine 中把 WebSocket 能力抽象为 Live Provider回到本文所在仓库Refine框架将 WebSocket 这类实时通道进一步抽象为Live Provider一个包含subscribe、unsubscribe和可选publish方法的对象让 React 管理后台无需关心底层是 WebSocket、SSE 还是第三方推送服务。相关文档见 Realtime 指南 与 Live Provider 指南。const liveProvider { subscribe: ({ channel, params: { ids }, types, callback, meta }) any, unsubscribe: (subscription) void, publish?: ({ channel, type, payload, date, meta }) void, };在Refine组件中传入liveProvider并设置options{{ liveMode: auto }}后useList、useOne、useMany等 Hook 会在对应资源数据变化时自动失效并重新请求开发者无需手动处理刷新逻辑liveMode也可在单个 Hook 上配置manual模式只触发onLiveEvent回调、off关闭实时且 Hook 级配置优先于全局配置。内置的 Ably、Supabase、Appwrite、Hasura 集成开箱即用其中 Ably 实现 展示了一个完整的 Live Providersubscribe通过types.includes(*) || types.includes(message.data.type)做事件类型过滤并针对created以外的批量事件按params.ids做交集匹配后才触发callbackunsubscribe负责解绑监听器以避免内存泄漏publish则将事件发布到对应频道。这正是本文协作编辑器消息过滤、类型分发、断连清理等实践在框架层的规范化表达。结论全双工双向实时通信是现代 Web 开发的重要能力WebSocket 提供了实现实时通信最相关且高效的途径。本文梳理了 HTTP 轮询、HTTP 流与 SSE 的局限剖析了 WebSocket 的握手与通信机制并以一个可运行的 Node.js React 协作文本编辑器完整走通了连接建立、消息广播、断连处理全流程同时给出了 WSS 加密、JWT 认证授权、防跨站劫持、限流校验与优雅关闭等生产级安全实践以及 STOMP/MQTT/WAMP 子协议选型建议。在此基础上Refine 的 Live Provider 将底层实时通道统一抽象让管理后台应用以极低成本获得开箱即用的实时数据刷新能力。【免费下载链接】refineA React Framework for building internal tools, admin panels, dashboards B2B apps with unmatched flexibility.项目地址: https://gitcode.com/GitHub_Trending/re/refine创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表