Node.js 中的 WebSocket 底层实现



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 实现中需要解决的其他问题。
相关文章:
Node.js 中的 WebSocket 底层实现
WebSockets 是一种网络通信协议,可实现双向客户端-服务器通信。 WebSockets 通常用于需要即时更新的应用程序,使用 HTTP 之上的持久双工通道来支持实时交互,而无需持续进行连接协商。服务器推送是 WebSockets 的众多常见用例之一。 本文首先…...
MySQl数据库的基本操作
1.1创建数据库 使用CREATE DATABASE语句可以轻松创建MySQL数据库,语法如下: CREATE DATABASE 数据库名; 例:创建fruitsales数据库 CREATE DATABASE fruitsales;1.2 查看数据库 使用SHOW语句查看当前服务器下所有已经存在的数据库 SHOW DAT…...
Egg.js 项目的合理 ESLint 配置文件模板
Egg.js 项目的合理 ESLint 配置文件模板 安装依赖 npm install eslint babel/eslint-parser eslint-plugin-import eslint-plugin-promise eslint-plugin-node --save-dev extends: 扩展了 eslint-config-egg 以及其他一些常用的插件配置。 parser: 使用 babel/eslint-parse…...
算法专题七: 分治归并
目录 1. 排序数组2. 交易逆序对的总数3. 计算右侧小于当前元素的个数4. 翻转对 1. 排序数组 算法思路: 本道题使用归并的思路进行排序, 先讲数组分为左右两个区间, 然后合并两个有序数组. class Solution {vector<int> tmp; public:vector<int> sortArray(vector&…...
一个基于vue功能强大的表格组件--vxe-table的二次封装
基础使用 一个基于 vue 的 PC 端表格组件,支持增删改查、虚拟滚动、懒加载、快捷菜单、数据校验、树形结构、打印导出、表单渲染、数据分页、虚拟列表、模态窗口、自定义模板、渲染器、贼灵活的配置项、扩展接口等… <vxe-grid v-bind"gridOptions1"…...
CSS网页布局(重塑网页布局)
一、实现两列布局 许多网站有一些特点,如页面顶部放置一个大的导航或广告条,右侧是链接或图片,左侧放置主要内容,页面底部放置版权信息等。 一般情况,此类网页布局的两列都有固定的宽度,而且从内容上很容易…...
计算机网络:数据链路层 —— 以太网(Ethernet)
文章目录 局域网局域网的主要特征 以太网以太网的发展100BASE-T 以太网物理层标准 吉比特以太网载波延伸物理层标准 10吉比特以太网汇聚层交换机物理层标准 40/100吉比特以太网传输媒体 局域网 局域网(Local Area Network, LAN)是一种计算机网络&#x…...
考研前所学c语言02(2024/10/16)
1.一个十进制的数转化为二进制的就是不断除二取余,得到的余数从下到上取 比如123: 结果为: 同理其他的十进制转八进制,十六进制就除八,除十六即可 再比如123转十六进制: 因为余数是11,十六进…...
R语言绘图——坐标轴及图例
掌握坐标轴与图例的设置与调整,对于提升数据可视化的清晰度和可读性至关重要。通过这些工具,可以有效地传达数据背后的故事,提高图表的表现力。 0x01 坐标轴 一、坐标轴的设置 1、修改坐标轴的标签 在ggplot2中,坐标轴是根据数…...
JDK中socket源码解析
目录 1、Java.net包 1. Socket通信相关类 2. URL和URI处理类 3. 网络地址和主机名解析类 4. 代理和认证相关类 5. 网络缓存和Cookie管理类 6. 其他网络相关工具类 2、什么是socket? 3、JDK中socket核心Api 4、核心源码 1、核心方法 2、本地方法 3、lin…...
Ansible自动化运维项目实战指南
Ansible自动化运维项目实战指南 在当今快速发展的IT环境中,运维工作的复杂性和规模性日益增加,传统的手动运维方式已难以满足高效、可靠、可重复性的需求。Ansible作为一款开源的自动化运维工具,凭借其简单易用、无需代理、基于SSH的架构特性…...
MySQL【知识改变命运】10
联合查询 0.前言1.联合查询在MySQL里面的原理2.练习一个完整的联合查询2.1.构造练习案例数据2.2 案例:⼀个完整的联合查询的过程2.2.1. 确定参与查询的表,学⽣表和班级表2.2.2. 确定连接条件,student表中的class_id与class表中id列的值相等2.…...
Java学习教程,从入门到精通, Java 基础语法(4)
1、Java 基础语法 一、Java 简介与开发环境搭建 Java 简介:Java 是一种面向对象的编程语言,具有跨平台、安全、稳定等特点。Java 主要应用于企业级应用、Android 应用开发、大数据处理等领域。开发环境搭建:搭建 Java 开发环境需要安装 JDK…...
反编译工具-Jclasslib的使用,与Java方法调用的探索
这里写目录标题 前言IDEA下查看字节码的两种方法使用idea自带的插件工具安装插件 为什么没有看出方法调用关系原因分析工厂举例 知识补充语言java可移植性 总结 前言 画时序图的时候,我想验证下方法的调用是否写的正确。方法调用不仅涉及到程序的基本逻辑流程&#…...
力扣 简单 876.快慢指针
文章目录 题目介绍题解 题目介绍 题解 class Solution {public ListNode middleNode(ListNode head) {ListNode slow head, fast head;while(fast ! null && fast.next ! null){slow slow.next;fast fast.next.next;}return slow;} }...
FineReport 计算同比增长
1、数据库查询 SELECTt1.年,t1.月,t1.总金额 AS 同期金额,t1.仓库名称,t2.总金额 AS 上期金额 FROMtest t1LEFT JOIN test t2 ON ( t1.年 t2.年 1 ) AND t1.月 t2.月 AND t1.仓库名称 t2.仓库名称2、配置字段 月份字段加后缀 月 数据列加后缀 计算同比增长率 if(LEN(B3)0 …...
从0开始深度学习(12)——多层感知机的逐步实现
依然以Fashion-MNIST图像分类数据集为例,手动实现多层感知机和激活函数的编写,大部分代码均在从0开始深度学习(9)——softmax回归的逐步实现中实现过 1 读取数据 import torch from torchvision import transforms import torchv…...
如何利用OpenCV和yolo实现人脸检测
在之前的blog里面,我们有介绍OpenCV和yolo的区别,本文就人脸检测为例,分别介绍下OpenCV和yolo的实现方式。 OpenCV实现人脸检测 一、安装 OpenCV 首先确保你已经安装了 OpenCV 库。可以通过以下方式安装: 使用包管理工具安装&…...
015集——c# 实现CAD excel交互(CAD—C#二次开发入门)
第一步:添加引用 程序集—>扩展 namespace WindowsFormsApp2 {public partial class Form1 : Form{public Form1(){InitializeComponent();}private void Form1_Load(object sender, EventArgs e){}private void 获取当前excel_Click(object sender, EventArgs e…...
【计网笔记】以太网
经典以太网 总线拓扑 物理层 Manchester编码 数据链路层 MAC子层 MAC帧 DIX格式与IEEE802.3格式 IEEE802.3格式兼容DIX格式 前导码(帧开始定界符SOF) 8字节 前7字节均为0xAA第8字节为0xAB前7字节的Manchester编码将产生稳定方波,用于…...
Leetcode 3576. Transform Array to All Equal Elements
Leetcode 3576. Transform Array to All Equal Elements 1. 解题思路2. 代码实现 题目链接:3576. Transform Array to All Equal Elements 1. 解题思路 这一题思路上就是分别考察一下是否能将其转化为全1或者全-1数组即可。 至于每一种情况是否可以达到…...
React hook之useRef
React useRef 详解 useRef 是 React 提供的一个 Hook,用于在函数组件中创建可变的引用对象。它在 React 开发中有多种重要用途,下面我将全面详细地介绍它的特性和用法。 基本概念 1. 创建 ref const refContainer useRef(initialValue);initialValu…...
R语言AI模型部署方案:精准离线运行详解
R语言AI模型部署方案:精准离线运行详解 一、项目概述 本文将构建一个完整的R语言AI部署解决方案,实现鸢尾花分类模型的训练、保存、离线部署和预测功能。核心特点: 100%离线运行能力自包含环境依赖生产级错误处理跨平台兼容性模型版本管理# 文件结构说明 Iris_AI_Deployme…...
【快手拥抱开源】通过快手团队开源的 KwaiCoder-AutoThink-preview 解锁大语言模型的潜力
引言: 在人工智能快速发展的浪潮中,快手Kwaipilot团队推出的 KwaiCoder-AutoThink-preview 具有里程碑意义——这是首个公开的AutoThink大语言模型(LLM)。该模型代表着该领域的重大突破,通过独特方式融合思考与非思考…...
2025 后端自学UNIAPP【项目实战:旅游项目】6、我的收藏页面
代码框架视图 1、先添加一个获取收藏景点的列表请求 【在文件my_api.js文件中添加】 // 引入公共的请求封装 import http from ./my_http.js// 登录接口(适配服务端返回 Token) export const login async (code, avatar) > {const res await http…...
ardupilot 开发环境eclipse 中import 缺少C++
目录 文章目录 目录摘要1.修复过程摘要 本节主要解决ardupilot 开发环境eclipse 中import 缺少C++,无法导入ardupilot代码,会引起查看不方便的问题。如下图所示 1.修复过程 0.安装ubuntu 软件中自带的eclipse 1.打开eclipse—Help—install new software 2.在 Work with中…...
华为云Flexus+DeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建
华为云FlexusDeepSeek征文|DeepSeek-V3/R1 商用服务开通全流程与本地部署搭建 前言 如今大模型其性能出色,华为云 ModelArts Studio_MaaS大模型即服务平台华为云内置了大模型,能助力我们轻松驾驭 DeepSeek-V3/R1,本文中将分享如何…...
Rapidio门铃消息FIFO溢出机制
关于RapidIO门铃消息FIFO的溢出机制及其与中断抖动的关系,以下是深入解析: 门铃FIFO溢出的本质 在RapidIO系统中,门铃消息FIFO是硬件控制器内部的缓冲区,用于临时存储接收到的门铃消息(Doorbell Message)。…...
今日学习:Spring线程池|并发修改异常|链路丢失|登录续期|VIP过期策略|数值类缓存
文章目录 优雅版线程池ThreadPoolTaskExecutor和ThreadPoolTaskExecutor的装饰器并发修改异常并发修改异常简介实现机制设计原因及意义 使用线程池造成的链路丢失问题线程池导致的链路丢失问题发生原因 常见解决方法更好的解决方法设计精妙之处 登录续期登录续期常见实现方式特…...
Razor编程中@Html的方法使用大全
文章目录 1. 基础HTML辅助方法1.1 Html.ActionLink()1.2 Html.RouteLink()1.3 Html.Display() / Html.DisplayFor()1.4 Html.Editor() / Html.EditorFor()1.5 Html.Label() / Html.LabelFor()1.6 Html.TextBox() / Html.TextBoxFor() 2. 表单相关辅助方法2.1 Html.BeginForm() …...
