Node.js 中的 WebSocket 底层实现

news2024/11/24 23:12:42

WebSockets 是一种网络通信协议,可实现双向客户端-服务器通信。

WebSockets 通常用于需要即时更新的应用程序,使用 HTTP 之上的持久双工通道来支持实时交互,而无需持续进行连接协商。服务器推送是 WebSockets 的众多常见用例之一。

本文首先从代码角度研究了 JavaScript 中 WebSockets 方程的两边,在服务器上使用 Node.js,在浏览器中使用原始 JavaScript。

WebSocket 协议

以前,在浏览器中通过 HTTP 进行双工通信或服务器推送需要相当多的技巧。如今,WebSockets 已成为 HTTP 的正式组成部分。它充当普通 HTTP 连接的“升级”连接。

WebSockets 可让您在浏览器客户端和后端之间来回发送任意数据。任一端都可以发起新消息,因此您拥有了用于各种需要持续通信或广播的实时应用程序的基础架构。开发人员将 WebSockets 协议用于游戏、聊天应用程序、直播、协作应用程序等。可能性无穷无尽。

为了本文的目的,我们将创建一个简单的服务器和客户端,然后使用它们来深入了解 WebSockets 通信期间发生的情况。

创建一个简单的服务器

首先,您需要一个/server包含两个子目录的目录/client和/server。有了这些之后,您需要一个非常简单的 Node 服务器,该服务器建立 WebSocket 连接并回显发送给它的任何内容。接下来,进入/websockets/server并开始一个新项目:

$ npm init

接下来我们需要ws 项目,我们将使用它来支持 WebSocket:

$ npm install ws

有了这些,我们可以绘制一个简单的回显服务器,如下所示 echo.js:

// echo.js
const WebSocket = require('ws');

const wss = new WebSocket.Server({ port: 3000 });

wss.on('connection', (ws) => {
  console.log('Client connected');

  ws.on('message', (message) => {
    console.log('Received message:', message);   

    ws.send(message); // Echo the message back to the client
  });

  ws.on('close', () => {
    console.log('Client disconnected');
  });
});

console.log(‘server started’);

这里,我们监听端口 3000,然后监听WebSocket.server对象上的连接事件。一旦connection发生,我们就会获取套接字对象 ( ws) 作为回调的参数。使用它,我们监听另外两个事件:message和close。

每当客户端发送消息时,它都会调用onMessage处理程序并将消息传递给我们。在该处理程序中,我们使用ws.send()方法发送回显响应。

请注意 ws.send() 还允许我们在需要时发送消息,因此我们可以根据其他事件将更新推送到客户端,例如来自服务的更新或来自另一个客户端的消息。

处理程序onClose让我们在客户端断开连接时执行工作。在本例中,我们只需记录它即可。

测试套接字服务器

如果能有一种简单的方法从命令行测试套接字服务器就好了,Websocat 工具非常适合此目的。它的安装过程很简单,如这里所述,并且有许多使用它的示例。

现在启动服务器:

/websockets/server $ node echo.js

使用Ctrl-z和将其置于背景状态$ bg,然后运行以下命令:

$ ./websocat.x86_64-unknown-linux-musl -t --ws-c-uri=wss://localhost:3000/ - ws-c:cmd:'socat - ssl:echo.websocket.org:443,verify=0'

这将建立一个开放的 WebSocket 连接,让您可以输入到控制台并查看响应。您将获得如下交互:

$ node echo.js 
Server started
^Z
[1]+  Stopped                 node echo.js
matthewcarltyson@dev3:~/websockets/server$ bg
[1]+ node echo.js &
matthewcarltyson@dev3:~/websockets/server$ ./websocat.x86_64-unknown-linux-musl -t --ws-c-uri=wss://localhost:3000/ - ws-c:cmd:'socat - ssl:echo.websocket.org:443,verify=0'
Request served by 7811941c69e658
An echo test
An echo test
Works
Works
^C
matthewcarltyson@dev3:~/websockets/server$ fg
node echo.js
^C

