WebSocket快速入门

WebSocket

借鉴:

https://blog.csdn.net/weixin_45747080/article/details/117477006

https://cloud.tencent.com/developer/article/1887095

简介

WebSocket 是一种网络传输协议,可在单个 TCP 连接上进行全双工通信,位于 OSI 模型的应用层。WebSocket 协议在 2011 年由 IETF 标准化为 RFC 6455,后由 RFC 7936 补充规范。

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

优点:

  • *1)*较少的控制开销:在连接创建后,服务器和客户端之间交换数据时,用于协议控制的数据包头部相对较小;
  • *2)*更强的实时性:由于协议是全双工的,所以服务器可以随时主动给客户端下发数据。相对于 HTTP 请求需要等待客户端发起请求服务端才能响应,延迟明显更少;
  • *3)*保持连接状态:与 HTTP 不同的是,WebSocket 需要先创建连接,这就使得其成为一种有状态的协议,之后通信时可以省略部分状态信息;
  • *4)*更好的二进制支持:WebSocket 定义了二进制帧,相对 HTTP,可以更轻松地处理二进制内容;
  • *5)*可以支持扩展:WebSocket 定义了扩展,用户可以扩展协议、实现部分自定义的子协议。

前后端使用WebSocket

img

服务端利用SpringBoot启动一个WebSocket服务,同时暴露出该服务的应用路径,客户端则利用该应用路径进行连接。需要注意的是,在服务端只需要启动一个WebSocket服务,而每一个客户端就是一个WebSocket应用。就有点像:服务端是古老的电话接线员,而客户端就是打电话的人。假如用户A想要给用户B打电话就需要先打电话到接线员那,然后接线员再接通用户B。不过WebSocket可以实现广播和私聊。

前端初始化WebSocket对象

不需要引入第三方依赖包,直接使用js自带的WebSocket对象。

  1. 创建WebSocket对象

    const ws = new WebSocket('ws://localhost:8000/websocket/')
    

    ws://jdbc://http://一样都是协议名,同样的,Websocket还支持更加安全的wss:///websocket即该服务的应用路径名

  2. onopen事件监听

    与服务端连接成功会触发。

    webSocketOnOpen(e){console.log('与服务端连接打开->',e)
    },
    
  3. onerror事件监听

    与服务端连接异常时触发。

    webSocketOnError(e){console.log('与服务端连接异常->',e)
    },
    
  4. onclose事件监听

    与服务端连接关闭时触发。

    webSocketOnClose(e){console.log('与服务端连接关闭->',e)
    },
    
  5. onmessage事件监听

    接收到来自服务端的消息时触发。

    webSocketOnMessage(e){console.log('来自服务端的消息->',e)
    },
    

一个完整的WebSocket对象应该具备以上属性,同时需要将以上属性跟WebSocket对象绑定。

使用原生JS初始化WebSocket对象演示

const ws = new WebSocket(webSocketUrl)
//onopen事件监听
ws.addEventListener('open',e=>{console.log('与服务端连接打开->',e)
},false)
//onclose事件监听
ws.addEventListener('close',e=>{console.log('与服务端连接关闭->',e)
},false)
//onmessage事件监听
ws.addEventListener('message',e=>{console.log('来自服务端的消息->',e)
},false)
//onerror事件监听
ws.addEventListener('error',e=>{console.log('与服务端连接异常->',e)
},false)

ws对象的addEventListener( )方法,为WebSocket绑定事件监听,从而在各个事件监听中处理事务。

使用Vue初始化WebSocket对象演示

