当前位置: 首页 > news >正文

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中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。


 


 

相关文章:

webSocket 开发

1 认识webSocket WebSocket_ohana&#xff01;的博客-CSDN博客 一&#xff0c;什么是websocket WebSocket是HTML5下一种新的协议&#xff08;websocket协议本质上是一个基于tcp的协议&#xff09;它实现了浏览器与服务器全双工通信&#xff0c;能更好的节省服务器资源和带宽…...

c#设计模式-结构型模式 之 代理模式

前言 由于某些原因需要给某对象提供一个代理以控制对该对象的访问。这时&#xff0c;访问对象不适合或者不能直接 引用目标对象&#xff0c;代理对象作为访问对象和目标对象之间的中介。在学习代理模式的时候&#xff0c;可以去了解一下Aop切面编程AOP切面编程_aop编程…...

openpnp - 自动换刀的设置

文章目录 openpnp - 自动换刀的设置概述笔记采用的openpnp版本自动换刀库的类型选择自动换刀设置前的注意事项先卸掉吸嘴座上所有的吸嘴删掉所有的吸嘴设置自动换刀的视觉识别设置吸嘴座为自动换刀 - 以N1为例备注补充 - 吸嘴轴差个0.3mm, 就有可能怼坏吸嘴END openpnp - 自动换…...

《HeadFirst设计模式(第二版)》第十章代码——状态模式

如下图所示&#xff0c;这是一个糖果机的状态机图&#xff0c;要求使用代码实现&#xff1a; 初始版本&#xff1a; package Chapter10_StatePattern.Origin;/*** Author 竹心* Date 2023/8/19**/public class GumballMachine {final static int SOLD_OUT 0;final static int…...

day-25 代码随想录算法训练营(19)回溯part02

216.组合总和||| 思路&#xff1a;和上题一样&#xff0c;差别在于多了总和&#xff0c;但是数字局限在1-9 17.电话号码的字母组合 思路&#xff1a;先纵向遍历第i位电话号码对于的字符串&#xff0c;再横向递归遍历下一位电话号码 93.复原IP地址 画图分析&#xff1a; 思…...

PG逻辑备份与恢复