创建客户端

现在,让我们进入/websockets/client目录并创建一个可用于与服务器交互的网页。让服务器在后台运行,我们将从客户端访问它。

首先,创建一个index.html如下文件:

// index.html
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WebSocket Client</title>
</head>
<body>
    <h1>WebSocket Client</h1>
    <input type="text" id="message" placeholder="Enter message">
    <button id="send-btn">Send</button>
    <div id="output"></div>
    <script src="script.js"></script>
</body>
</html>

这仅提供了一个文本输入和一个提交按钮。它本身不做任何事情,仅提供我们在包含的脚本文件中需要的 DOM 元素:

// script.js
const wsUri = "ws://localhost:3000";
const outputDiv = document.getElementById("output");
const messageInput = document.getElementById("message");
const sendButton = document.getElementById("send-btn");

let websocket;

function connect() {
    websocket = new WebSocket(wsUri);

    websocket.onopen = function (event) {
        outputDiv.innerHTML += "
Connected to server!

";
    };

    websocket.onmessage = function (event) {
        const receivedMessage = event.data;
        outputDiv.innerHTML += "
Received: " + receivedMessage + "

";
    };

    websocket.onerror = function (event) {
        outputDiv.innerHTML += "
Error: " + event.error + "

";
    };

    websocket.onclose = function (event) {
        outputDiv.innerHTML += "
Connection closed.

";
    };
}

sendButton.addEventListener("click", function () {
    const message = messageInput.value;
    if (websocket && websocket.readyState === WebSocket.OPEN) {
        websocket.send(message);
        messageInput.value = "";
    } else {
        outputDiv.innerHTML += "
Error: Connection not open.

";
    }
});

connect(); // Connect immediately

此脚本使用浏览器原生 API 设置了多个事件处理程序。脚本加载后,我们立即启动 WebSocket,并监视open、onclose、onmessage和onerror事件。

每个事件都会将其更新附加到 DOM。最重要的是onmessage,我们从服务器接受消息并显示它。

按钮本身的 Click 处理程序接收用户输入的输入(messageInput.value),并使用 WebSocket 对象通过函数将其发送到服务器send()。然后我们将输入的值重置为空字符串。

假设后端仍在运行且可在ws://localhost:3000处使用,我们现在可以运行前端。我们可以使用http-server作为运行前端的简单方法。

这是一种在 Web 服务器中托管静态文件的简单方法,类似于 Python 的 http 模块或Java 的简单 Web 服务器,但适用于 Node。

它可以作为全局 NPM 包安装,也可以简单地npx从客户端目录使用运行:

/websockets/client/ $ npx http-server -o

 当我们运行上一个命令并访问该页面时,我们会得到应有的表单。但是当我们在输入框中输入消息并点击发送时,它显示:

Received: [object Blob]

 

ws如果您查看浏览器开发控制台,所有内容都通过 WebSocket 通道(选项卡中的选项卡)进行network。问题是,为什么它会以 blob 的形式返回?

如果你查看服务器控制台,它会显示:

Client connected
Received message: <Buffer 6f 6d 20 6d 61 6e 69 20 70 61 64 6d 65 20 68 75 6d>

所以现在我们知道问题出在服务器上。问题是ws模块的较新版本不会自动将消息解码为字符串,而是只提供二进制缓冲区。这是echo.js onmessage处理程序中的快速修复: 

ws.on('message', (message, isBinary) => {
  message = isBinary ? message : message.toString();
  console.log('Received message:', message);   
  ws.send(message); 
});

我们对回调使用第二个参数isBinary,如果处理程序接收到一个字符串,我们会使用快速将其转换为字符串message.toString()。

这篇快速指南阐明了 WebSocket 客户端-服务器通信的底层机制,没有框架的混淆。

如您所见,使用 WebSocket 高级功能的基础非常简单。只需使用简单的回调和消息发送,您就可以使用浏览器标准 API 和流行的 Node 库进行全双工和异步通信。

