如何使用websocket

如何使用websocket

之前看到过一个面试题:吃饭点餐的小程序里,同一桌的用户点餐菜单如何做到的实时同步?
答案就是:使用websocket使数据变动时服务端实时推送消息给其他用户。
最近在我们自己的项目中我也遇到了类似问题:后端需要调用第三方接口然后异步得到结果,前端却不知道具体的回调时间,只能反复轮询,后来找了找资料,想要达到服务端主动推送消息,也许需要使用websocket。
 

参考:
websocket 学习–简单使用,nodejs搭建websocket服务器
一文吃透 WebSocket
比第一个文章更加深入地实现:NodeJS 落地 WebSocket 实践
主参考(必看)
 

学习前的疑惑:

  1. 服务端广播消息时如何具体推送到相关用户?
  2. 代码书写中针对websocket的网络协议有没有什么不安全的行为,如何避免传输中信息泄露。
  3. 长连接涉及的断联和重传行为如何解决

1、什么是websocket

webSocket是一种网络应用层协议,它是基于TCP连接上进行全双工通信的协议,在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接, 并进行双向数据传输,也就是说可以达到服务端主动向客户端推送数据的效果。

WebSocket连接的过程是:

websocket首先通过HTTP协议把TCP连接好,然后通过Upgrade字段进行协议转化,收到服务器的101 Switching Protocols应答后,后续的TCP消息就通过websocket协议解析。

首先,WebSocket需要一个握手过程,在这里它利用了HTTP本身协议升级的特性。经过3次握手后,服务器和客户端建立起TCP连接,然后一方发起一个http get请求,请求头里存放WebSocket支持的版本号等信息,如:Upgrade、Connection、WebSocket-Version等;

  • “Connection: Upgrade”,表示要求协议“升级”;
  • “Upgrade: websocket”,表示要“升级”成 WebSocket 协议。
  • Sec-WebSocket-Key:一个 Base64 编码的 16 字节随机数,作为简单的认证密钥;
  • Sec-WebSocket-Version:协议的版本号,当前必须是 13。
    然后,服务器收到客户端的握手请求后,就不会走普通的 HTTP 处理流程,而是构造一个特殊的“101 Switching Protocols”响应报文,通知客户端,接下来就不用 HTTP 了,全改用 WebSocket 协议通信。;
  • Sec-WebSocket-Accept:响应报文响应头,具体是把请求头里Sec-WebSocket-Key的值+某一个UUID,计算一番传给客户端,然后客户端验证后连接成功。
    最后,客户端收到连接成功的消息后,开始借助于TCP传输信道进行全双工通信。

2、简单的demo

服务器采用epress+ws简单构建。 然后node express-server.js启动。

// express-server.jsvar WebSocketServer = require('ws').Server;wss = new WebSocketServer({ port: 9999 });
wss.on('connection', function (ws) {console.log('client connected');ws.on('message', function (message) {console.log(message);ws.send('服务端接收到请求后,发送给客户端的数据' + message);});ws.on('close', () => {console.log('close');});
});

服务端使用react脚手架直接启一个,页面上将这个封装好的测试连接组件显示出来。

