WebSockets 教程:如何使用 Node 和 React 实现实时

编者注:本 WebSockets 教程最后更新于 2022 年 12 月 23 日,将示例应用程序升级到 React v18,使用 Hooks 和功能组件,如何修复 Mac 无法通过蓝牙耳机播放声音并使用库来处理 WebSockets。react-use-websocket

以前,大多数 Web 应用程序都有一个紧密连接的后端和前端是很常见的,如何在 Mac 上更改触控板灵敏度因此这些应用程序将数据和视图内容提供给用户的浏览器。如今,我们通常通过将两者与面向网络的通信线路连接起来,来开发松耦合、独立的后端和前端。

例如,如何在 Mac 上设置和自定义屏幕保护程序开发人员经常使用 RESTful 模式和 HTTP 协议来实现前端和后端之间的通信线路以进行数据传输。但是基于 HTTP 的 RESTful 概念使用单工通信(单向),为什么 Mac 桌面壁纸不会停留以及如何修复因此我们不能在不实施轮询等变通方法的情况下将数据直接从客户端(前端)发送到服务器(后端)。

WebSocket 协议解决了传统 HTTP 模式的这一缺点,如何在 Mac 上使用总结工具提供了全双工(或双向)通信机制,帮助开发人员构建实时应用程序。

在本文中,我将解释 WebSocket 协议背后的理论概念,如何在 Mac 上添加或删除管理员并演示如何使用 WebSocket 协议构建一个具有 Node.js 后端和 React 前端的实时协作文档编辑应用程序。

跳跃前进:

  • 什么是网络套接字?

  • WebSocket 与 HTTP 轮询、HTTP 流和服务器发送的事件有何不同?

  • 为什么你应该使用 WebSockets

  • 如何在 Node.js 和 React 中使用 WebSockets

  • 议程一:WebSocket建立服务器与客户端之间的握手

  • 接受 HTTP 连接时

  • 在客户端级别创建握手请求

  • 议程二:实时消息传输

  • 在客户端使用发送和收听消息react-use-websocket

  • 在 Node.js WebSocket 服务器上发送和收听消息

什么是 WebSocket 协议?

WebSocket 协议通过单个 TCP 套接字连接在客户端和服务器之间提供持久如何在 Mac 上使用通用剪贴板、实时、全双工的通信。

WebSocket 协议只有两个议程:打开握手和帮助数据传输。一旦服务器接受了客户端发送的握手请求并发起了一个WebSocket连接,如何检查和优化 Mac 的存储空间它们就可以以较少的开销随意地向对方发送数据。

WebSocket 通信通过使用 WS(端口 80)或 WSS(端口 443)协议的单个 TCP 套接字进行。根据Can I Use的说法,在撰写本文时,如何在 Mac 上使用对焦模式除了 Opera Mini 之外,几乎所有浏览器都对 WebSockets 提供了令人钦佩的支持。

以下视频解释了与传统 HTTP 协议相比,如何在 Mac 上添加、断开和忘记蓝牙设备WebSocket 协议如何工作以及如何让用户受益:

WebSocket 与 HTTP 轮询、HTTP 流和服务器发送的事件有何不同?

从历史上看,如何在 Mac 和 iPad 上禁用 Hot Corner 的快速笔记创建需要实时数据的 Web 应用程序(如游戏或聊天应用程序)需要滥用HTTP 协议来建立双向数据传输。有多种方法用于实现实时功能,但没有一种方法像 WebSocket 那样高效。HTTP 轮询、HTTP 流、Comet和 SSE(服务器发送的事件)都有其缺点。

HTTP轮询

解决这个问题的第一个尝试是定期轮询服务器。如何修复你的 Mac 屏幕看起来模糊setInterval正常的轮询方法根据客户端定义的时间间隔(通常使用或递归)频繁地从服务器获取数据setTimeout。另一方面,长轮询方法与普通轮询类似,但服务器会处理超时/等待时间。