文章目录 创建测试数据pg_dump 备份pg_restore 恢复pg_restore 恢复并行备份的文件PG 只导出指定函数 创建测试数据 drop database if exists test; create database test ; \c test create table t1(id int primary key); create table t2(id serial primary key, name varch…...

图数据库_Neo4j和SpringBoot整合使用_实战创建明星关系图谱---Neo4j图数据库工作笔记0010

然后我们再来看一下这个明星关系图谱 可以看到这里 这个是原来的startRelation 我们可以写CQL去查询对应的关系 可以看到,首先查询出来以后,然后就可以去创建 我们可以把写的创建明星关系的CQL,拿到 springboot中去执行 可以看到,这里我们先写一个StarRelationRepository,然…...

Linux网络编程:Socket套接字编程(Server服务器 Client客户端)

文章目录&#xff1a; 一&#xff1a;定义和流程分析 1.定义 2.流程分析 3.网络字节序 二&#xff1a;相关函数 IP地址转换函数inet_pton inet_ntop&#xff08;本地字节序 网络字节序&#xff09; socket函数(创建一个套接字) bind函数(给socket绑定一个服务器地址结…...

Mac OS下应用Python+Selenium实现web自动化测试

在Mac环境下应用PythonSelenium实现web自动化测试 在这个过程中要注意两点&#xff1a; 1.在终端联网执行命令“sudo pip install –U selenium”如果失败了的话&#xff0c;可以尝试用命令“sudo easy_install selenium”来安装selenium; 2.安装好PyCharm后新建project&…...

每天一道leetcode:934. 最短的桥(图论中等广度优先遍历)

今日份题目&#xff1a; 给你一个大小为 n x n 的二元矩阵 grid &#xff0c;其中 1 表示陆地&#xff0c;0 表示水域。 岛 是由四面相连的 1 形成的一个最大组&#xff0c;即不会与非组内的任何其他 1 相连。grid 中 恰好存在两座岛 。 你可以将任意数量的 0 变为 1 &#…...

【学习日记】【FreeRTOS】FreeRTOS 移植到 STM32F103C8

前言 本文基于野火 FreeRTOS 教程&#xff0c;内容是关于 FreeRTOS 官方代码的移植的注意事项&#xff0c;并将野火例程中 STM32F103RC 代码移植到 STM32F103C8。 一、FreeRTOS V9.0.0 源码的获取 两个下载链接&#xff1a; 官 网 代码托管 二、源码文件夹内容简介 Source…...

Qt 屏幕偶发性失灵

项目场景: 基于NXP i.mx7的Qt应用层项目开发,通过goodix使用触摸屏,走i2c协议。 问题描述 触摸屏使用过程中意外卡死,现场分为多种: i2c总线传输错误,直观表现为触摸屏无效,任何与触摸屏挂接在同一总线上的i2c设备,均受到干扰,并且在传输过程中内核报错以下代码: G…...

如何在pycharm中指定GPU

如何在pycharm中指定GPU 作者:安静到无声 个人主页 目录 如何在pycharm中指定GPU打开编辑配置点击环境变量添加GPU配置信息推荐专栏在Pycharm运行程序的时候,有时候需要指定GPU,我们可以采用以下方式进行设置: 打开编辑配置 点击环境变量 添加GPU配置信息 添加名称:CU…...

C#判断字符串中有没有字母,正则表达式、IsLetter

要判断字符串中是否包含字母&#xff0c;可以使用正则表达式或者循环遍历字符串的方式。 方法一&#xff1a;使用正则表达式 using System.Text.RegularExpressions;string input "Hello123"; bool containsLetter Regex.IsMatch(input, "[a-zA-Z]");上…...

Jtti:Ubuntu怎么限制指定端口和IP访问

在 Ubuntu 系统中&#xff0c;可以使用防火墙规则来限制特定的端口和IP访问。常用的防火墙管理工具是 iptables&#xff0c;以下是使用 iptables 来限制指定端口和IP访问的步骤&#xff1a; 安装 iptables&#xff1a; 如果系统中没有安装 iptables&#xff0c;可以使用以下命…...

机器学习/深度学习需要掌握的linux基础命令

很多深度学习/机器学习/数据分析等领域&#xff08;或者说大多数在Python环境下进行操作的领域&#xff09;的初学者入门时是在Windows上进行学习&#xff0c;也得益于如Anaconda等工具把环境管理做的如此友善 但如果想在该领域继续深耕&#xff0c;一定会与Linux操作系统打交…...

C++11 std::async推荐使用 std::launch::async 模式

async真假多线程 std::launch::async真多线程 std::launch::async | std::launch::deferred可能多线程 std::launch::deferred假多线程 枚举变量说明 枚举定义 enum class launch {async 1, // 0b1deferred 2, // 0b10any async | def…...

没有使用springboot 单独使用spring-boot-starter-logging

如果您不使用Spring Boot框架&#xff0c;但想单独使用Spring Boot Starter Logging&#xff0c;您可以按照以下步骤进行&#xff1a; 1. 添加Maven依赖&#xff1a; <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boo…...

创建Azure资源锁

锁的介绍 在Azure中&#xff0c;资源锁是一种用于保护订阅、资源组或者单个资源的机制。它可以防止对受锁定的资源进行删除或修改操作&#xff0c;帮助确保资源的连续可用性和安全性。 Azure中的资源锁可以分为两种类型&#xff1a; 删除锁&#xff08;CanNotDelete&#xf…...

卷积神经网络教程 (CNN) – 使用 TensorFlow 在 Python 中开发图像分类器

在这篇博客中,让我们讨论什么是卷积神经网络 (CNN) 以及 卷积神经网络背后的架构——旨在解决 图像识别系统和分类问题。 卷积神经网络在图像和视频识别、推荐系统和自然语言处理方面有着广泛的应用。 目录 计算机如何读取图像? 为什么不是全连接网络?...

业务系统对接大模型的基础方案:架构设计与关键步骤

业务系统对接大模型&#xff1a;架构设计与关键步骤 在当今数字化转型的浪潮中&#xff0c;大语言模型&#xff08;LLM&#xff09;已成为企业提升业务效率和创新能力的关键技术之一。将大模型集成到业务系统中&#xff0c;不仅可以优化用户体验&#xff0c;还能为业务决策提供…...

大数据学习栈记——Neo4j的安装与使用

本文介绍图数据库Neofj的安装与使用&#xff0c;操作系统&#xff1a;Ubuntu24.04&#xff0c;Neofj版本&#xff1a;2025.04.0。 Apt安装 Neofj可以进行官网安装&#xff1a;Neo4j Deployment Center - Graph Database & Analytics 我这里安装是添加软件源的方法 最新版…...

rknn优化教程(二)

文章目录 1. 前述2. 三方库的封装2.1 xrepo中的库2.2 xrepo之外的库2.2.1 opencv2.2.2 rknnrt2.2.3 spdlog 3. rknn_engine库 1. 前述 OK&#xff0c;开始写第二篇的内容了。这篇博客主要能写一下&#xff1a; 如何给一些三方库按照xmake方式进行封装&#xff0c;供调用如何按…...

Java 8 Stream API 入门到实践详解

一、告别 for 循环&#xff01; 传统痛点&#xff1a; Java 8 之前&#xff0c;集合操作离不开冗长的 for 循环和匿名类。例如&#xff0c;过滤列表中的偶数&#xff1a; List<Integer> list Arrays.asList(1, 2, 3, 4, 5); List<Integer> evens new ArrayList…...

【SpringBoot】100、SpringBoot中使用自定义注解+AOP实现参数自动解密

在实际项目中,用户注册、登录、修改密码等操作,都涉及到参数传输安全问题。所以我们需要在前端对账户、密码等敏感信息加密传输,在后端接收到数据后能自动解密。 1、引入依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId...

关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案

问题描述&#xff1a;iview使用table 中type: "index",分页之后 &#xff0c;索引还是从1开始&#xff0c;试过绑定后台返回数据的id, 这种方法可行&#xff0c;就是后台返回数据的每个页面id都不完全是按照从1开始的升序&#xff0c;因此百度了下&#xff0c;找到了…...

江苏艾立泰跨国资源接力:废料变黄金的绿色供应链革命

在华东塑料包装行业面临限塑令深度调整的背景下&#xff0c;江苏艾立泰以一场跨国资源接力的创新实践&#xff0c;重新定义了绿色供应链的边界。 跨国回收网络&#xff1a;废料变黄金的全球棋局 艾立泰在欧洲、东南亚建立再生塑料回收点&#xff0c;将海外废弃包装箱通过标准…...

深入解析C++中的extern关键字:跨文件共享变量与函数的终极指南

&#x1f680; C extern 关键字深度解析&#xff1a;跨文件编程的终极指南 &#x1f4c5; 更新时间&#xff1a;2025年6月5日 &#x1f3f7;️ 标签&#xff1a;C | extern关键字 | 多文件编程 | 链接与声明 | 现代C 文章目录 前言&#x1f525;一、extern 是什么&#xff1f;&…...

多模态大语言模型arxiv论文略读(108)

CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文标题&#xff1a;CROME: Cross-Modal Adapters for Efficient Multimodal LLM ➡️ 论文作者&#xff1a;Sayna Ebrahimi, Sercan O. Arik, Tejas Nama, Tomas Pfister ➡️ 研究机构: Google Cloud AI Re…...

关键领域软件测试的突围之路:如何破解安全与效率的平衡难题

在数字化浪潮席卷全球的今天&#xff0c;软件系统已成为国家关键领域的核心战斗力。不同于普通商业软件&#xff0c;这些承载着国家安全使命的软件系统面临着前所未有的质量挑战——如何在确保绝对安全的前提下&#xff0c;实现高效测试与快速迭代&#xff1f;这一命题正考验着…...