当然,在许多项目中,你会希望在前端使用React之类的东西,在后端使用Node或类似的运行时。幸运的是,一旦你了解了基础知识,这些框架就很容易集成。

此处的讨论和示例有意忽略了安全性,就像 Web 开发的每个领域一样,安全性增加了一层复杂性,需要在堆栈的两侧进行管理。

可扩展性和错误处理是我们在实际 WebSockets 实现中需要解决的其他问题。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2212349.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

剖析DNS劫持攻击原理及其防御措施

DNS劫持可导致用户访问到虚假网站&#xff0c;还可能使攻击者获取用户的敏感信息或利用受感染的计算机进行其他恶意活动。了解清楚DNS劫持攻击的原理有助于我们提升安全意识&#xff0c;避免个人信息泄露及财产损失。 1. 基础概念 DNS劫持是一种网络攻击&#xff0c;通过修改域…

低代码开发技术:驱动MES系统创新与制造业数字化转型的融合之路

低代码开发与生产管理MES系统的融合&#xff0c;是当今制造业数字化转型的一个重要趋势。以下是对这一融合现象的详细分析&#xff1a; 一、低代码开发的概念与特点 低代码开发是一种通过图形化界面和预构建模块来简化应用程序开发过程的方法。它允许开发人员使用拖放组件和最…

MT1331-MT1340 码题集 (c 语言详解)

MT1341反比例函数 c 语言实现代码 #include <stdio.h>double f(double x) { return 1.0 / x; }double trapezoidal_integration(double a, double b, int n) {// computer step lengthdouble h (b - a) / n;// computer points valuedouble sum (f(a) f(b)) / 2.0;//…

linux的sed命令

学习链接 Linux文本处理器sed - B站视频 文章目录 学习链接基础介绍语法格式sed的处理过程sed的选项-n-e-f-r-i pattern的7种用法sed中的编辑命令详解p 打印d 删除a 行后追加i 行前追加r 行后追加指定文件内容w 行追加到其它文件中 替换操作s/pattern/strings/pattern/string/…

【AI论文精读4】RAG论文综述2(微软亚研院 2409)P2-问题定义

【AI论文解读】【AI知识点】【AI小项目】【AI战略思考】【AI大项目】【AI应用】 P1 二、问题定义 数据增强的大语言模型&#xff08;LLM&#xff09;应用可以采取多种形式&#xff0c;从常见的基于领域特定数据的问答机器人&#xff0c;到复杂数据管道中的语义处理操作&#x…

AI工具在论文选题方面可以提供的帮助

学境思源&#xff0c;一键生成论文初稿&#xff1a; AcademicIdeas - 学境思源AI论文写作 在论文写作中&#xff0c;选题是决定研究质量和成果的关键步骤。随着人工智能技术的快速发展&#xff0c;AI工具在论文选题方面的应用逐渐受到关注。通过智能算法和大数据分析&#xff…

selenium安装

一、安装selenium 1&#xff0c;安装selenium -- 命令&#xff1a; pip install selenium -- 网络不稳的请换源安装&#xff1a; pip install selenium -i http://pypi.douban.com/simple/ --trusted-host pypi.douban.com 二、安装chrome webdriver 1.查看谷歌浏览器版本 …

Excel:vba实现合并工作表(表头相同)

这个代码应该也适用于一些表头相同的工作表的汇总&#xff0c;只需要修改想要遍历的表&#xff0c;适用于处理大量表头相同的表的合并 这里的汇总合并表 total 是我事先创建的&#xff0c;我觉得比用vba代码创建要容易一下&#xff0c;如果不事先创建汇总表就用下面的代码&…

二十一、浮动(1 文本)

