WebSocket开发

目录

前言

1.介绍

2.原理解析 

3.简单的聊天室搭建

4.点到点消息传输 

总结


前言

WebSocket 是互联网项目中画龙点睛的应用,可以用于消息推送、站内信、在线聊天等业务。


1.介绍

WebSocket 是一种基于 TCP 的新网络协议,它是一种持久化的协议,实现了全双工通信,可以让服务器主动发送消息给客户端。

在 WebSocket 出现之前,要保持消息更新和推送一般采用轮询的方式,例如,开启一个服务进程每隔一段时间去发送请求给另外一个服务,以此获取最新的资源信息。这里都很阻塞请求,性能非常差,也会占用资源。所以考虑使用 WebSocket 来实现,使用连接实现传输,性能很高,整个客户端和服务之间交互的请求过程如图。

由图可知,最开始客户端也需要发起一次 http 请求,然后 WebSocket 协议需要通过已建立的 TCP 连接来传输数据,可见 WebSocket 和 HTTP 请求也有一些交集。但是 WebSocket 只用发起一次 HTTP 请求之后就可以通过回调机制不断地获取数据并进行交互。 

整合 WebSocket 到 SpringBoot 

在 pom.xml 中添加如下依赖:

        <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-websocket</artifactId></dependency>

2.原理解析 

通信协议转换过程: 

服务端 API 

Tomcat的7.0.5 版本开始支持WebSocket,并且实现了Java WebSocket规范。

Java WebSocket应用由一系列的Endpoint组成。Endpoint 是一个java对象,代表WebSocket链接的一端,对于服务端,我们可以视为处理具体WebSocket消息的接口。

我们可以通过两种方式定义Endpoint:

  • 第一种是编程式, 即继承类 javax.websocket.Endpoint并实现其方法。
  • 第二种是注解式, 即定义一个POJO, 并添加 @ServerEndpoint相关注解。

Endpoint实例在WebSocket握手时创建,并在客户端与服务端链接过程中有效,最后在链接关闭时结束。在Endpoint接口中明确定义了与其生命周期相关的方法, 规范实现者确保生命周期的各个阶段调用实例的相关方法。 

服务端接受客户端发送的数据:

编程式注解式
通过添加 MessageHandler 消息处理器来接收消息在定义Endpoint时,通过@OnMessage注解指定接收消息的方法

服务端推送消息给客户端:

发送消息则由 RemoteEndpoint 完成, 其实例由 Session 维护。

发送消息有2种方式发送消息

  • 通过session.getBasicRemote 获取同步消息发送的实例 , 然后调用其 sendXxx()方法发送消息。
  • 通过session.getAsyncRemote 获取异步消息发送实例,然后调用其 sendXxx() 方法发送消息。

3.简单的聊天室搭建

WebSocket 使用 ws 或 wss 作为通信协议,和 HTTPS 协议类似,其中 wss 表示在 TLS 之上的 WebSocket。一个完整的 URL 如下:ws://example.com/api。实现聊天室的思路可分成以下两种:

  1. 利用 websocket 依赖中的 HandshakeInterceptor 和 WebSocketHandler,实现对应的方法。
  2. 利用注解实现对话的连接、断开、信息发送触发等功能。为了更好地理解 WebSocket 的流程,我们选择第二种方式来实现聊天室的功能。

编写一个配置文件,配置类代码:

package org.example.config;import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;@Configuration
public class WebSocketConfig {/*** 配置 WebSocketEndpointServer** 注入 ServerEndpointExporter 到web启动流程中* @return*/@Beanpublic ServerEndpointExporter serverEndpointExporter(){return new ServerEndpointExporter();}
}

创建 WebSocket 服务端,需要通过注解来实现如下方法,见如下:

事件类型

WebSocket 注解

事件描述

open

@OnOpen

当打开连接后触发

message

@OnMessage

当接收客户端信息时触发

error

@OnError

当通信异常时触发

close

@OnClose

当连接关闭时触发

下面正式编写 WebSocket 服务端代码,代码如下:

package org.example.componment;import com.alibaba.fastjson.JSON;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.example.chatobject.Message;
import org.example.config.WebSocketConfig;
import org.springframework.stereotype.Component;import javax.servlet.http.HttpSession;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;@Component
@ServerEndpoint(value = "/chat",configurator = GetHttpSessionConfig.class)//标记此类为服务端
public class WebSocketChatSerever {/*使用线程安全的 Map存储会话*/private static Map<String, Session> onlineSession = new ConcurrentHashMap<>();private HttpSession httpSession;/*当打开连接的时候,添加会话和更新在线人数*/@OnOpenpublic void onOpen(Session session) {//WebSocket Session,不是httpSession//1.将 session进行保存onlineSession.put(session.getId(), session);//2.广播消息推送给所有用户sendMessageToAll(Message.toJsonResult(Message.ENTER, "", "", onlineSession.size()));}
/*    @OnOpenpublic void onOpen(Session session,EndpointConfig endpointConfig) {//和 http ServerEndpointConfig 是同一个对象//1.将 session进行保存this.httpSession = (HttpSession) endpointConfig.getUserProperties().get(HttpSession.class.getName());String user = (String) this.httpSession.getAttribute("user");onlineSession.put(user, session);//2.广播消息推送给所有用户sendMessageToAll(Message.toJsonResult(Message.ENTER, "", "", onlineSession.size()));}*//*** 发送消息给所有人* @param msg*/private static void sendMessageToAll(String msg) {onlineSession.forEach((id, session) -> {try {//发送同步消息的方法 getBasicRemote,getAsyncRemote发送异步的消息。session.getBasicRemote().sendText(msg);} catch (IOException e) {e.printStackTrace();}});}/*    @OnMessagepublic static void sendMessageToAll(String msg, Session session, @PathParam("nickname")String nickname) {log.info("来自客户端:{} 发来的消息:{}",nickname,msg);SocketConfig socketConfig;ObjectMapper objectMapper = new ObjectMapper();try {socketConfig = objectMapper.readValue(msg, SocketConfig.class);if (socketConfig.getType() == 1){//私聊socketConfig.setFormUser(session.getId());Session fromSession = map.get(socketConfig.getFormUser());Session toSession = map.get(socketConfig.getToUser());if (toSession != null){// 接收者存在,发送以下消息给接受者和发送者fromSession.getAsyncRemote().sendText(nickname+": "+socketConfig.getMsg());toSession.getAsyncRemote().sendText(nickname+": "+socketConfig.getMsg());}else {fromSession.getAsyncRemote().sendText("频道号不存在或者对方不在线");}}else {//群聊broadcast(nickname+": "+socketConfig.getMsg());}} catch (Exception e) {log.error("发送消息出错");e.printStackTrace();}}*//*** 浏览器发送消息给服务端,该方法会被调用* 张三 ---> 李四*/@OnMessagepublic void OnMessage(String msg){//1.消息推送给指定的用户Message message = JSON.parseObject(msg, Message.class);//2.获取接收方的用户名//3.获取接收方用户对象的session对象//4.发送消息  session.getAsyncRemote().sendText(msg);//未实现私聊,先这样onlineSession.forEach((id, session) -> {try {//发送同步消息的方法 getBasicRemote,getAsyncRemote发送异步的消息。session.getBasicRemote().sendText(msg);} catch (IOException e) {e.printStackTrace();}});}/*当关闭连接,移除会话并减少在线人数。*/@OnClosepublic void onClose(Session session) {//1.从 onlineSession 中剔除当前的 session 对象onlineSession.remove(session.getId());//2.通知所有用户,当前用户下线了。sendMessageToAll(Message.toJsonResult(Message.QUIT, "", "下线啦!", onlineSession.size()));}/*当通信发生异常时,打印错误日志*/@OnErrorpublic void OnError(Session session,Throwable error){error.printStackTrace();}
}

上面的代码就是对 onError、onMessage、onOpen 注解进行编写接口,使用线程安全的 Map 来存储对话信息,根据不同的操作对在线人数和当前会话用户进行删改。sessionid 对应每一个连接的用户,由于没有注册,所有保存用户信息用处不大。其中 Message 对象是对聊天消息进行封装的数据类,具体实现代码如下:

package org.example.chatobject;import com.alibaba.fastjson.JSON;/*** 聊天对象*/
public class Message {//进入聊天public static final String ENTER = "ENTER";//聊天public static final String TALK = "TALK";//退出聊天public static final String QUIT = "QUIT";//消息类型private String type;//发送人private String username;//发送消息private String message;//在线人数private int onlineCount;//返回处理后的 json 结果public Message(String type, String username, String message, int onlineCount) {this.type = type;this.username = username;this.message = message;this.onlineCount = onlineCount;}public static String toJsonResult(String type, String username, String message, int onlineCount) {return JSON.toJSONString(new Message(type, username, message, onlineCount));}public String getType() {return type;}public void setType(String type) {this.type = type;}public String getUsername() {return username;}public void setUsername(String username) {this.username = username;}public String getMessage() {return message;}public void setMessage(String message) {this.message = message;}public int getOnlineCount() {return onlineCount;}public void setOnlineCount(int onlineCount) {this.onlineCount = onlineCount;}
}

为了更好地编写前端页面和处理参数(JSON 格式为主)传递,添加如下配置到 pom.xml:

        <dependency><groupId>com.alibaba</groupId><artifactId>fastjson</artifactId><version>2.0.25</version></dependency><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-thymeleaf</artifactId></dependency>

然后编写一个 Controller 文件来实现简单地路由和逻辑,具体实现代码如下:

package org.example.controller;import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;@RestController
public class ChatController {@GetMapping("/login")public ModelAndView login() {return new ModelAndView("/login");}@GetMapping("/chat")public ModelAndView index(String username, String password, HttpServletRequest request) {return new ModelAndView("/chat");}}

编写前端页面,运行项目,访问登录页面 http://localhost:8080/login,如图所示:

错误页面:

​ 

聊天页面,如图:

​在前端中也需要编写 WebSocket 的客户端操作,核心代码如下:

<script>/*** WebSocket客户端**/function createWebSocket() {/*** WebSocket客户端*/var serviceUri = 'ws://localhost:8080/chat';var webSocket = new WebSocket(serviceUri);/*** 打开连接的时候*/webSocket.onopen = function (event) {console.log('websocket打开连接....');};/*** 接受服务端消息*/webSocket.onmessage = function (event) {console.log('websocket:%c' + event.data);// 获取服务端消息var message = JSON.parse(event.data) || {};var $messageContainer = $('.message_container');if (message.type === 'TALK') {var insertOneHtml = '<div class="mdui-card" style="margin: 10px 0;">' +'<div class="some-class">' +'<div class="message_content">' + message.username + ":" + message.message + '</div>' +'</div></div>';$messageContainer.append(insertOneHtml);}// 更新在线人数$('#chat_num').text(message.onlineCount);//防止刷屏var $cards = $messageContainer.children('.mdui-card:visible').toArray();if ($cards.length > 5) {$cards.forEach(function (item, index) {index < $cards.length - 5 && $(item).slideUp('fast');});}};/*** 关闭连接*/webSocket.onclose = function (event) {console.log('WebSocket关闭连接');};/*** 通信失败*/webSocket.onerror = function (event) {console.log('WebSocket发生异常');};return webSocket;}var webSocket = createWebSocket();/*** 通过WebSocket对象发送消息给服务端*/function sendMsgToServer() {var $message = $('#msg');if ($message.val()) {webSocket.send(JSON.stringify({username: $('#username').text(), msg: $message.val()}));$message.val(null);}}/*** 清屏*/function clearMsg() {$(".message_container").empty();}/*** 使用ENTER发送消息*/document.onkeydown = function (event) {var e = event || window.event || arguments.callee.caller.arguments[0];e.keyCode === 13 && sendMsgToServer();};</script>

其中,createWebSocket 方法是专门用来创建 WebSocket 对象的,并且封装了处理服务器的消息、错误处理、连接服务器等操作。

解析:

websocket对象创建:

let  ws  =  new WebSocket(URL);

websocket对象相关事件 :

事件

事件处理程序

描述

open

ws.onopen

连接建立时触发

message

ws.onmessage

客户端接收到服务器发送的数据时触发

close

ws.onclose

连接关闭时触发

websocket对象提供的方法 :

方法名称

描述

send()

通过websocket对象调用该方法发送数据给服务端

4.点到点消息传输 

所谓点到点消息传输,其实就是我们常说的私聊功能。一个用户相当于一个连接,两个用户之间在一个通道中进行消息传输,私密地聊天。

在之前项目地基础上,进一步引入一个新的概念:频道号。就像听广播一样,必须要在相同的频道上才能听到消息,同样地,两个用户必须在相同的频道上才能接受到对方发来的消息。

于是改写 WebSocket 类中的 OnMessage 方法,具体代码如下:

    @OnMessagepublic static void sendMessageToAll(String msg, Session session, @PathParam("nickname")String nickname) {log.info("来自客户端:{} 发来的消息:{}",nickname,msg);SocketConfig socketConfig;ObjectMapper objectMapper = new ObjectMapper();try {socketConfig = objectMapper.readValue(msg, SocketConfig.class);if (socketConfig.getType() == 1){//私聊socketConfig.setFormUser(session.getId());Session fromSession = map.get(socketConfig.getFormUser());Session toSession = map.get(socketConfig.getToUser());if (toSession != null){// 接收者存在,发送以下消息给接受者和发送者fromSession.getAsyncRemote().sendText(nickname+": "+socketConfig.getMsg());toSession.getAsyncRemote().sendText(nickname+": "+socketConfig.getMsg());}else {fromSession.getAsyncRemote().sendText("频道号不存在或者对方不在线");}}else {//群聊broadcast(nickname+": "+socketConfig.getMsg());}} catch (Exception e) {log.error("发送消息出错");e.printStackTrace();}}

总结

了解了 WebSocket 在Spring Boot 中的整合和实践,以群聊和私聊两大功能为例,具体讲解了相应的实现方法。对 WebSocket 的常用方法的封装,进行了详细的介绍。

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

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

相关文章

Java精品项目源码新基于协同过滤算法的旅游推荐系统(编号V69)

Java精品项目源码新基于协同过滤算法的旅游推荐系统(编号V69) 大家好&#xff0c;小辰今天给大家介绍一个基于协同过滤算法的旅游推荐系统

056:vue工具 --- CSS在线格式化

第056个 查看专栏目录: VUE ------ element UI 专栏目标 在vue和element UI联合技术栈的操控下&#xff0c;本专栏提供行之有效的源代码示例和信息点介绍&#xff0c;做到灵活运用。 &#xff08;1&#xff09;提供vue2的一些基本操作&#xff1a;安装、引用&#xff0c;模板使…

Netty应用(七) ----MQTT编解码器

目录 0.前言1. MqttEncoder--编码器1.1 构造方法1.2 encodeConnectMessage -- 连接消息1.3 encodeConnAckMessage - 确认连接1.4 encodePublishMessage -- 发布消息1.5 encodeSubscribeMessage - 订阅主题1.6 encodeUnsubscribeMessage - 取消订阅1.7 encodeSubAckMessage - 订…

HarmonyOS应用开发实战—开箱即用的应用首页页面【ArkTS】【鸿蒙专栏-34】

一.HarmonyOS应用开发实战—开箱即用的应用首页页面【ArkTS】【鸿蒙专栏-34】 1.1 项目背景 HarmonyOS(鸿蒙操作系统)是华为公司推出的一种分布式操作系统。它被设计为一种全场景、全连接的操作系统,旨在实现在各种设备之间的无缝协同和共享,包括智能手机、平板电脑、智能…

计算机网络(四)

九、网络安全 &#xff08;一&#xff09;什么是网络安全&#xff1f; A、网络安全状况 分布式反射攻击逐渐成为拒绝攻击的重要形式 涉及重要行业和政府部门的高危漏洞事件增多。 基础应用和通用软硬件漏洞风险凸显&#xff08;“心脏出血”&#xff0c;“破壳”等&#x…

出国旅游需要注意些什么

出国旅游是一种令人兴奋、令人期待的经历。然而&#xff0c;在进行这种经历之前&#xff0c;有几件事情是需要注意的。本文将为您介绍出国旅游需要注意的一些重要事项。首先&#xff0c;为了确保您的出国旅行顺利进行&#xff0c;您应该提前办理好您的签证和护照。不同国家对于…

【神器】wakatime代码时间追踪工具

文章目录 wakatime简介支持的IDE安装步骤API文档插件费用写在最后 wakatime简介 wakatime就是一个IDE插件&#xff0c;一个代码时间追踪工具。可自动获取码编码时长和度量指标&#xff0c;以产生很多的coding图形报表。这些指标图形可以为开发者统计coding信息&#xff0c;比如…

头部首发优志愿头部u_sign生成与TLS指纹处理! + 数据可视化技术讲解【Python爬虫】

目录 针对大学名称 大学排名, 综合指数,学校情况等数据进行爬取 找对应得数据包 请求发现数据有加密 发现加密参数 搜索加密参数&#xff0c;好进行分析 分析过程 数据可视化 针对大学名称 大学排名, 综合指数,学校情况等数据进行爬取 首先进行鼠标右键&#xff0c;进行…

Spring Boot+Mybatis设置sql日志打印

在全局配置文件添加以下内容&#xff1a;logging.level.com.demo.mapperdebug&#xff0c;com.demo.mapper&#xff1a;src下的mapper路径&#xff0c;debug&#xff1a;设置日志打印级别为debug&#xff0c;亦可设置为&#xff1a;ERROR、WARN、INFO application.properties …

TikTok获客技巧分享(纯干货)

随着全球短视频的兴起&#xff0c;TikTok已经成为了最受欢迎的社交媒体平台之一&#xff0c;对于企业和个人而言&#xff0c;如何在TikTok上获取更多的客户和粉丝&#xff0c;成为了他们关注的焦点&#xff0c;本文将分享一些TikTok获客技巧&#xff0c;帮助大家在短视频平台上…

初识Redis缓存,一文掌握Redis重要知识文集。

&#x1f3c6;作者简介&#xff0c;普修罗双战士&#xff0c;一直追求不断学习和成长&#xff0c;在技术的道路上持续探索和实践。 &#x1f3c6;多年互联网行业从业经验&#xff0c;历任核心研发工程师&#xff0c;项目技术负责人。 &#x1f389;欢迎 &#x1f44d;点赞✍评论…

云原生基础入门概念

文章目录 发现宝藏云原生的概念云原生的关键技术为何选择云原生&#xff1f;云原生的实际应用好书推荐 发现宝藏 前些天发现了一个巨牛的人工智能学习网站&#xff0c;通俗易懂&#xff0c;风趣幽默&#xff0c;忍不住分享一下给大家。【宝藏入口】。 云原生的概念 当谈及现…

[23] GaussianAvatars: Photorealistic Head Avatars with Rigged 3D Gaussians

[paper | proj] 给定FLAME&#xff0c;基于每个三角面片中心初始化一个3D Gaussian&#xff08;3DGS&#xff09;&#xff1b;当FLAME mesh被驱动时&#xff0c;3DGS根据它的父亲三角面片&#xff0c;做平移、旋转和缩放变化&#xff1b;3DGS可以视作mesh上的辐射场&#xff1…

智能优化算法应用:基于算术优化算法3D无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于算术优化算法3D无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于算术优化算法3D无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.算术优化算法4.实验参数设定5.算法结果6.…

SD-WAN网络的可扩展性解析

SD-WAN组网以其卓越的可扩展性而脱颖而出&#xff0c;为企业提供了一个灵活适应不断扩张和增长需求的网络解决方案。SD-WAN组网通过轻松实现规模调整、拓扑变更以及多种接入方式的切换&#xff0c;确保网络的高效性和可管理性。对于正处于快速发展时期的企业而言&#xff0c;SD…

在IDEA中使用Git 、远程仓库克隆工程到本地

4.1 在IDEA中配置Git 安装好IntelliJ IDEA后,如果Git安装在默认路径下,那么idea会自动找到git的位置,如果更改了Git的安装位置则需要手动配置下Git的路径。 选择File→Settings打开设置窗口,找到Version Control下的git选项: 选择git的安装目录后可以点击“Test”按钮测…

探讨小鹏汽车CAN通讯协议分析破解过程数据研究技术应用

当前新能源电动汽车设计日益复杂&#xff0c;为提高舒适性、功能性、提升性能和确保更高的安全性&#xff0c;很多汽车的设计中融入了更复杂的功能。包括了雷达、激光雷达、自适应巡航、L2以上自动驾驶系统&#xff0c;高级驾驶辅助系统、盲区监测等等。安装在汽车上的传感器和…

Java中线程状态的描述

多线程-基础方法的认识 截止目前线程的复习 Thread 类 创建Thread类的方法 继承Thread类,重写run方法实现Runnable接口,重写run方法使用匿名内部类继承Thread类,重写run方法使用匿名内部类实现Runnable接口,重写run方法使用Lambda表达式 run方法中的所有的代码是当前线程对…

oracle 锁表解决办法

相关表介绍 V$LOCKED_OBJECT&#xff08;记录锁信息的表&#xff09;v$session&#xff08;记录会话信息的表&#xff09;v$sql&#xff08;记录 sql 执行的表&#xff09;dba_objects&#xff08;用来管理对象&#xff0c;表、库等等&#xff09; 查询锁表的 SID select b.…

滑动窗口最大值(LeetCode 239)

文章目录 1.问题描述2.难度等级3.热门指数4.解题思路方法一&#xff1a;暴力法方法二&#xff1a;优先队列方法三&#xff1a;单调队列 参考文献 1.问题描述 给你一个整数数组 nums&#xff0c;有一个大小为 k 的滑动窗口从数组的最左侧移动到数组的最右侧。你只可以看到在滑动…