HTTP长轮询生命周期如下:

  1. 客户端发出请求并等待响应

  1. 服务器延迟响应,直到发生更改、更新或超时。如何在 Apple Pages 中插入签名请求保持“挂起”状态,直到服务器有东西要返回给客户端

  1. 当服务器端有一些更改或更新时,它会将响应发送回客户端

  1. 客户端发送一个新的长轮询请求来监听下一组变化

长轮询存在很多漏洞——标头开销、延迟、超时、缓存等等。

HTTP 流

这种机制避免了网络延迟带来的痛苦,如何阻止应用程序在 Mac 上启动时打开因为初始请求会无限期地保持打开状态。请求永远不会终止,即使在服务器推送数据之后也是如此。HTTP Streaming 的前三个生命周期方法与 HTTP 长轮询相同。

但是,当响应被发送回客户端时,请求永远不会终止;关闭盖子时如何防止 MacBook 休眠服务器保持连接打开并在发生变化时发送新的更新。

服务器发送的事件 (SSE)

使用 SSE,服务器将数据推送到客户端,如何修复 Finder 上缺少的侧边栏类似于 HTTP 流式传输。SSE 是 HTTP 流概念的标准化形式,并带有内置的浏览器 API。聊天或游戏应用程序不能完全依赖 SSE。SSE 的完美用例是,如何更改 Finder 中的默认打开文件夹例如,Facebook 新闻提要:无论何时有新帖子出现,服务器都会将它们推送到时间轴。SSE 通过传统的 HTTP 发送,并且对打开的连接数有限制。

从此 GitHub Gist 文件了解有关 SSE 架构的更多信息。如何在 Mac 上使用磁盘工具格式化驱动器与 WebSockets 相比,这些方法不仅效率低下。进入它们的代码似乎是一种变通方法,可以使请求-回复类型的协议成为类似全双工的协议。

为什么你应该使用 WebSockets

WebSockets 旨在取代现有的双向通信方法。如何在 Mac 和 iPad 上切换 Universal Control 和 Sidecar当涉及到全双工实时通信时,上述现有方法既不可靠也不高效。

WebSockets 类似于 SSE,但在将消息从客户端返回到服务器方面也取得了成功。连接限制不再是问题,因为数据是通过单个 TCP 套接字连接提供的。

如何在 Node.js 和 React 中使用 WebSockets

如介绍中所述,如何在 Mac 上使用照片应用编辑图片WebSocket 协议只有两个议程:1) 打开握手,以及 2) 帮助数据传输。让我们看看 WebSockets 如何完成这些任务。如何恢复以前版本的页面文档为此,我将分拆一个 Node.js 服务器并将其连接到使用 React.js 构建的客户端。

首先,将此 GitHub 存储库下载或克隆到您的计算机中。如何在 Mac 上编辑屏幕截图此存储库包含示例协作文档编辑应用程序的源代码。使用您喜欢的代码编辑器打开它。你会看到如下两个目录:

  • server: 处理文档编辑器后端逻辑的 Node.js WebSocket 服务器

  • client: 连接到 WebSocket 服务器以获得实时功能的 React 应用程序

您可以使用以下命令启动文档编辑器应用程序:

#-- Setup and start the server

cd server

npm install # or yarn install

npm start # or yarn start

#-- Setup and start the client

cd client

npm install # or yarn install

npm start # or yarn start

使用上述命令运行应用程序,如何修复 macOS Monterey 上的蓝牙连接问题尝试使用两个浏览器窗口打开它,然后从两个浏览器窗口编辑文档:

让我们研究源代码并了解它是如何使用 WebSockets 工作的!

议程一:WebSocket建立服务器与客户端之间的握手

使用 Node.js 在服务器级别创建握手

我们可以使用单个端口来分离 HTTP 服务器并连接 WebSocket 服务器。下面的要点(摘自)显示了一个简单的 HTTP 服务器的创建。如何在 Mac 上自定义鼠标指针创建后,我们将 WebSocket 服务器绑定到 HTTP 端口:server/index.js