<!DOCTYPE html> <html> <head> <meta charset"UTF-8"> <title></title> <style> img{ widows: 100px; float: left;/*脱离文档流*/ …

BUG调试记录:C++中有符号类型和无符号类型在自动补位时的差异

BUG调试记录&#xff1a;C中有符号类型和无符号类型在自动补位时的差异 昨天和同事一起干活的过程中&#xff0c;项目中一个函数的计算结果出现了问题&#xff0c;即使我们反复对照流程图并进行了一系列的手算也没有发现任何编码上的差错&#xff0c;在逻辑上整个函数的编写是完…

【C++入门(7)】string

一、与C语言的联系 C的string类其实是从C语言的字符串演变过来的&#xff0c;是STL中用于处理字符串的一个容器。 在C语言中&#xff0c;字符串是以 \0 为结尾的一串字符&#xff0c;为了便于操作&#xff0c;C语言标准库中也提供了不少str系列的库函数供人使用。但是这些库函…

Java生成图片_基于Spring AI

Spring AI 优势 过去&#xff0c;使用Java编写AI应用时面临的主要困境是没有统一且标准的封装库&#xff0c;开发者需自行对接各个AI服务提供商的接口&#xff0c;导致代码复杂度高、迁移成本大。如今&#xff0c;Spring AI Alibaba的出现极大地缓解了这一问题&#xff0c;它提…

vue3之生命周期钩子

Vue 组件实例生命周期 每个 Vue 组件实例在创建时都需要经历一系列的初始化步骤&#xff0c;比如设置好数据侦听&#xff0c;编译模板&#xff0c;挂载实例到 DOM&#xff0c;以及在数据改变时更新 DOM。在此过程中&#xff0c;它也会运行被称为生命周期钩子的函数&#xff0c…

基于Arduino的燃气泄漏检测系统与自动切断调节器

Arduino LPG燃气泄漏检测系统与自动切断调节器 引言 大家好&#xff0c;今天我要分享一个非常实用的项目——Arduino LPG燃气泄漏检测系统与自动切断调节器。这个项目的重要性不言而喻&#xff0c;因为燃气泄漏是一个严重的安全隐患。在厨房烹饪时&#xff0c;我们可能会忘记…

望繁信科技成功签约国显科技 流程挖掘助力制造业智造未来

近日&#xff0c;上海望繁信科技有限公司&#xff08;简称“望繁信科技”&#xff09;成功与深圳市国显科技有限公司&#xff08;简称“国显科技”&#xff09;达成合作。国显科技作为全球领先的TFT-LCD液晶显示及Mini/Micro LED显示产品供应商&#xff0c;致力于为笔记本、手机…

设计模式02-桥接模式(Java)

4.2 桥接模式 **1.定义&#xff1a;**将抽象与实现分离&#xff0c;使它们可以独立变化。它是用组合关系代替继承关系来实现&#xff0c;从而降低了抽象和实现这两个可变维度的耦合度。 2.结构&#xff1a; 抽象化角色 &#xff1a;定义抽象类&#xff0c;并包含一个对实现化…

Spring Security无脑使用

步骤1&#xff1a;添加Spring Security依赖 在你的Spring Boot项目的pom.xml文件中&#xff0c;添加Spring Security的依赖&#xff1a; <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-security</art…

数据库的特点

数据库是有组织的&#xff0c;规范的把数据保存起来的。 怎么个组织的&#xff0c;规范的&#xff1f; 数据库的特点&#xff1a; 1.将数据放到数据表格&#xff08;二维表&#xff09;中&#xff0c;在将表格放到库中。 2.一个数据库中可以有多张表&#xff0c;每个表都有一…

【OpenCV】(四)—— 边界填充

在OpenCV的图像处理中&#xff0c;边界填充主要用于在图像边缘添加额外的像素。这项技术在多种图像处理任务中都有重要的应用&#xff0c;包括但不限于卷积操作、图像缩放、形态学操作等。 opencv中边界填充的主要方法为copyMakeBorder&#xff0c;其函数原型如下&#xff1a;…

分享一道逻辑题

1、有一个水无限多的是水塘.只有两个水瓶.一个5升一个6升.如何从水塘里用这两个水瓶取出3升水?