webSocket 笔记

1 认识webSocket 

WebSocket_ohana!的博客-CSDN博客

一,什么是websocket

  • WebSocket是HTML5下一种新的协议(websocket协议本质上是一个基于tcp的协议)
  • 它实现了浏览器与服务器全双工通信,能更好的节省服务器资源和带宽并达到实时通讯的目的
  • Websocket是一个持久化的协议

WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输

2 技术选型 为什么选择webSocket

WebSocket有以下特点:

  •  是真正的全双工方式,建立连接后客户端与服务器端是完全平等的,可以互相主动请求。而HTTP长连接基于HTTP,是传统的客户端对服务器发起请求的模式。
  •  HTTP长连接中,每次数据交换除了真正的数据部分外,服务器和客户端还要大量交换HTTP header,信息交换效率很低。Websocket协议通过第一个request建立了TCP连接之后,之后交换的数据都不需要发送 HTTP header就能交换数据,这显然和原有的HTTP协议有区别所以它需要对服务器和客户端都进行升级才能实现(主流浏览器都已支持HTML5)

 

3 WebSocket  Demo

前端服务:

npm install websocket // "websocket": "^1.0.32",

新建 websocket.js 文件

node 安装:

npm install ws //     "ws": "^7.3.0",

vue项目中使用WebSocket_vue websocket服务端_weixin_43964779的博客-CSDN博客

开发者API

WebSocket() - Web API 接口参考 | MDN