const { WebSocketServer } = require('ws');

const http = require('http');

// Spinning the http server and the WebSocket server.

const server = http.createServer();

const wsServer = new WebSocketServer({ server });

const port = 8000;

server.listen(port, () => {

console.log(`WebSocket server is running on port ${port}`);

});

在示例项目中,如何使用预览在 Mac 上删除图像背景我使用流行的ws库将 WebSocket 服务器实例附加到 HTTP 服务器实例。一旦 WebSocket 服务器连接到 HTTP 服务器实例,它将通过将协议从 HTTP 升级到 WebSocket 来接受传入的 WebSocket 连接请求。

uuid我将所有连接的客户端作为一个对象维护在我的代码中,并在收到来自浏览器的请求时通过包生成一个唯一的密钥:

// I'm maintaining all active connections in this object

const clients = {};

// A new client connection request received

wsServer.on('connection', function(connection) {

// Generate a unique code for every user

const userId = uuidv4();

console.log(`Recieved a new connection.`);

// Store the new connection and handle messages

clients[userId] = connection;

console.log(`${userId} connected.`);

});

接受 HTTP 连接时

在发送常规 HTTP 请求以建立连接时,客户端在请求标头中发送. 服务器对该值进行编码和散列处理,并添加一个预定义的 GUID。它在服务器发送的握手中回应生成的值。Sec-WebSocket-KeySec-WebSocket-Accept

一旦请求在服务器中被接受(在生产中进行必要的验证之后),握手就会用状态码 101(切换协议)来完成。如果您在浏览器中看到除状态代码之外的任何内容101,则 WebSocket 升级失败,将遵循正常的 HTTP 语义。

标头字段指示服务器是否愿意接受连接。此外,如果响应缺少标头字段,或者不等于,则表示 WebSocket 连接失败。Sec-WebSocket-AcceptUpgradeUpgradewebsocket

成功的 WebSocket 服务器握手如下所示:

HTTP GET ws://127.0.0.1:8000/ 101 Switching Protocols

Connection: Upgrade

Sec-WebSocket-Accept: Nn/XHq0wK1oO5RTtriEWwR4F7Zw=

Upgrade: websocket

在客户端级别创建握手请求

在客户端级别,我使用库来启动 WebSocket 连接。我们也可以使用内置的 WebSocket 浏览器 API 而无需任何第三方包,但是直接在 React 功能组件中使用浏览器 API 通常会产生复杂的代码。react-use-websocket

作为解决方案,我们可以为 WebSocket 连接创建一个自定义的 React 挂钩,但随后我们将重新发明轮子并创建一个克隆。提供挂钩来管理来自 React 功能组件的 WebSocket 连接。查看文档以更加熟悉特定的 React hook 的设计。react-use-websocketreact-use-websocketuseWebSocketreact-use-websocket

一旦请求被服务器接受,我们就会在浏览器控制台上看到。WebSocket connection established

App这是通过组件(在 中)创建与服务器的连接的初始脚手架:client/src/App.js

import React from 'react';

import useWebSocket from 'react-use-websocket';

import './App.css';

const WS_URL = 'ws://127.0.0.1:8000';

function App() {

useWebSocket(WS_URL, {

onOpen: () => {

console.log('WebSocket connection established.');

}

});

return (

<div>Hello WebSockets!</div>

);

}

export default App;

客户端发送以下标头以建立握手:

HTTP GET ws://127.0.0.1:8000/ 101 Switching Protocols

Upgrade: websocket

Connection: Upgrade

Sec-WebSocket-Key: vISxbQhM64Vzcr/CD7WHnw==

Origin: http://localhost:3000

Sec-WebSocket-Version: 13

现在客户端和服务器通过 WebSocket 握手事件连接,WebSocket 连接可以在接收消息时传输消息,从而实现 WebSocket 协议的第二个议程。