import { useRef, useState } from 'react';
import { Button } from 'antd';export default function Index() {const ws = useRef(null);const startWs = () => {if ('WebSocket' in window) {// 初始化一个 WebSocket 对象,参数指明urlws.current = new WebSocket('ws://localhost:9999');// WebSocket 连接时候触发ws.current.onopen = () => {// 使用 send() 方法发送数据ws.current.send('客户端发送的数据');console.log('数据发送中...');};/*** 接收服务端数据时触发* @param {[{type:string,number:number}]} evt.data* @param {string} evt.data.type a :a+1,b:b+1* @param {number} evt.data.number*/ws.current.onmessage = (evt) => {let received_msg = evt.data;console.log('数据已接收...', received_msg);};// 断开 web socket 连接成功触发事件ws.current.onclose = () => {// 关闭 websocketconsole.log('连接已关闭...');};} else {// 浏览器不支持 WebSocketconsole.log('您的浏览器不支持 WebSocket!');}};return (<><Button onClick={startWs}>测试WS连接</Button><div>{Object.keys(data).map((key) => (<h2>{key} : {data[key]}<br /></h2>))}</div></>);
}

是的,就上面两个代码,就能测试一个最简单的ws连接是什么样的。
打开浏览器的控制台 network也可以看到ws的传输报文。可以看到httpCode:101,而requestHeaders里包含几个升级到websocket的请求头,后续我们就可以利用这些特性进行连接的鉴权。

request
Sec-WebSocket-Key: 是随机的字符串,用于后续校验。
Origin: 请求源
Upgrade: websocket
Connection: Upgrade\

response
Sec-WebSocket-Accept: 用匹配寻找客户端连接的值,计算公式为toBase64(sha1( Sec-WebSocket-Key + 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 ) )
这里的258EAFA5-E914-47DA-95CA-C5AB0DC85B11 为魔术字符串,为常量。

若计算不正确,或者没有返回该字段,则websocket连接不能建立成功。

3、连接的鉴权和安全行为

首先https的连接必须采用wss的连接方式(防止中间人)。
websocket本身是不支持http封装好的cookie 、headers等信息传递方式的,但是它在升级协议的那个请求还是http协议,因此我们可以手动实现一个类似cookie鉴权。
数据传输时的鉴权采取了基于信道建立时鉴权的方案,用户第一次认证后,回传给客户端一个类似token的令牌,用户在每一次使用websocket进行数据传输时,则需要回传这个token到服务端进行验证。


// 参考https://www.npmjs.com/package/ws
import { createServer } from 'http';
import { WebSocketServer } from 'ws';const server = createServer();
const wss = new WebSocketServer({ noServer: true });wss.on('connection', function connection(ws, request, client) {ws.on('message', function message(data) {console.log(`Received message ${data} from user ${client}`);});
});server.on('upgrade', function upgrade(request, socket, head) {// This function is not defined on purpose. Implement it with your own logic.authenticate(request, function next(err, client) {if (err || !client) {socket.write('HTTP/1.1 401 Unauthorized\r\n\r\n');socket.destroy();return;}wss.handleUpgrade(request, socket, head, function done(ws) {wss.emit('connection', ws, request, client);});});
});server.listen(8080);

4、开发过程中需要注意的心跳检测、断线重连机制。

长时间的保持连接是比较浪费网络资源的,因此对于很久没有响应的通道最好进行一个心跳检测,间隔一定时间发送一个心跳包,保持通道连接。
服务端发送心跳包可以直接使用封装好的方法:ws.ping('', false, true);
客服端这边发送心跳包需要自己定义一个定时器,假设保活时间为10分钟一次,收不到服务器返回的回应包就把连接挂了,其他的情况保持通道。

function heartCheck (ws) {const timeout = 10*60*1000; // 间隔10分钟发送一次。const serverTimeout = 5000; // 5秒内若还没有响应则关闭连接。let timer = null; // 心跳包的发送定时器。let serverTimer = null; // 服务器端响应定时器时间。// OnMessage接收消息则清除定时器。const resetServerTimer = () => {clearTimeout(resetTimer);};// 删除全部定时器(正常关闭链接或者没有收到心跳包)  const resetTimer = () => {clearTimeout(timer);clearTimeout(serverTimer);ws.close();};const start = () => {timer = setTimeout(()=>{//这里发送一个心跳,后端收到后,返回一个心跳消息,//onmessage拿到返回的心跳就说明连接正常ws.send("ping");serverTimer = (() => {resetTimer();},serverTimeout)},timeout)};return {resetServerTimer,resetTimer,start};
}


 

5、多个服务端的测试demo,如何推送消息到具体用户

// 服务端代码
var WebSocketServer = require('ws').Server;
const { createServer } =  require('http');wss = new WebSocketServer({ noServer:true });
const server = createServer();// 鉴权行为函数
const checkAuth = () => new Promise((res,rej)=>{res(true)
})let obj = {a:1,b:2}
let timer = null;// 定时器定时更新数据
function setImmediateFun (){timer = setImmediate(() => {obj = {a:obj.a+1,b:obj.b+1};},3000)          
}setImmediateFun();wss.on('connection', async function (ws,req,client) {// 检测协议升级时的鉴权 // 因为无法修改返回状态码以及返回token故采用ws.on("upgrade",(ws, req) => void)// if (headers['upgrade'] && headers['sec-websocket-key']) {//   // 请求用户服务身份验证//   const authorized = await checkAuth(headers.authToken);//   if(authorized){//     // 升级成功,生成一个token给客户端,状态码为101//   }else{//     // 连接失败,返回403//   }// }// open发送第一条消息ws.on('open', function (ws) {console.log("connect successfully");ws.send(JOSN.stringify(obj));});// 响应那边传来的数据,更新新数据ws.on('message', function (message) {ws.send(JSON.stringify(obj));wss.clients.forEach((client) => {// console.log(client)if (client.readyState === 1) {client.send(JSON.stringify(obj));}});});// 关闭连接ws.on('close', () => {console.log('close');clearInterval(timer)});
});server.on('upgrade', async function upgrade(request, socket, head) {if (await checkAuth(request.headers.authToken)) {// 升级成功,生成一个token给客户端,状态码为101wss.handleUpgrade(request, socket, head, function done(ws) {wss.emit('connection', ws, request);});}else{// 连接失败,返回403socket.write('HTTP/1.1 403 Unauthorized\r\n\r\n');socket.destroy();return;}
});server.listen(9999);
// 客户端代码
import { useRef, useState } from 'react';
import { Button } from 'antd';export default function Index() {const ws_current = useRef(null);let ws = ws_current.current;const [data, setData] = useState({ a: 0, b: 0 });const startWs = () => {if ('WebSocket' in window) {// 初始化一个 WebSocket 对象,参数指明urlws = new WebSocket('ws://localhost:9999');// WebSocket 连接时候触发ws.onopen = () => {console.log('连接成功');};/*** 接收服务端数据时触发* @param {[{type:string,number:number}]} evt.data* @param {string} evt.data.type a :a+1,b:b+1* @param {number} evt.data.number*/ws.onmessage = (evt) => {let received_msg = evt.data;received_msg = JSON.parse(received_msg);console.log(received_msg)setData(received_msg);console.log('数据已接收...', received_msg);};// 断开 web socket 连接成功触发事件ws.onclose = () => {// 关闭 websocketconsole.log('连接已关闭...');};} else {// 浏览器不支持 WebSocketconsole.log('您的浏览器不支持 WebSocket!');}};const sendMessage = () => {ws?.send('send message')}return (<><Button onClick={startWs}>测试WS连接</Button><Button onClick={sendMessage}>连接成功后发送信号获得响应数据</Button><div>{Object.keys(data).map((key) => (<h2>{key} : {data[key]}<br /></h2>))}</div></>);
}

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

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

相关文章

使用CMSIS-DSP库进行嵌入式音频信号处理

在嵌入式环境下&#xff0c;使用CMSIS-DSP库进行音频信号处理是一种常见的应用场景。通过CMSIS-DSP库&#xff0c;开发人员可以利用嵌入式系统的处理能力来实现各种数字信号处理&#xff08;DSP&#xff09;功能&#xff0c;例如音频滤波、均衡器、噪音消除等。本文将介绍如何在…

问题 | IT行业有哪些证书含金量高?

IT行业有哪些证书含金量高? Cisco认证&#xff08;CCNA&#xff0c;CCNP&#xff0c;CCIE&#xff09;&#xff1a;思科是全球最大的网络设备供应商之一&#xff0c;它的认证证书在网络和通信领域被广泛认可。CCNA是初级认证&#xff0c;CCNP是高级认证&#xff0c;而CCIE是专…

NLP_Seq2Seq编码器-解码器架构

文章目录 Seq2Seq架构构建简单Seq2Seq架构1.构建实验语料库和词汇表2.生成Seq2Seq训练数据3. 定义编码器和解码器类4.定义Seq2Seq架构5. 训练Seq2Seq架构6.测试Seq2Seq架构 归纳Seq2Seq编码器-解码器架构小结 Seq2Seq架构 起初&#xff0c;人们尝试使用一个独立的RNN来解决这种…

CentOS7搭建Hadoop集群

准备工作 1、准备三台虚拟机&#xff0c;参考&#xff1a;CentOS7集群环境搭建&#xff08;3台&#xff09;-CSDN博客 2、配置虚拟机之间免密登录&#xff0c;参考&#xff1a;CentOS7集群配置免密登录-CSDN博客 3、虚拟机分别安装jdk&#xff0c;参考&#xff1a;CentOS7集…

【51单片机】实现一个动静态数码管显示项目(前置知识铺垫,代码&图演示)(5)

前言 大家好吖&#xff0c;欢迎来到 YY 滴单片机 系列 &#xff0c;热烈欢迎&#xff01; 本章主要内容面向接触过单片机的老铁 主要内容含&#xff1a; 欢迎订阅 YY滴C专栏&#xff01;更多干货持续更新&#xff01;以下是传送门&#xff01; YY的《C》专栏YY的《C11》专栏YY…

vue electron应用调exe程序

描述 用Python写了一个本地服务编译成exe程序&#xff0c;在electron程序启动后&#xff0c;自动执行exe程序 实现 1. 使用node的child_process模块可以执行windows执行&#xff0c;通过指令调exe程序 // electron/index.js var cp require("child_process"); /…

.NET Core 实现 JWT 认证

写在前面 JWT&#xff08;JSON Web Token&#xff09;是一种开放标准, 由三部分组成&#xff0c;分别是Header、Payload和Signature&#xff0c;它以 JSON 对象的方式在各方之间安全地传输信息。通俗的说&#xff0c;就是通过数字签名算法生产一个字符串&#xff0c;然后在网络…

bpmn.js一个基于Bpmn 2.0的前端工作流展示和绘制工具

bpmn.js是由开源工作流引擎camunda内部组织BPMN.IO组织开发的一款基于BPMN 2.0的工作流展示、编辑的web端工具库。由于工作流引擎activiti、flowable、camunda属于同宗分流&#xff0c;其工作流定义格式大致相同&#xff0c;所以我们可以使用bpmn.js完美融合其中任一工作流引擎…

VScode为什么选择了Electron,而不是QT?

选择Electron而不是QT可能是基于以下几个原因&#xff1a; Web技术的普及和开发者生态系统&#xff1a;Web技术如HTML、CSS和JavaScript在开发者中非常普及&#xff0c;开发者生态系统庞大且活跃。使用Electron可以利用这些熟悉的Web技术和丰富的开发者社区资源。跨平台支持&am…

CleanMyMac是否有必要购买?2024有啥优惠

CleanMyMac X是一款专业的Mac电脑清理和优化工具&#xff0c;它提供了多种功能&#xff0c;如智能清理、系统垃圾清理、恶意软件移除、个人隐私保护、优化加速等&#xff0c;可以帮助用户解决Mac系统维护问题&#xff0c;保持Mac电脑的最佳运行状态。 此外&#xff0c;Mac电脑的…

文件上传的另类应用

1.Imagemagick CVE-2016-3714 CVE-2022-44268 CVE-2020-29599可在vulhub靶场进行复现1.1.Imagemagick简介 ImageMagic是一款图片处理工具&#xff0c;当传入一个恶意图片时&#xff0c;就有可能存在命令注入漏洞。 ImageMagick默认支持一种图片格式mvg&#xff0c;而mvg与svg…

登录+JS逆向进阶【过咪咕登录】(附带源码)

JS渗透之咪咕登录 每篇前言&#xff1a;咪咕登录参数对比 captcha参数enpassword参数搜索enpassword参数搜索J_RsaPsd参数setPublic函数encrypt加密函数运行时可能会遇到的问题此部分改写的最终形态JS代码&#xff1a;运行结果python编写脚本运行此JS代码&#xff1a;运行结果&…

Java实现民宿预定管理系统 JAVA+Vue+SpringBoot+MySQL

目录 一、摘要1.1 项目介绍1.2 项目录屏 二、功能模块2.1 用例设计2.2 功能设计2.2.1 租客角色2.2.2 房主角色2.2.3 系统管理员角色 三、系统展示四、核心代码4.1 查询民宿4.2 新增民宿4.3 新增民宿评价4.4 查询留言4.5 新增民宿订单 五、免责说明 一、摘要 1.1 项目介绍 基于…

Android7.0-Fiddler证书问题

一、将Fiddler的证书导出到电脑&#xff0c;点击Tools -> Options -> HTTPS -> Actions -> Export Root Certificate to Desktop 二、下载Window版openssl&#xff0c; 点击这里打开页面&#xff0c;下拉到下面&#xff0c;选择最上面的64位EXE点击下载安装即可 安…

51单片机 跑马灯

#include <reg52.h>//毫秒级延时函数 void delay(int z) {int x,y;for(x z; x > 0; x--)for(y 114; y > 0 ; y--); }sbit LED1 P1^0x0; sbit LED2 P1^0x1; sbit LED3 P1^0x2; sbit LED4 P1^0x3; sbit LED5 P1^0x4; sbit LED6 P1^0x5; sbit LED7 P1^0x6; s…

Rust通用代码生成器莲花发布红莲尝鲜版二十一,前端代码生成物有巨大改进

Rust通用代码生成器莲花发布红莲尝鲜版二十一&#xff0c;前端代码生成物有巨大改进 Rust通用代码生成器莲花已发布红莲尝鲜版二十一&#xff0c;此版本采用了新的前端代码生成引擎&#xff1a;时空之门前端代码生成器6.2.0。此引擎支持Nodejs 21,Nodejs 18和Nodejs 14。消除了…

Ansible自动化工具(1)

目录 ansible的特性&#xff1a;. 二.部署ansible 管理端安装 ansible&#xff1a; ansible 目录结构&#xff1a; 管理主机上配置主机清单&#xff1a; ​编辑 配置密钥对验证&#xff1a; ansible 命令行模块 &#xff1a; 1&#xff0e;command 模块 指定 ip 执行…

蓝桥杯-求阶乘-python

问题描述 满足N!的末尾恰好有K个0的最小的N是多少&#xff1f; 如果这样的N不存在输出一1。 思路解析 末尾的0是由10产生的&#xff0c;而10是由质数2和5产生的 在求阶乘的过程中&#xff0c;只要是偶数就会有2&#xff0c;而5相对2更少&#xff0c;所以对于10的数量我们可以…

MyBatis中#和$符的区别,sql注入问题,动态sql语句

MyBatis中#{}和${}的区别 #{}和${}都是MyBatis提供的sql参数替换。区别是&#xff1a;#{}是预编译处理&#xff0c;${}是字符串直接替换。#{}可以防止SQL注入&#xff0c;${}存在SQL注入的风险&#xff0c;例如 “ or 11”虽然存在SQL注入风险&#xff0c;但也有自己的适用场…

分享3款开源免费好用的Docker可视化管理工具安装部署教程

文章目录 1.前言2.Docker Desktop3.Portainer3.1 Portainer默认英文版本安装3.2 Portainer汉化版本安装3.3官方镜像说明3.3.1ssl访问3.3.2Nginx反代3.3.3Nginx反代设置子目录3.3.4docker-compose部署 3.4登录 4.DockerUI4.1简介4.2项目地址4.3部署启动命令4.4登录4.5首页 5.总结…