WebSocket --- ws模块源码解析(详解)

摘要

在这一篇文章中,写了如何在node端和web端,实现一个WebSocket通信。
WebSocket在node端和客户端的使用

而在node端里面,我们使用了ws模块来创建WebSocket和WebSocketServer,那ws模块是如何做到可以和客户端进行双向通信的呢?

426状态码

在HTTP中,426表示“Upgrade Required”,即客户端需要通过HTTP协议的升级版进行访问。这个状态码主要用在WebSockets协议中,表示客户端需要使用WebSockets协议来连接服务器。

什么意思呢?例如我们创建一个HTTP服务如果这么写:

const http = require('http')const server = http.createServer((req, res) => {const body = http.STATUS_CODES[426];res.writeHead('426', {'Content-Type': 'text/align','Content-Length': body.length})res.end(body)
})server.listen(8080)

就是告诉客户端,如果你访问我这边的服务,那么你就要进行升级服务。也就是使用WebSocket对我进行访问!

那有一个问题,如果客户端使用了WebSocket访问,服务端要怎么进行响应呢?

还直接在createServer里面的回调中处理吗?

upgrade事件

在这里面,如果客户端通过WebSocket进行访问服务端,会触发服务端server的upgrade事件,也就是说会进下面的回调函数里。

server.on('upgrade',(req, socket, head) => {// 固定格式const key = req.headers['sec-websocket-key'];const digest = createHash('sha1').update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11',).digest('base64');const headers = ['HTTP/1.1 101 Switching Protocols','Upgrade: websocket','Connection: Upgrade',`Sec-WebSocket-Accept: ${digest}`];socket.write(headers.concat('\r\n').join('\r\n'));// 客户端发送的消息socket.on('data', (data) => {console.log(data.toString());})// 服务端向客户端发送消息socket.write('你好')
})

这个回调中,通过socket来进行和客户端进行双向通信。

转码

但是只有上面的例子,似乎每次拿到的数据都是乱码。这是因为WebSocket之间的通信的报文,不能通过Buffer的toString直接转码。这里提供一下在网上找到的转码方法:

server.on('upgrade', (req, socket, head) => {const key = req.headers['sec-websocket-key'];const digest = createHash('sha1').update(key + '258EAFA5-E914-47DA-95CA-C5AB0DC85B11',).digest('base64');const headers = ['HTTP/1.1 101 Switching Protocols','Upgrade: websocket','Connection: Upgrade',`Sec-WebSocket-Accept: ${digest}`];socket.write(headers.concat('\r\n').join('\r\n'));socket.on('data',(data) => {console.log(decodeSocketFrame(data).payloadBuf.toString())socket.write(encodeSocketFrame({fin:1,opcode:1,payloadBuf:Buffer.from('你好')}))})
})function decodeSocketFrame (bufData){let bufIndex = 0const byte1 = bufData.readUInt8(bufIndex++).toString(2)const byte2 = bufData.readUInt8(bufIndex++).toString(2)console.log(byte1);console.log(byte2);const frame =  {fin:parseInt(byte1.substring(0,1),2),// RSV是保留字段,暂时不计算opcode:parseInt(byte1.substring(4,8),2),mask:parseInt(byte2.substring(0,1),2),payloadLen:parseInt(byte2.substring(1,8),2),}// 如果frame.payloadLen为126或127说明这个长度不够了,要使用扩展长度了// 如果frame.payloadLen为126,则使用Extended payload length同时为16/8字节数// 如果frame.payloadLen为127,则使用Extended payload length同时为64/8字节数// 注意payloadLen得长度单位是字节(bytes)而不是比特(bit)if(frame.payloadLen==126) {frame.payloadLen = bufData.readUIntBE(bufIndex,2);bufIndex+=2;} else if(frame.payloadLen==127) {// 虽然是8字节,但是前四字节目前留空,因为int型是4字节不留空int会溢出bufIndex+=4;frame.payloadLen = bufData.readUIntBE(bufIndex,4);bufIndex+=4;}if(frame.mask){const payloadBufList = []// maskingKey为4字节数据frame.maskingKey=[bufData[bufIndex++],bufData[bufIndex++],bufData[bufIndex++],bufData[bufIndex++]];for(let i=0;i<frame.payloadLen;i++) {payloadBufList.push(bufData[bufIndex+i]^frame.maskingKey[i%4]);}frame.payloadBuf = Buffer.from(payloadBufList)} else {frame.payloadBuf = bufData.slice(bufIndex,bufIndex+frame.payloadLen)}return frame
}function encodeSocketFrame (frame){const frameBufList = [];// 对fin位移七位则为10000000加opcode为10000001const header = (frame.fin<<7)+frame.opcode;frameBufList.push(header)const bufBits = Buffer.byteLength(frame.payloadBuf);let payloadLen = bufBits;let extBuf;if(bufBits>=126) {//65536是2**16即两字节数字极限if(bufBits>=65536) {extBuf = Buffer.allocUnsafe(8);buf.writeUInt32BE(bufBits, 4);payloadLen = 127;} else {extBuf = Buffer.allocUnsafe(2);buf.writeUInt16BE(bufBits, 0);payloadLen = 126;}}let payloadLenBinStr = payloadLen.toString(2);while(payloadLenBinStr.length<8){payloadLenBinStr='0'+payloadLenBinStr;}frameBufList.push(parseInt(payloadLenBinStr,2));if(bufBits>=126) {frameBufList.push(extBuf);}frameBufList.push(...frame.payloadBuf)return Buffer.from(frameBufList)
}

WebSocketServer的实现

有了上面的基础,基本知道双向通信是怎么做到的了。就来看一下WebSocketServer的实现。
当我们使用的时候,我们是以这种方式:

const WebSocketServer = require('ws')const wss = new WebSocketServer('8080');
wss.on('connection', (ws) => {})

我们知道,connection是httpServer的回调,为什么在WebSocketServer中可以使用呢?

export default class WebSocketServer {constructor(port) {this._server = http.createServer((req, res) => {const body = http.STATUS_CODES[426];res.writeHead('426', {'Content-Type': 'text/align','Content-Length': body.length})res.end(body)})this._server.listen(port);const connectionEmit = this.emit.bind(this, 'connection');const closeEmit = this.emit.bind(this, 'close');// 其他事件,都是http能监听到的;const map = {connection: connectionEmit,close: closeEmit}for(let emitName in map) {this._server.on(emitName, map[emitName])}}
}

在WebSocketServer中,如果客户端触发了http的事件时,它便将其转发到WebSocket实例上面。
然后再处理自己的逻辑。

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

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

相关文章

前端本地存储数据库IndexedDB

前端本地存储数据库IndexedDB 1、前言2、什么是 indexedDB&#xff1f;3、什么是 localForage&#xff1f;4、localForage 的使用5、VUE 推荐使用 Pinia 管理 localForage 1、前言 前端本地化存储算是一个老生常谈的话题了&#xff0c;我们对于 cookies、Web Storage&#xff…

树莓派镜像安装 + 设置 + 镜像批量化操作 - 自动化烧写工具 (四)

简介 当需要大批量使用树莓派时, SD Card烧录过程中的重复和繁杂操作需要被工具给取代, AT Disk Imager这就出现了;软件介绍 实现监控读卡器&#xff0c;当SD Card接入读卡器时自动格式化、 烧写设定镜像、并自动软弹出设备;目前可设定参数: 1) 镜像文件&#xff0c; 烧录的镜…

[github配置] 远程访问仓库以及问题解决

作者&#xff1a;20岁爱吃必胜客&#xff08;坤制作人&#xff09;&#xff0c;近十年开发经验, 跨域学习者&#xff0c;目前于新西兰奥克兰大学攻读IT硕士学位。荣誉&#xff1a;阿里云博客专家认证、腾讯开发者社区优质创作者&#xff0c;在CTF省赛校赛多次取得好成绩。跨领域…

MATLAB算法实战应用案例精讲-【神经网络】Transformer

目录 前言 算法原理 编码器 自注意力机制 从宏观视角看自注意力机制

Vue3 源码解读系列(九)——依赖注入

依赖注入 依赖注入用于祖先组件向后代组件传递数据。 特点&#xff1a; 祖先组件不需要知道哪些后代组件在使用它提供的数据。 后代组件也不需要知道注入的数据来自哪里。 /*** provide 的实现*/ function provide(key, value) {let provides currentInstance.provides // 当…

【LSTM】北京pm2.5 天气预测--pytorch版本,有代码可以跑通-LSTM回归问题,工程落地一网打尽

文章目录 前言1. 知识理解1.1 核心理解1.2 原理1.2.1 图解LSTM1.2.1 分词1.2.1 英语的词表示1.2.2 中文的词表示1.2.3 构建词表 2. 工程代码2.1 数据预处理2.2 数据集&模型构建2.3 模型训练2.4 保持模型&加载模型&预测 前言 LSTM 少分析原理&#xff0c;更强调工程…

网络渗透测试(TCP/IP)理论篇

TCP/IP体系 垂直服务&#xff1a;底层为高层服务 TCP/IP体系结构是一个分层的协议体系&#xff0c;由多个层次组成&#xff0c;每个层次都负责不同的功能。以下是TCP/IP体系结构的主要层次&#xff1a; 物理层&#xff08;Physical Layer&#xff09;&#xff1a;该层负责传输…

Debian系列的Linux发行版上部署wvp

Debian系列的Linux发行版上部署wvp 环境搭建1.Debian系列的Linux发行版上安装nginx2.安装mysql设置mysql密码修改权限sudo mysql ERROR 1045 (28000): Access denied for user root@localhost (using password: NO)配置相关navicat 连接不上 报错 10061navicat 连接报错 1130 -…

Python选择排序和冒泡排序算法

选择排序和冒泡排序都是常见的排序算法。以下是这两种算法的Python实现&#xff1a; 选择排序&#xff08;Selection Sort&#xff09; 选择排序的基本思想是在未排序的序列中找到最小&#xff08;或最大&#xff09;元素&#xff0c;存放到排序序列的起始位置&#xff0c;然…

grafana面板介绍

grafana 快速使用 背景 随着公司业务的不断发展&#xff0c;紧接来的是业务种类的增加、服务器数量的增长、网络环境的越发复杂以及发布更加频繁&#xff0c;从而不可避免地带来了线上事故的增多&#xff0c;因此需要对服务器到应用的全方位监控&#xff0c;提前预警&#xf…

MATLAB | 官方举办的动图绘制大赛 | 第二周赛情回顾

今天带来一下MATHWORKS官方举办的迷你黑客大赛第三期(MATLAB Flipbook Mini Hack)的最新进展&#xff01;&#xff01;目前比赛已经进行了两周非常荣幸能够成为第一周的阶段性获奖者&#xff1a; 本来并不打算每周进行一次赛况讲解&#xff0c;但是由于字符限制改成了2000&…

何时使用Elasticsearch而不是MySql?

何时使用Elasticsearch而不是MySql&#xff1f; MySQL 和 Elasticsearch 是两种不同的数据管理系统&#xff0c;它们各有优劣&#xff0c;适用于不同的场景。本文将从以下几个方面对它们进行比较和分析&#xff1a; 数据模型查询语言索引和搜索分布式和高可用性能和扩展性使用…

Linux系统编程 day02 vim、gcc、库的制作与使用

Linux系统编程 day02 vim、gcc、库的制作与使用 01. vim0101. 命令模式下的操作0102. 切换到文本输入模式0103. 末行模式下的操作0104. vim的配置文件 02. gcc03. 库的制作与使用0301. 静态库的制作与使用0302. 动态库(共享库)的制作与使用 01. vim vim是一个编辑器&#xff0…

adb突然获取不到华为/荣耀手机。。。

手机一开始都是好好的&#xff0c;adb获取正常&#xff0c;adb执行命令正常。突然有一天不好使了。。。。。 重启、换usb线都试过。。。。。。 看到hisuite模式和adb冲突这篇帖子&#xff0c;尝试下载华为手机助手去链接&#xff0c;但一直连接不上。 最后我的处理方法是&#…

Run Legends将健身运动游戏化,使用户保持健康并了解Web3游戏

最近&#xff0c;我们有机会采访Talofa Games的首席执行官兼创始人Jenny Xu&#xff0c;一起讨论游戏开发&#xff0c;Talofa Games是Run Legends这款健身游戏的开发工作室。她已经创作了超过一百款游戏&#xff0c;对于推动游戏的可能性并将她的创造力和叙事技巧带入她最喜爱的…

代码随想录算法训练营Day36 —— 738.单调递增的数字

738.单调递增的数字 思路&#xff1a; 题目要求小于等于N的最大单调递增的整数&#xff0c;那么拿一个两位的数字来举例。 例如&#xff1a;98&#xff0c;一旦出现strNum[i - 1] > strNum[i]的情况&#xff08;非单调递增&#xff09;&#xff0c;首先想让strNum[i - 1]…

POS系统完整体系的介绍 Pos终端主密钥MK、DUKPT、PEK、DEK、MEK、TUSN的含义 ---安全行业基础篇7

POS系统完整体系的介绍 销售点终端&#xff08;POS机&#xff09;是零售和服务行业中用于处理销售和交易的关键技术。POS系统不仅涉及支付处理&#xff0c;还包括库存管理、顾客关系管理、数据分析等多个方面。下面是POS系统完整体系的介绍&#xff1a; 1. 硬件组件 终端机&…

智能电销机器人好做吗?ai机器人有没有用?

电销机器人是基于深度神经学算法和卷积神经网络算法&#xff0c;将网络电话、语音识别、自然语言理解、多轮对话、知识图谱等多个门类集于一身的智能产品。不但能与客户智能交流&#xff0c;更能根据已经设定好的专业话术进行业务描述和问题解答&#xff0c;在电销行业是不可多…

leetcode数据结构与算法刷题(三)

目录 第一题 交叉链表 思想&#xff1a; 注意点 第一步先求两个链表的长度 第二步 让长的先走&#xff0c;当长短一样时一起走。 犯错点 第二题 判断是有环 思想&#xff1a; 注意 错误分享 第三题&#xff08;重点面试题&#xff09; 思路&#xff1a; 这题面试问题&a…

电子学会C/C++编程等级考试2022年06月(一级)真题解析

C/C++等级考试(1~8级)全部真题・点这里 第1题:倒序输出 依次输入4个整数a、b、c、d,将他们倒序输出,即依次输出d、c、b、a这4个数。 时间限制:1000 内存限制:65536输入 一行4个整数a、b、c、d,以空格分隔。 0 < a,b,c,d < 108输出 一行4个整数d、c、b、a,整数之…