议程二:实时消息传输

用户可以在示例 React 应用程序中加入并编辑文档。该应用程序跟踪两个事件:

  1. 用户活动:每次用户加入或离开时,我都会向所有其他连接的客户端广播消息

  1. 内容更改:每次编辑器中的内容更改时,都会广播到所有其他连接的客户端

该协议允许我们以二进制数据或 UTF-8 格式发送和接收消息(注意,传输和转换 UTF-8 的开销较小)。

只要我们很好地理解套接字事件,理解和实现 WebSockets 就非常容易:onopen、onclose和onmessage。客户端和服务器端的术语相同。

在客户端使用发送和收听消息react-use-websocket

从客户端,当新用户加入或内容更改时,我们会触发一条消息到服务器,用于sendJsonMessage将新信息发送到服务器:

/* When a user joins, I notify the

server that a new user has joined to edit the document. */

function LoginSection({ onLogin }) {

const [username, setUsername] = useState('');

useWebSocket(WS_URL, {

share: true,

filter: () => false

});

function logInUser() {

if(!username.trim()) {

return;

}

onLogin && onLogin(username); // Triggers sendJsonMessage in App

}

// ----

// ----

/* When content changes, we send the

current content of the editor to the server. */

function handleHtmlChange(e) {

sendJsonMessage({

type: 'contentchange',

content: e.target.value

});

}

return (

<DefaultEditor value={html} onChange={handleHtmlChange} />

);

收听来自服务器的消息非常简单。例如,查看History组件如何监听用户事件并呈现活动日志:

function History() {

const { lastJsonMessage } = useWebSocket(WS_URL, {

share: true,

filter: isUserEvent

});

const activities = lastJsonMessage?.data.userActivity || [];

return (

<ul>

{activities.map((activity, index) => <li key={`activity-${index}`}>{activity}</li>)}

</ul>

);

}

在这里,我们使用设置来重用我们在组件中启动的现有 WebSocket 连接。默认情况下,只要 WebSocket 连接从服务器接收到新消息并且连接状态发生变化,挂钩就会重新呈现组件。share: trueAppuseWebSocket

因此,该History组件将为用户和编辑器事件重新呈现。因此,作为性能增强,我们使用设置来仅为用户事件重新呈现组件。filter: isUserEvent

在 Node.js WebSocket 服务器上发送和收听消息

在服务器中,我们只需捕获传入的消息并将其广播给连接到 WebSocket 的所有客户端。这就是臭名昭著的Socket.IO和 WebSocket之间的区别之一:我们在使用 WebSocket 时需要手动将消息发送给所有客户端。Socket.IO 是一个成熟的库,所以它自己处理。

看看我们如何在后端处理广播:

function broadcastMessage(json) {

// We are sending the current data to all connected active clients

const data = JSON.stringify(json);

for(let userId in clients) {

let client = clients[userId];

if(client.readyState === WebSocket.OPEN) {

client.send(data);

}

};

}

浏览器关闭时会发生什么?

当浏览器关闭时,WebSocket 调用事件close,这允许我们编写逻辑来终止当前用户的连接。在我的代码中,当用户离开文档时,我向其余用户广播一条消息:

function handleDisconnect(userId) {

console.log(`${userId} disconnected.`);

const json = { type: typesDef.USER_EVENT };

const username = users[userId]?.username || userId;

userActivity.push(`${username} left the document`);

json.data = { users, userActivity };

delete clients[userId];

delete users[userId];

broadcastMessage(json);

}

// User disconnected

connection.on('close', () => handleDisconnect(userId));

结论

WebSockets 是在应用程序中实现实时功能的最有趣和最方便的方法之一。它为我们提供了很大的灵活性来利用全双工通信。我强烈建议在试用 Socket.IO 和其他可用库之前使用 WebSocket。

猜你喜欢

转载自blog.csdn.net/weixin_47967031/article/details/130014210