export default {name: "Home",data() {return {webSocketObject: null,}},created() {//初始化WebSocketthis.webSocketInit()},methods: {webSocketInit(){const webSocketUrl = 'ws://localhost:8000/websocket/'+this.usernamethis.webSocketObject = new WebSocket(webSocketUrl);this.webSocketObject.onopen = this.webSocketOnOpenthis.webSocketObject.onmessage = this.webSocketOnMessagethis.webSocketObject.onerror = this.webSocketOnErrorthis.webSocketObject.onclose = this.webSocketOnClose},webSocketOnOpen(e){console.log('与服务端连接打开->',e)},webSocketOnMessage(e){console.log('来自服务端的消息->',e)},webSocketOnError(e){console.log('与服务端连接异常->',e)},webSocketOnClose(e){console.log('与服务端连接关闭->',e)},},
}
</script>

同样的,利用methods分别定义好OnOpen、OnMessage、OnError、OnClose四个事件监听,然后进行初始化并且绑定就可以了。这样就完成了WebSocket对象以及事件监听的初始化。

后端初始化WebSocket对象

  • SpringBoot自带的WebSocket有以下5个常用注解:

@ServerEndpoint

暴露出的ws应用的路径,支持RESTful风格传参,类似/websocket/{username}

@OnOpen

与当前客户端连接成功,有入参Session对象(当前连接对象),同时可以利用@PathParam()获取上述应用路径中传递的参数,比如@PathParam(“username”) String username。

@OnClose

与当前客户端连接失败,有入参Session对象(当前连接对象),同时也可以利用@PathParam()获取上述应用路径中传递的参数。

@OnError

与当前客户端连接异常,有入参Session对象(当前连接对象)、Throwable对象(异常对象),同时也可以利用@PathParam()获取上述应用路径中传递的参数。

@OnMessage

当前客户端发送消息,有入参Session对象(当前连接对象)、String message对象(当前客户端传递过来的字符串消息)

  • 利用SpringBoot创建项目,需要引入依赖:
<dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
  • 在application.yaml中定义好该服务的端口号:
server:port: 8000
  • 利用自定义配置类开启WebSocket:
package cn.wqk.serverwebsocket.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;@Configuration
@EnableWebSocket
public class WebSocketConfig {@Beanpublic ServerEndpointExporter serverEndpointExporter(){return new ServerEndpointExporter();}}
  • 定义Websocket主业务类:
package cn.wqk.serverwebsocket.socket;import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Date;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicInteger;@Component
@Slf4j
@ServerEndpoint("/websocket/{username}") //暴露的ws应用的路径
public class WebSocket {/*** 客户端与服务端连接成功* @param session* @param username*/@OnOpenpublic void onOpen(Session session,@PathParam("username") String username){/*do something for onOpen与当前客户端连接成功时*/}/*** 客户端与服务端连接关闭* @param session* @param username*/@OnClosepublic void onClose(Session session,@PathParam("username") String username){/*do something for onClose与当前客户端连接关闭时*/}/*** 客户端与服务端连接异常* @param error* @param session* @param username*/@OnErrorpublic void onError(Throwable error,Session session,@PathParam("username") String username) {}/*** 客户端向服务端发送消息* @param message* @param username* @throws IOException*/@OnMessagepublic void onMsg(Session session,String message,@PathParam("username") String username) throws IOException {/*do something for onMessage收到来自当前客户端的消息时*/}
}

前后端联动实现简单聊天室

前端在OnMessage事件监听中接收到来自后端的消息,然后进行处理(展示在页面上);同样的,后端也是在OnMessage中接收到来自前端的消息,然后进行处理(发送到所有客户端)。

前端

  1. 定义一个输入框,再定义一个按钮:接收消息并且发送
<inputtype="text"v-model="sendMessage"placeholder="请输入你要发送的消息">
<button @click="handleSendButton()">发送</button>

**注意:**直接利用websocket对象的send()方法发送消息,前后端数据传输利用JSON字符串,所以发送的时候需要将对象转为JSON字符串。

  1. 定义一个列表:用于展示聊天信息
<table><thead><tr><th>消息编号</th><th>发送者</th><th>发送时间</th><th>发送内容</th></tr></thead><tbody><tr v-for="item in messageList" :key="item.time"><td>{{ item.id }}</td><td>{{ item.username }}</td><td>{{ new Date(item.time).toLocaleTimeString() }}</td><td>{{ item.message }}</td></tr></tbody>
</table>
  1. 当客户端的onMessage接收到消息后就把消息展示到列表中
webSocketOnMessage(e){console.log('来自服务端的消息->',e)const receiveMessage = JSON.parse(e.data);this.messageList.push(receiveMessage)
},

**注意:**通过console.log(e)不难发现,来自服务端的消息是存储在e.data中的,并且是JSON字符串,所以我们需要将它转为JSON对象。

此时已经完成了前端发送消息并且接收消息且展示消息了。

后端

接收消息并且群发消息:

@OnMessage
public void onMsg(Session session,String message,@PathParam("username") String username) throws IOException {/*do something for onMessage收到来自当前客户端的消息时*/sendAllMessage(message);
}
//向所有客户端发送消息(广播)
private void sendAllMessage(String message){Set<String> sessionIdSet = onlineClientMap.keySet(); //获得Map的Key的集合for (String sessionId : sessionIdSet) { //迭代Key集合Session session = onlineClientMap.get(sessionId); //根据Key得到valuesession.getAsyncRemote().sendText(message); //发送消息给客户端}
}

前端完整代码

<template><div><table><thead><tr><th>消息编号</th><th>发送者</th><th>发送时间</th><th>发送内容</th></tr></thead><tbody><tr v-for="item in messageList" :key="item.time"><td>{{ item.id }}</td><td>{{ item.username }}</td><td>{{ new Date(item.time).toLocaleTimeString() }}</td><td>{{ item.message }}</td></tr></tbody></table><inputtype="text"v-model="sendMessage"placeholder="请输入你要发送的消息"><button @click="handleSendButton()">发送</button><button @click="handleLogoutButton()">退出</button></div>
</template><script>import {getUsername,removeUsername
} from "@/utils/username";export default {name: "Home",data() {return {webSocketObject: null,username: '',messageList: [],sendMessage: ''}},created() {//从localStorage中获得usernamethis.username = getUsername()//如果username不存在返回到登录页面if (!this.username){this.$router.push({name: 'Login'})}//初始化WebSocketthis.webSocketInit()},beforeDestroy() {this.webSocketObject.close();//在该组件销毁时关闭该连接以节约资源},methods: {webSocketInit(){const webSocketUrl = 'ws://localhost:8000/websocket/'+this.usernamethis.webSocketObject = new WebSocket(webSocketUrl);this.webSocketObject.onopen = this.webSocketOnOpenthis.webSocketObject.onmessage = this.webSocketOnMessagethis.webSocketObject.onerror = this.webSocketOnErrorthis.webSocketObject.onclose = this.webSocketOnClose},webSocketOnOpen(e){console.log('与服务端连接打开->',e)},webSocketOnMessage(e){console.log('来自服务端的消息->',e)const receiveMessage = JSON.parse(e.data);this.messageList.push(receiveMessage)},webSocketOnError(e){console.log('与服务端连接异常->',e)},webSocketOnClose(e){console.log('与服务端连接关闭->',e)},handleSendButton() {const username = this.usernameconst message = this.sendMessagethis.webSocketObject.send(JSON.stringify({id: 1,message,username,time: new Date().getTime()}))this.sendMessage = ''},handleLogoutButton(){removeUsername() //清除username然后断开连接this.webSocketObject.close();this.$router.push({name: 'Login'})}},
}
</script>

这里采用的是在上一个页面获取到用户的用户名然后存储到LocalStorage中。

后端完整代码

package cn.wqk.serverwebsocket.socket;import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Date;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.atomic.AtomicInteger;@Component
@Slf4j
@ServerEndpoint("/websocket/{username}") //暴露的ws应用的路径
public class WebSocket {/** 当前在线客户端数量(线程安全的) */private static AtomicInteger onlineClientNumber = new AtomicInteger(0);/** 当前在线客户端集合(线程安全的):以键值对方式存储,key是连接的编号,value是连接的对象 */private static Map<String ,Session> onlineClientMap = new ConcurrentHashMap<>();/*** 客户端与服务端连接成功* @param session* @param username*/@OnOpenpublic void onOpen(Session session,@PathParam("username") String username){/*do something for onOpen与当前客户端连接成功时*/onlineClientNumber.incrementAndGet();//在线数+1onlineClientMap.put(session.getId(),session);//添加当前连接的sessionlog.info("时间[{}]:与用户[{}]的连接成功,当前连接编号[{}],当前连接总数[{}]",new Date().toLocaleString(),username,session.getId(),onlineClientNumber);}/*** 客户端与服务端连接关闭* @param session* @param username*/@OnClosepublic void onClose(Session session,@PathParam("username") String username){/*do something for onClose与当前客户端连接关闭时*/onlineClientNumber.decrementAndGet();//在线数-1onlineClientMap.remove(session.getId());//移除当前连接的sessionlog.info("时间[{}]:与用户[{}]的连接关闭,当前连接编号[{}],当前连接总数[{}]",new Date().toLocaleString(),username,session.getId(),onlineClientNumber);}/*** 客户端与服务端连接异常* @param error* @param session* @param username*/@OnErrorpublic void onError(Throwable error,Session session,@PathParam("username") String username) {/*do something for onError与当前客户端连接异常时*/error.printStackTrace();}/*** 客户端向服务端发送消息* @param message* @param username* @throws IOException*/@OnMessagepublic void onMsg(Session session,String message,@PathParam("username") String username) throws IOException {/*do something for onMessage收到来自当前客户端的消息时*/log.info("时间[{}]:来自连接编号为[{}]的消息:[{}]",new Date().toLocaleString(),session.getId(),message);sendAllMessage(message);}//向所有客户端发送消息(广播)private void sendAllMessage(String message){Set<String> sessionIdSet = onlineClientMap.keySet(); //获得Map的Key的集合for (String sessionId : sessionIdSet) { //迭代Key集合Session session = onlineClientMap.get(sessionId); //根据Key得到valuesession.getAsyncRemote().sendText(message); //发送消息给客户端}}}

流程图

img

总结

服务端启动一个WebSocket服务,初始化应用路径、连接打开OnOpen、连接关闭OnClose、连接异常OnError、收到消息OnMessage。OnMessage中需要对来自客户端的消息进行对应的处理,比如广播或者私聊给具体某人。

客户端需要利用WebSocket对象(定义好url),然后初始化OnOpen、OnClose、OnError、OnMessage。OnMessage中需要对来自服务端的消息进行处理,如展示到页面上等。同时还可以需用WebSocket对象的send()方法来给服务端发送数据,并且切记在页面关闭时需要将该连接关闭(利用WebSocket对象的close()方法)。

项目地址

博客作者给的github项目可以直接跑通,良心:https://github.com/FanGaoXS/websocket-demo

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

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

相关文章

LangChain 14 SequencialChain链接不同的组件

LangChain系列文章 LangChain 实现给动物取名字&#xff0c;LangChain 2模块化prompt template并用streamlit生成网站 实现给动物取名字LangChain 3使用Agent访问Wikipedia和llm-math计算狗的平均年龄LangChain 4用向量数据库Faiss存储&#xff0c;读取YouTube的视频文本搜索I…

【vue】v-model在表单元素上的应用

表单元素&#xff1a; https://blog.csdn.net/m0_67930426/article/details/134655644 使用模板 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><title>Title</title> </head><body>&l…

通过git上传文件到github仓库

一、新建github仓库 访问github官网&#xff1a;GitHub: Let’s build from here GitHub 点击个人头像&#xff0c;在右侧栏选择Your repositories。 点击New&#xff0c;新建一个github仓库。 创建Repository name仓库名&#xff0c;如果这个仓库名已经创建过的话&#xff…

【MySql】悲观锁和乐观锁的介绍

一、并发控制 当程序中可能出现并发的情况时&#xff0c;就需要保证在并发情况下数据的准确性&#xff0c;以此确保当前用户和其他用户一起操作时&#xff0c;所得到的结果和他单独操作时的结果是一样的。这就叫做并发控制。并发控制的目的是保证一个用户的工作不会对另一个用…

monorepo多项目管理主流实现方式:1.learn + yarn/npm workspace 2.pnpm

npm域级包 随着npm包越来越多&#xff0c;而且包名也只能是唯一的&#xff0c;如果一个名字被别人占了&#xff0c;那你就不能再使用这个名字&#xff1b;假设我想要开发一个utils包&#xff0c;但是张三已经发布了一个utils包&#xff0c;那我的包名就不能叫utils了&#xff…

每天五分钟计算机视觉:LeNet是最早用于数字识别的卷积神经网络

LeNet 假设你有一张 32321 的图片,然后使用 6 个 55的过滤器,步幅为 1,padding 为 0,输出结果为 28286。图像尺寸从 3232 缩小到 2828。 然后进行池化操作,使用平均池化,过滤器的宽度为 2,步幅为 2,图像的尺寸,高度和宽度都缩小了 2 倍,输出结果是一个14146 的图像。…

三十、elasticsearch集群

目录 一、集群的概念 1、节点 2、索引 3、分片和副本 二、集群的架构 三、集群的部署方式 1、单主节点 2、多主节点 3、安全集群 四、搭建ES集群 1、elasticsearch中集群节点有不同的职责划分 2、elasticsearch中的每个节点角色都有自己不同的职责&#xff0c;因此…

Android flutter项目 启动优化实战(一)使用benchmark分析项目

背景描述 启动时间是用户对应用的第一印象&#xff0c;较慢的加载会对用户的留存和互动造成负面影响 在刚上线的B端项目中&#xff1a; 1.提高启动速度能提高整体流程的效率 2.提高首次运行速度能提高应用推广的初体验效果 问题描述 项目刚上线没多久、目前存在冷启动过程存在…

【古月居《ros入门21讲》学习笔记】09_订阅者Subscriber的编程实现

目录 说明&#xff1a; 1. 话题模型 图示 说明 2. 实现过程&#xff08;C&#xff09; 创建订阅者代码&#xff08;C&#xff09; 配置发布者代码编译规则 编译并运行 编译 运行 3. 实现过程&#xff08;Python&#xff09; 创建订阅者代码&#xff08;Python&…

LLM能力与应用全解析

一、简介 经过几年时间的发展&#xff0c;大语言模型&#xff08;LLM&#xff09;已经从新兴技术发展为主流技术。而以大模型为核心技术的产品将迎来全新迭代。大模型除了聊天机器人应用外&#xff0c;能否在其他领域产生应用价值&#xff1f;在回答这个问题前&#xff0c;需要…

产品解读:GreatADM如何快速改造单实例为双主、MGR、读写分离架构?

前言 单机GreatDB/GreatSQL/MySQL将架构调整为多副本复制的好处有哪些&#xff1f;为什么要调整&#xff1f; 性能优化&#xff1a;如果单个GreatDB服务器的处理能力达到瓶颈&#xff0c;可能需要通过主从复制、双主复制或MGR及其他高可用方案来提高整体性能。通过将读请求分发…

STM32 ADC转换器、串口输出

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 前言一、ADC是什么&#xff1f;二、STM32的ADC2.1 认识STM32 ADC2.2转换方式2.3 为什么要校准&#xff1f;2.4 采样时间计算2.5 触发方式2.6 多通道采集解决方案2.7…

手把手教你如何实现List——ArrayList

目录 前言&#xff1a; 线性表 顺序表 接口的实现 一. 打印顺序表 二.新增元素,默认在数组最后新增 三.在 pos 位置新增元素 四.判定是否包含某个元素 五. 查找某个元素对应的位置 六.获取 pos 位置的元素 七.给 pos 位置的元素设为 value 八.删除第一次出现的关键字k…

基于SSM的酒店预订管理系统设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;Vue 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#xff1a;是 目录…

Matlab R2022b 安装成功小记

Matlab R2022b 安装成功小记 前言一、 下载链接二、 安装过程小记 叮嘟&#xff01;这里是小啊呜的学习课程资料整理。好记性不如烂笔头&#xff0c;今天也是努力进步的一天。一起加油进阶吧&#xff01; 前言 windows 10系统之前安装过Matlab R2010b做基础研究&#xff0c;最…

Anaconda离线下载torch与安装包

一、下载离线安装包 命令&#xff1a; pip download 安装包名 -d 安装到文件夹名 -i https://pypi.tuna.tsinghua.edu.cn/simple执行这样的命令就会把安装包的离线文件下载到指定文件夹中。 操作&#xff1a; 打开cmd命令行&#xff0c;并进入相应的目录中。 如果是tor…

k8s中pod的hostport端口突然无法访问故障处理

故障背景&#xff1a; 租户告知生产环境的sftp突然无法访问了&#xff0c;登录环境查看sftp服务运行都是正常的&#xff0c;访问sftp的hostport端口确实不通。 故障处理过程 既然访问不通那就先给服务做个全面检查&#xff0c;看看哪里出了问题&#xff0c;看下sftp日志&#…

Docker基本操作---镜像与容器操作

Docker基本操作---镜像与容器操作 1. 操作镜像1.1 查看镜像1.2 删除镜像1.2.1 删除镜像1.2.2 强制删除镜像1.2.3 删除所有镜像 1.3 启动镜像1.4 常见错误1.4.1 image is being used by stopped container e3b9df6dc6ae 2 操作容器2.1 新建启动容器2.2 查看正在运行的容器2.3 退…

Unity学习笔记11

一、视频播放功能 1.如何让视频在游戏场景中播放&#xff1f; 在Assets目录下添加一个渲染器纹理&#xff0c;步骤&#xff1a;新建→渲染器纹理 首先在创建一个平面&#xff0c;想让视频在平面上显示。在平面上添加一个组件 Video Player 然后将视频文件拖拽到视频剪辑位置上…

黄金比例设计软件Goldie App mac中文版介绍

Goldie App mac是一款测量可视化黄金比例的工具。专门为设计师打造&#xff0c;可以帮助他们在Mac上测量和可视化黄金比例&#xff0c;从而轻松创建出完美、平衡的设计。 Goldie App mac体积小巧&#xff0c;可以驻留在系统的菜单栏之上&#xff0c;随时提供给用户调用。 拥有独…