client:

  <script>var app = new Vue({el: '#app',data: {message: '',lists: [],ws: {},name: '',isShow: true,num: 0,roomid: '',uid: '',handle: {}},mounted() {},methods: {init() {this.ws = new WebSocket('ws://127.0.0.1:3000')this.ws.onopen = this.onOpenthis.ws.onmessage = this.onMessagethis.ws.onclose = this.onClosethis.ws.onerror = this.onError},enter() {if (this.name.trim() === '') {alert('用户名不得为空')return}this.init()this.isShow = false},onOpen: function () {// console.log('open:' + this.ws.readyState);//ws.send('Hello fro,m client!')// 发起鉴权请求//this.ws.send(JSON.stringify({//  event: 'auth',//  message: //'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIx//MjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNT//E2MjM5MDIyfQ.//XbPfbIHMI6arZ3Y922BhjWgQzWXcXNrz0ogtVhfEd2o'//}))this.ws.send(JSON.stringify({event: 'enter',message: this.name,roomid: this.roomid,uid: this.uid}))},onMessage: function (event) {// 当用户未进入聊天室,则不接收消息if (this.isShow) {return}// 接收服务端发送过来的消息var obj = JSON.parse(event.data)switch (obj.event) {case 'noauth':// 鉴权失败// 路由跳转到 /login 重新获取tokenbreak;case 'enter':// 当有一个新的用户进入聊天室this.lists.push('欢迎:' + obj.message + '加入聊天室!')break;case 'out':this.lists.push(obj.name + '已经退出了聊天室!')break;case 'heartbeat'://this.checkServer() // timeInterval + t// 可以注释掉以下心跳状态,主动测试服务端是否会断开客户端的连接this.ws.send(JSON.stringify({event: 'heartbeat',message: 'pong'}))breakdefault:if (obj.name !== this.name) {// 接收正常的聊天this.lists.push(obj.name + ':' + obj.message)}}this.num = obj.num},onClose: function () {// 当链接主动断开的时候触发close事件console.log('close:' + this.ws.readyState);console.log('已关闭websocket');this.ws.close()},onError: function () {// 当连接失败时,触发error事件console.log('error:' + this.ws.readyState);console.log('websocket连接失败!');// 连接失败之后,1s进行断线重连!var _this = thissetTimeout(function () {_this.init()}, 1000)},// 发送消息send: function () {this.lists.push(this.name + ':' + this.message)this.ws.send(JSON.stringify({event: 'message',message: this.message,name: this.name}))this.message = ''},checkServer: function () {var _this = thisclearTimeout(this.handle)this.handle = setTimeout(function () {_this.onClose()setTimeout(function () {_this.init()}, 1000)// 设置1ms的时延,调试在服务器测未及时响应时,客户端的反应}, 30000 + 1000)}}})</script>



Server:

package.json:

{"name": "server","version": "1.0.0","description": "","main": "index.js","scripts": {"start": "nodemon src/index.js"},"keywords": [],"author": "","license": "ISC","dependencies": {"bluebird": "^3.7.2","jsonwebtoken": "^8.5.1","redis": "^2.8.0","ws": "^7.2.1"},"devDependencies": {"nodemon": "^2.0.2"}
}

index.js:

const WebSocket = require('ws')
const wss = new WebSocket.Server({ port: 3000 })
// const jwt = require('jsonwebtoken')
const {getValue, setValue, existKey} = require('./config/RedisConfig')const timeInterval = 30000
// 多聊天室的功能
// roomid -> 对应相同的roomid进行广播消息
let group = {}// const run = async () =>{
//   setValue('imooc', 'hello')
//   const result = await getValue('imooc')
//   console.log('TCL: run -> result', result)
// }// run()
const prefix = 'imooc-'
wss.on('connection', function connection (ws) {// 初始的心跳连接状态ws.isAlive = trueconsole.log('one client is connected');// 接收客户端的消息ws.on('message', async function(msg) {const msgObj = JSON.parse(msg)const roomid = prefix + (msgObj.roomid ? msgObj.roomid : ws.roomid)if (msgObj.event === 'enter') {// 当用户进入之后,需要判断用户的房间是否存在// 如果用户的房间不存在,则在redis中创建房间号,用户保存用户信息// 主要是用于统计房间里的人数,用于后面进行消息发送ws.name = msgObj.messagews.roomid = msgObj.roomidws.uid = msgObj.uidconsole.log('TCL: connection -> ws.uid', ws.uid)// 判断redis中是否有对应的roomid的键值const result = await existKey(roomid)if (result === 0) {// 初始化一个房间数据setValue(roomid, ws.uid)} else {// 已经存在该房间缓存数据const arrStr = await getValue(roomid)let arr = arrStr.split(',')if (arr.indexOf(ws.uid) === -1) {setValue(roomid, arrStr + ',' + ws.uid)}}if (typeof group[ws.roomid] === 'undefined') {group[ws.roomid] = 1} else {group[ws.roomid] ++}}// // 鉴权// if (msgObj.event === 'auth') {//   jwt.verify(msgObj.message, 'secret', (err, decode) => {//     if (err) {//       // websocket返回前台鉴权失败消息//       ws.send(JSON.stringify({//         event: 'noauth',//         message: 'please auth again'//       })) //       console.log('auth error');//       return//     } else {//       // 鉴权通过//       console.log(decode);//       ws.isAuth = true//       return //     }//   })//   return// }// // 拦截非鉴权的请求// if (!ws.isAuth) {//   return// }// 心跳检测if (msgObj.event === 'heartbeat' && msgObj.message === 'pong') {ws.isAlive = truereturn}// 广播消息// 获取房间里所有的用户信息const arrStr = await getValue(roomid)let users = arrStr.split(',')wss.clients.forEach(async (client) => {// 判断非自己的客户端if (client.readyState === WebSocket.OPEN && client.roomid === ws.roomid) {msgObj.name = ws.namemsgObj.num = group[ws.roomid]client.send(JSON.stringify(msgObj))// 排队已经发送了消息了客户端 -> 在线if (users.indexOf(client.uid) !== -1) {users.splice(users.indexOf(client.uid), 1)}// 消息缓存信息:取redis中的uid数据let result = await existKey(ws.uid)if (result !== 0) {// 存在未发送的离线消息数据let tmpArr = await getValue(ws.uid)let tmpObj = JSON.parse(tmpArr)let uid = ws.uidif (tmpObj.length > 0) {let i = []// 遍历该用户的离线缓存数据// 判断用户的房间id是否与当前一致tmpObj.forEach((item) => {if (item.roomid === client.roomid && uid === client.uid) {client.send(JSON.stringify(item))i.push(item)}})// 删除已经发送的缓存消息数据if (i.length > 0) {i.forEach((item) => {tmpObj.splice(item, 1)})}setValue(ws.uid, JSON.stringify(tmpObj))}}}})// 断开了与服务端连接的用户的id,并且其他的客户端发送了消息if (users.length> 0 && msgObj.event === 'message') {users.forEach(async function(item) {const result = await existKey(item)if (result !== 0) {// 说明已经存在其他房间该用户的离线消息数据let userData = await getValue(item)let msgs = JSON.parse(userData)msgs.push({roomid: ws.roomid,...msgObj})setValue(item, JSON.stringify(msgs))} else {// 说明先前这个用户一直在线,并且无离线消息数据setValue(item, JSON.stringify([{roomid: ws.roomid,...msgObj}]))}})}})// 当ws客户端断开链接的时候ws.on('close', function() {if (ws.name) {group[ws.roomid] --}let msgObj = {}// 广播消息wss.clients.forEach((client) => {// 判断非自己的客户端if (client.readyState === WebSocket.OPEN && ws.roomid === client.roomid) {msgObj.name = ws.namemsgObj.num = group[ws.roomid]msgObj.event = 'out'client.send(JSON.stringify(msgObj))}})})
})// setInterval(()=> {
//   wss.clients.forEach((ws) => {
//     if (!ws.isAlive && ws.roomid) {
//       group[ws.roomid] --  
//       delete ws['roomid']
//       return ws.terminate()
//     }
//     // 主动发送心跳检测请求
//     // 当客户端返回了消息之后,主动设置flag为在线
//     ws.isAlive = false
//     ws.send(JSON.stringify({
//       event: 'heartbeat',
//       message: 'ping',
//       num: group[ws.roomid]
//     }))
//   })
// }, timeInterval)

4 心跳检测&断线重连

服务器先发->客户端->服务器 ∞  

心跳检测:

服务ping->客户端   服务器端有定时器 如果没有收到 会在下次遍历中关闭与该客户的服务 ws.terminate() //终止发送  退出了本次连接

客户段Clinet:

客户段在定时器中加入 断开重连的代码,在下次服务器发送过来的PIng  的代码中 清除掉上次的定时器,同时就清除了上次心跳检查的断开代码,然后发送pong->服务器,服务器收到后继续大发送ping->客户端,当本次请求一直未收到ping时  心跳检查的定时器没有被清除 ,就会执行close方法,关闭本次连接,并重新Init新的链接,这就是断线重连。

服务器端: 定时器

setInterval(()=> {  //定时器wss.clients.forEach((ws) => {if (!ws.isAlive && ws.roomid) {  //客户段终止group[ws.roomid] --  delete ws['roomid']return ws.terminate() //终止发送  退出了本次连接}// 主动发送心跳检测请求// 当客户端返回了消息之后,主动设置flag为在线ws.isAlive = falsews.send(JSON.stringify({event: 'heartbeat',message: 'ping',num: group[ws.roomid]}))})
}, timeInterval)

客户端 心跳检查与 断线重连:

//心跳检查case 'heartbeat'://this.checkServer() // timeInterval + t   如果一直接收到ping   那么这次的请求就会删除上次的定时器  定时器不会被执行// 可以注释掉以下心跳状态,主动测试服务端是否会断开客户端的连接this.ws.send(JSON.stringify({event: 'heartbeat',message: 'pong'}))break//检查心跳 checkServer: function () {var _this = thisclearTimeout(this.handle)  //清除计时器this.handle = setTimeout(function () {_this.onClose()setTimeout(function () {_this.init()}, 1000)// 设置1ms的时延,调试在服务器测未及时响应时,客户端的反应}, 30000 + 1000)}

springboot整合webSocket(看完即入门)_springboot websocket_hmb↑的博客-CSDN博客

webSocket前端开发实现+心跳检测机制_前端心跳检测_mayuan2011的博客-CSDN博客

为什么要使用webSocket以及心跳检测机制

在使用webSocket的过程中,如果遇到网络断开,服务端并没有触发onclose事件,就会出现此状况:服务端会继续向客户端发送多余的连接,并且这些数据会丢失。
因此就需要一种机制来检测客户端和服务端是否处于正常的连接状态,因此就有了webSocket的心跳检测机制,即如果有心跳则说客户端和服务端的连接还存在,无心跳相应则说明链接已经断开,需要采取重新连接等措施。

5 总结 

WebSocket是一种在单个TCP连接上进行全双工通信的协议。WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。


 


 

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

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

相关文章

centos 7.9 部署django项目

1、部署框架 主要组件&#xff1a;nginx、uwsgi、django项目 访问页面流程&#xff1a;nginx---》uwsgi---》django---》uwsgi---》nginx 2、部署过程 操作系统&#xff1a;centos 7.9 配置信息&#xff1a;4核4G 50G 内网 eip &#xff1a;10.241.103.216 部署过程&…

深入学习SpringCloud Alibaba微服务架构,揭秘Nacos、Sentinel、Seata等核心技术,助力构建高效系统!

课程链接&#xff1a; 链接: https://pan.baidu.com/s/1hRN0R8VFcwjyCTWCEsz-8Q?pwdj6ej 提取码: j6ej 复制这段内容后打开百度网盘手机App&#xff0c;操作更方便哦 --来自百度网盘超级会员v4的分享 课程介绍&#xff1a; &#x1f4da;【第01阶段】课程简介&#xff1a;全…

Android FrameWork 层 Handler源码解析

Handler生产者-消费者模型 在android开发中&#xff0c;经常会在子线程中进行一些耗时操作&#xff0c;当操作完毕后会通过handler发送一些数据给主线程&#xff0c;通知主线程做相应的操作。 其中&#xff1a;子线程、handler、主线程&#xff0c;其实构成了线程模型中经典的…

STM32存储左右互搏 I2C总线FATS读写EEPROM ZD24C1MA

STM32存储左右互搏 I2C总线FATS读写EEPROM ZD24C1MA 在较低容量存储领域&#xff0c;EEPROM是常用的存储介质&#xff0c;可以通过直接或者文件操作方式进行读写。不同容量的EEPROM的地址对应位数不同&#xff0c;在发送字节的格式上有所区别。EEPROM是非快速访问存储&#xf…

vue2+Spring Boot2.7 大文件分片上传

之前我们文章 手把手带大家实现 vue2Spring Boot2.7 文件上传功能 将了上传文件 但如果文件很大 就不太好处理了 按正常情况甚至因为超量而报错 这里 我弄了个足够大的文件 我们先搭建 Spring Boot2.7 环境 首先 application.yml 代码编写如下 server:port: 80 upload:path:…

【佳佳怪文献分享】使用点云从半监督到全监督房间布局估计

标题&#xff1a;From Semi-supervised to Omni-supervised Room Layout Estimation Using Point Cloud 作者&#xff1a;Huan-ang Gao, Beiwen Tian, Pengfei Li, Xiaoxue Chen, Hao Zhao, Guyue Zhou , Yurong Chen and Hongbin Zha 来源&#xff1a;2023 IEEE Internation…

根据源码,模拟实现 RabbitMQ - 通过 SQLite + MyBatis 设计数据库(2)

目录 一、数据库设计 1.1、数据库选择 1.2、环境配置 1.3、建库建表接口实现 1.4、封装数据库操作 1.5、针对 DataBaseManager 进行单元测试 一、数据库设计 1.1、数据库选择 MySQL 是我们最熟悉的数据库&#xff0c;但是这里我们选择使用 SQLite&#xff0c;原因如下&am…

手机出现 不读卡 / 无信号时应该怎么办?

当手机屏幕亮起&#xff0c;一般在屏幕最上方都会有代表手机卡状态的显示&#xff0c;其中网络信号和读卡状态的标识&#xff0c;依旧有很多人分不太清&#xff0c;更不清楚改怎么办了。 1、当我们的手机里有两张卡时&#xff0c;则会有两个信号显示 2、信号状态一般是由短到…

CSS自己实现一个步骤条

前言 步骤条是一种用于引导用户按照特定流程完成任务的导航条&#xff0c;在各种分步表单交互场景中广泛应用。例如&#xff1a;在HIS系统-门诊医生站中的接诊场景中&#xff0c;我们就可以使用步骤条来实现。她的执行步骤分别是&#xff1a;门诊病历>遗嘱录入>完成接诊…

ArcGIS Pro基础入门、制图、空间分析、影像分析、三维建模、空间统计分析与建模、python融合、案例全流程科研能力提升

目录 第一章 入门篇 GIS理论及ArcGIS Pro基础 第二章 基础篇 ArcGIS数据管理与转换 第三章 数据编辑与查询、拓扑检查 第四章 制图篇 地图符号与版面设计 第五章 空间分析篇 ArcGIS矢量空间分析及应用 第六章 ArcGIS栅格空间分析及应用 第七章 影像篇 遥感影像处理 第八…

Python random模块用法整理

随机数在计算机科学领域扮演着重要的角色&#xff0c;用于模拟真实世界的随机性、数据生成、密码学等多个领域。Python 中的 random 模块提供了丰富的随机数生成功能&#xff0c;本文整理了 random 模块的使用。 文章目录 Python random 模块注意事项Python random 模块的内置…

30行JS代码带你手写自动回复语音聊天机器人

&#x1f942;(❁◡❁)您的点赞&#x1f44d;➕评论&#x1f4dd;➕收藏⭐是作者创作的最大动力&#x1f91e; 前言 现如今生活中到处都是聊天机器人的身影&#xff0c;聊天机器人不仅仅能减少人工的聊天压力&#xff0c;而且十分的可爱有趣&#xff0c;安卓系统的小AI&#xf…

Springboot整合Mybatis调用Oracle存储过程

1、配置说明 Oracel11g+springboot2.7.14+mybatis3.5.13 目标:springboot整合mybatis访问oracle中的存储过程,存储过程返回游标信息。 mybatis调用oracle中的存储过程方式 2、工程结构 3、具体实现 3.1、在Oracle中创建测试数据库表 具体数据可自行添加 create table s…

Lodash——使用与实例

1. 简介 Lodash是一个一致性、模块化、高性能的JavaScript实用库。Lodash通过降低array、number、objects、string等等的使用难度从而让JavaScript变得简单。Lodash的模块方法&#xff0c;非常适用于&#xff1a; 遍历array、object 和 string对值进行操作和检测创建符合功能的…

字符个数统计(同类型只统计一次)

思路&#xff1a;因为题目圈定出现的字符都是 ascii 值小于等于127的字符&#xff0c;因此只需要定义一个标记数组大小为128 &#xff0c;然后将字符作为数组下标在数组中进行标记&#xff0c;若数组中没有标记过表示第一次出现&#xff0c;进行计数&#xff0c;否则表示重复字…

简单线性回归:预测事物间简单关系的利器

文章目录 &#x1f340;简介&#x1f340;什么是简单线性回归&#xff1f;&#x1f340;简单线性回归的应用场景使用步骤&#xff1a;注意事项&#xff1a; &#x1f340;代码演示&#x1f340;结论 &#x1f340;简介 在数据科学领域&#xff0c;线性回归是一种基本而强大的统…

Kali Linux助您网络安全攻防实战

Kali Linux&#xff1a;黑客与防御者的神器 Kali Linux是一款专为网络安全测试和攻防实践而设计的操作系统。它汇集了大量的安全工具&#xff0c;可以用于渗透测试、漏洞扫描、密码破解等任务&#xff0c;不仅为黑客提供了强大的攻击能力&#xff0c;也为安全防御者提供了测试和…

Kafka 入门到起飞 - 什么是 HW 和 LEO?何时更新HW和LEO呢?

上文我们已经学到&#xff0c; 一个Topic&#xff08;主题&#xff09;会有多个Partition&#xff08;分区&#xff09;为了保证高可用&#xff0c;每个分区有多个Replication&#xff08;副本&#xff09;副本分为Leader 和 Follower 两个角色&#xff0c;Follower 从Leader同…

爬虫逆向实战(十八)--某得科技登录

一、数据接口分析 主页地址&#xff1a;某得科技 1、抓包 通过抓包可以发现数据接口是AjaxLogin 2、判断是否有加密参数 请求参数是否加密&#xff1f; 查看“载荷”模块可以发现有一个password加密参数和一个__RequestVerificationToken 请求头是否加密&#xff1f; 无…

【Linux】Reactor模式

Reactor模式 Reactor模式的定义 Reactor反应器模式&#xff0c;也叫做分发者模式或通知者模式&#xff0c;是一种将就绪事件派发给对应服务处理程序的事件设计模式。 Reactor模式的角色构成 Reactor主要由以下五个角色构成&#xff1a; reactor模式的角色 角色解释Handle(句…