WebSocket协议在Java中的整合

1. 常见的消息推送方式

2.WebSocket API

3.基于WebSocket的实战(实时聊天室)

这里以解析后端代码为主,前端不作为重点,若想复现项目,请从作者的仓库中拉取代码

WebSocket-chatRoom: 基于WebSocket协议实现一个简单的聊天室

项目架构如下:

最后一个为@onclose(图上写错了)

3.1 基础环境搭建

 Resut实体类

@Data
public class Result {private boolean flag;private String message;
}

用户信息实体类

@Data
public class User {private String userId;private String username;private String password;
}

用户登录与获取用户信息的实现

@RestController
@RequestMapping("user")
public class UserController {/*** 登陆* @param user 提交的用户数据,包含用户名和密码* @param session* @return*/@PostMapping("/login")public Result login(@RequestBody User user, HttpSession session) {Result result = new Result();if(user != null && "123".equals(user.getPassword())) {result.setFlag(true);//将数据存储到session对象中session.setAttribute("user",user.getUsername());} else {result.setFlag(false);result.setMessage("登陆失败");}return result;}/*** 获取用户名* @param session* @return*/@GetMapping("/getUsername")public String getUsername(HttpSession session) {String username = (String) session.getAttribute("user");return username;}
}

3.2 WebSocket配置

  WebsocketConfig 配置类
@Configuration
public class WebsocketConfig {@Beanpublic ServerEndpointExporter serverEndpointExporter(){return new ServerEndpointExporter();}}
  • @Configuration 注解:表明这个类是一个配置类,它可以包含一个或多个@Bean方法,这些方法返回的对象会被Spring容器管理。
  • serverEndpointExporter() 方法:该方法被@Bean注释标记,表示它返回的对象(在这个例子中是ServerEndpointExporter实例)将被Spring容器作为bean管理。ServerEndpointExporter的作用是扫描并注册所有使用了@ServerEndpoint注解的类,使得它们可以处理WebSocket连接。

GetHttpSessionConfig 配置类
 /*** ServerEndpointConfig.Configurator 是一个抽象类,提供了在WebSocket握手阶段进行自定义配置的机会。通过继承这个类,我们可以在握手过程中执行额外的操作,如获取HTTP会话等。*/
@Configuration
public class GetHttpSessionConfig extends ServerEndpointConfig.Configurator {@Overridepublic void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {//获取HttpSession对象HttpSession httpSession= (HttpSession) request.getHttpSession();//将httpSession对象保存起来//我们将获取到的HttpSession对象存储在UserProperties集合中。这样做的目的是为了让后续的WebSocket消息处理方法能够访问到这个HTTP会话信息,例如用于身份验证或会话跟踪。sec.getUserProperties().put(HttpSession.class.getName(),httpSession);}
}
  • 继承自 ServerEndpointConfig.ConfiguratorServerEndpointConfig.Configurator 是一个抽象类,提供了在WebSocket握手阶段进行自定义配置的机会。通过继承这个类,我们可以在握手过程中执行额外的操作,如获取HTTP会话等。
  • modifyHandshake 方法:这是Configurator类中的一个重写方法,它允许我们在WebSocket握手阶段对连接进行修改。具体来说,在这里我们做了两件事:
    • 获取 HttpSession 对象:通过调用request.getHttpSession(),我们可以从握手请求中获得当前的HTTP会话。这在需要将WebSocket连接与特定用户的HTTP会话关联起来时非常有用。
    • 将 HttpSession 对象保存到 UserProperties 中:通过sec.getUserProperties().put(HttpSession.class.getName(), httpSession),我们将获取到的HttpSession对象存储在UserProperties集合中。这样做的目的是为了让后续的WebSocket消息处理方法能够访问到这个HTTP会话信息,例如用于身份验证或会话跟踪。
总结

这两段配置共同实现了以下功能:

  • 自动扫描并注册所有使用了@ServerEndpoint注解的类,使其成为WebSocket端点。
  • 在WebSocket握手阶段,获取当前用户的HTTP会话信息,并将其与WebSocket连接关联起来,以便在后续的消息交换中可以利用这些会话数据。

这种方式特别适用于需要在WebSocket通信中保持用户状态的应用场景,比如实时聊天应用、在线游戏等。通过这种方式,开发者可以确保WebSocket连接与用户的HTTP会话紧密关联,从而实现更安全、个性化的服务。

3.3 消息的处理

定义两个消息对象
 /*** 用于封装浏览器发送给服务端的消息数据*/
@Data
public class Message {private String toName;private String message;
}
 /*** 用来封装服务端给浏览器发送的消息数据*/
@Data
public class ResultMessage {private boolean isSystem;private String fromName;private Object message;//如果是系统消息是数组}
定义消息的工具类
public class MessageUtils {/*** @param isSystemMessage 是否是系统消息。只有广播消息才是系统消息。如果是私聊消息的话,就不是系统消息* @param fromName 给谁发消息,如果是系统消息的话,这个参数不需要指定* @param message 消息的具体内容* @return*/public static String getMessage(boolean isSystemMessage,String fromName, Object message) {ResultMessage result = new ResultMessage();result.setSystem(isSystemMessage);result.setMessage(message);if(fromName != null) {result.setFromName(fromName);}return JSON.toJSONString(result);}}
定义消息的处理类
@ServerEndpoint(value="/chat",configurator = GetHttpSessionConfig.class)
@Component
public class ChatEndpoint {//开一个线程安全的Mapprivate static final Map<String,Session> onlineUsers=new ConcurrentHashMap<>();private HttpSession httpSession;/*** 广播系统消息* @param message*/private void broadcastAllUsers(String message){try {//遍历mapSet<Map.Entry<String, Session>> entries = onlineUsers.entrySet();for (Map.Entry<String, Session> entry : entries) {//获取到所有用户对应的session对象Session session=entry.getValue();//发送对象session.getBasicRemote().sendText(message);}}catch (IOException e) {e.printStackTrace();}}/*** 返回所有在线用户的用户名集合。* @return*/public Set getFriends() {Set<String> set = onlineUsers.keySet();return set;}/*** 建立WebSocket连接后调用* @param session*/@OnOpenpublic void onOpen(Session session, EndpointConfig config){     //config与配置类中的sec是一个对象//将session保存this.httpSession=(HttpSession)config.getUserProperties().get(HttpSession.class.getName());String user=(String) this.httpSession.getAttribute("user");onlineUsers.put(user,session);//广播消息,将登录的所有用户推给所有的用户String message = MessageUtils.getMessage(true, null,getFriends());broadcastAllUsers(message);}/*** 浏览器发送消息到服务端,该方法被调用* @param message*/@OnMessagepublic void onMessage(String message) throws IOException {//将消息推送给指定的用户Message msg = JSON.parseObject(message, Message.class);//获取消息接收方的用户名String toName = msg.getToName();String mess=msg.getMessage();//获取消息接收方用户对象的sessionSession session=onlineUsers.get(toName);String user=(String) httpSession.getAttribute("user");String message1 = MessageUtils.getMessage(false, user,mess);session.getBasicRemote().sendText(message1);}/*** 断开WebSocket连接时被调用* @param session*/@OnClosepublic void onClose(Session session){//从onlineUsers中移除当前用户的session对象(用户退出)String user=(String) httpSession.getAttribute("user");onlineUsers.remove(user);//通知其他所有用户,当前用户下线String message = MessageUtils.getMessage(true, null,getFriends());broadcastAllUsers(message);}}

下面是对代码的每一步思路进行详细解释:

类定义与注解

@ServerEndpoint(value="/chat", configurator = GetHttpSessionConfig.class)
@Component
public class ChatEndpoint {
  • @ServerEndpoint(value="/chat", configurator = GetHttpSessionConfig.class): 这个注解表明ChatEndpoint类是一个WebSocket端点,监听路径为/chatconfigurator = GetHttpSessionConfig.class指定了一个配置器,用于获取HTTP会话信息。
  • @Component: 这个注解将ChatEndpoint类声明为Spring的一个组件,这样Spring容器可以自动发现并管理它。
成员变量
private static final Map<String, Session> onlineUsers = new ConcurrentHashMap<>();
private HttpSession httpSession;
  • onlineUsers: 一个线程安全的Map,键是用户名,值是对应的WebSocket Session对象。使用ConcurrentHashMap确保多线程环境下的安全性。
  • httpSession: 存储当前用户的HTTP会话对象,用于获取用户的相关信息(如用户名)。
广播系统消息方法
private void broadcastAllUsers(String message) {try {Set<Map.Entry<String, Session>> entries = onlineUsers.entrySet();for (Map.Entry<String, Session> entry : entries) {Session session = entry.getValue();session.getBasicRemote().sendText(message);}} catch (IOException e) {e.printStackTrace();}
}
  • 目的: 向所有在线用户广播一条消息。
  • 步骤:
    1. 获取onlineUsers中所有的条目(即用户名和Session的映射)。
    2. 遍历每个条目,获取对应的Session对象。
    3. 使用session.getBasicRemote().sendText(message)向每个用户发送消息。
    4. 捕获并打印可能发生的IO异常。
获取在线好友列表方法
public Set getFriends() {Set<String> set = onlineUsers.keySet();return set;
}
  • 目的: 返回当前在线用户的用户名集合。
  • 步骤:
    1. 调用onlineUsers.keySet()获取所有在线用户的用户名集合。
    2. 返回这个集合。
处理连接建立事件方法
@OnOpen
public void onOpen(Session session, EndpointConfig config) {this.httpSession = (HttpSession) config.getUserProperties().get(HttpSession.class.getName());String user = (String) this.httpSession.getAttribute("user");onlineUsers.put(user, session);String message = MessageUtils.getMessage(true, null, getFriends());broadcastAllUsers(message);
}
  • 目的: 当客户端与服务器建立WebSocket连接时执行的操作。
  • 步骤:
    1. config.getUserProperties()中获取HTTP会话对象,并赋值给this.httpSession
    2. httpSession中获取当前用户的用户名。
    3. 将用户名和对应的Session对象存入onlineUsers中。
    4. 构造一条包含当前在线用户信息的消息。
    5. 调用broadcastAllUsers(message)向所有在线用户广播这条消息。
处理接收到的消息方法
@OnMessage
public void onMessage(String message) throws IOException {Message msg = JSON.parseObject(message, Message.class);String toName = msg.getToName();String mess = msg.getMessage();Session session = onlineUsers.get(toName);String user = (String) httpSession.getAttribute("user");String message1 = MessageUtils.getMessage(false, user, mess);session.getBasicRemote().sendText(message1);
}
  • 目的: 当客户端发送消息到服务器时执行的操作。
  • 步骤:
    1. 解析客户端发送的JSON格式的消息,将其转换为Message对象。
    2. Message对象中提取接收方的用户名toName和实际消息内容mess
    3. 根据接收方的用户名从onlineUsers中获取对应的Session对象。
    4. httpSession中获取当前发送消息的用户的用户名。
    5. 构造一条包含发送者和消息内容的消息。
    6. 使用session.getBasicRemote().sendText(message1)将消息发送给接收方。
处理连接关闭事件方法
@OnClose
public void onClose(Session session) {String user = (String) httpSession.getAttribute("user");onlineUsers.remove(user);String message = MessageUtils.getMessage(true, null, getFriends());broadcastAllUsers(message);
}
  • 目的: 当客户端断开WebSocket连接时执行的操作。
  • 步骤:
    1. httpSession中获取当前用户的用户名。
    2. onlineUsers中移除该用户的Session对象。
    3. 构造一条包含当前在线用户信息的消息。
    4. 调用broadcastAllUsers(message)向所有在线用户广播这条消息。
总结

整个类的核心功能是维护一个在线用户列表,并在用户上线、下线或发送消息时进行相应的处理和通知。通过这些方法,多个客户端之间可以通过服务器转发消息,实现简单的即时通讯功能。

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

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

相关文章

蓝桥杯每日真题 - 第15天

题目&#xff1a;&#xff08;钟表&#xff09; 题目描述&#xff08;13届 C&C B组B题&#xff09; 解题思路&#xff1a; 理解钟表指针的运动&#xff1a; 秒针每分钟转一圈&#xff0c;即每秒转6度。 分针每小时转一圈&#xff0c;即每分钟转6度。 时针每12小时转一圈…

在 Node.js 中解决极验验证码:使用 Puppeteer 自动化

近年来&#xff0c;极验验证码在区分真实用户和自动化系统方面越来越先进&#xff0c;使其成为网页抓取和自动化的重大障碍。如果您正在使用 Node.js 并致力于在自动化流程中解决极验验证码&#xff0c;那么使用 Puppeteer 是一种有效的方法。Puppeteer 提供了一个高级 API 来控…

centos7 升级openssl 与升级openssh 安装卸载 telnet-server

前言&#xff1a; 服务器被安全扫描&#xff0c;扫出了漏洞需要修复&#xff0c;根据提示将openssh升级为9.8p1的版本&#xff0c;同时需要升级openssl&#xff0c;但是升级openssh可能会导致ssh连接失败&#xff0c;从而无法继续操作&#xff0c;特别是远程机房尤为危险&#…

PETR/PETRv2/StreamPETR论文阅读

1. PETR PETR网络结构如下&#xff0c;主要包括image-backbone&#xff0c;3D Coordinates Generator&#xff0c;3D Position Encoder&#xff0c;transformer Decoder四个模块。 把N 个视角的图像输入到骨干网络中以提取 2D 多视图特征。在 3D 坐标生成器中&#xff0c;首先…

若点集A=B则A必能恒等变换地变为B=A这一几何常识推翻直线(平面)公理

黄小宁 关键词&#xff1a;“更无理”复数 复平面z各点z的对应点z1的全体是z1面。z面平移变为z1面就使x轴⊂z面沿本身平移变为ux1轴。R可几何化为R轴&#xff0c;R轴可沿本身平移变为R′轴&#xff0c;R′轴可沿本身平移变为R″轴&#xff0c;...。直线公理和平面公理使几百年…

在Node.js中如何使用TypeScript

第一步&#xff1a;创建一个Node.js项目的package.json文件 npm init -y第二步&#xff1a;添加TypeScript、添加node.d.ts npm install typescript -D npm install types/node -D第三步&#xff1a;初始化一个tsconfig.json文件 npx tsc --init --rootDir src --outDir lib…

海康大华宇视视频平台EasyCVR私有化视频平台服务器选购主要参数有哪些?

在构建现代服务器和视频监控系统时&#xff0c;选择合适的硬件配置和关键技术是确保系统性能和稳定性的基础。服务器选购涉及到多个关键参数&#xff0c;这些参数直接影响到服务器的处理能力、数据存储、网络通信等多个方面。 同时&#xff0c;随着视频监控技术的发展&#xf…

async 和 await的使用

一、需求 点击按钮处理重复提交&#xff0c;想要通过disabled的方式实现。 但是点击按钮调用的方法里有ajax、跳转、弹窗等一系列逻辑操作&#xff0c;需要等方法里流程都走完&#xff0c;再把disabled设为false&#xff0c;这样下次点击按钮时就可以继续走方法里的ajax等操作…

【Pikachu】XML外部实体注入实战

若天下不定&#xff0c;吾往&#xff1b;若世道不平&#xff0c;不回&#xff01; 1.XXE漏洞实战 首先写入一个合法的xml文档 <?xml version "1.0"?> <!DOCTYPE gfzq [<!ENTITY gfzq "gfzq"> ]> <name>&gfzq;</name&…

g++与gdb简单学习

本文的内容由智谱清言产生 ------ 使用g编译C程序 使用gdb设置断点&#xff0c;反汇编代码&#xff0c;单步执行 int main() {int a 1;a;return 0; } 1.编译程序&#xff1a;使用 g 编译器将 C 源代码编译成 IA-32 可执行文件。 这可以通过添加 -m32 标志来实现&#xff0…

【小白可懂】微信小程序---课表渲染

结果展示&#xff1a;&#xff08;代码在最后&#xff09; WeChat_20241116174431 项目简介 在数字化校园建设的大背景下&#xff0c;为了更好地服务于在校师生&#xff0c;我们开发了一款基于微信小程序的课表管理系统。该系统采用了现代化的前端技术和优雅的设计风格&#x…

【实验11】卷积神经网络(2)-基于LeNet实现手写体数字识别

&#x1f449;&#x1f3fc;目录&#x1f448;&#x1f3fc; &#x1f352;1. 数据 1.1 准备数据 1.2 数据预处理 &#x1f352;2. 模型构建 2.1 模型测试 2.2 测试网络运算速度 2.3 输出模型参数量 2.4 输出模型计算量 &#x1f352;3. 模型训练 &#x1f352;4.模…

Python酷库之旅-第三方库Pandas(221)

目录 一、用法精讲 1036、pandas.DatetimeIndex.to_pydatetime方法 1036-1、语法 1036-2、参数 1036-3、功能 1036-4、返回值 1036-5、说明 1036-6、用法 1036-6-1、数据准备 1036-6-2、代码示例 1036-6-3、结果输出 1037、pandas.DatetimeIndex.to_series方法 10…

【WPF】Prism学习(三)

Prism Commands 1.复合命令&#xff08;Composite Commanding&#xff09; 这段内容主要介绍了在应用程序中如何使用复合命令&#xff08;Composite Commands&#xff09;来实现多个视图模型&#xff08;ViewModels&#xff09;上的命令。以下是对这段内容的解释&#xff1a; …

【Oracle篇】掌握SQL Tuning Advisor优化工具:从工具使用到SQL优化的全方位指南(第六篇,总共七篇)

&#x1f4ab;《博主介绍》&#xff1a;✨又是一天没白过&#xff0c;我是奈斯&#xff0c;DBA一名✨ &#x1f4ab;《擅长领域》&#xff1a;✌️擅长Oracle、MySQL、SQLserver、阿里云AnalyticDB for MySQL(分布式数据仓库)、Linux&#xff0c;也在扩展大数据方向的知识面✌️…

【一键整合包及教程】AI照片数字人工具EchoMimic技术解析

在数字化时代&#xff0c;人工智能&#xff08;AI&#xff09;正以前所未有的速度改变着我们的生活。EchoMimic&#xff0c;作为蚂蚁集团旗下支付宝推出的开源项目&#xff0c;不仅为数字人技术的发展掀开了新的一页&#xff0c;更为娱乐、教育、虚拟现实、在线会议等多个领域带…

基于Lora通讯加STM32空气质量检测WIFI通讯

目录 目录 前言 一、本设计主要实现哪些很“开门”功能&#xff1f; 二、电路设计原理图 1.电路图采用Altium Designer进行设计&#xff1a; 2.实物展示图片 三、程序源代码设计 四、获取资料内容 前言 随着环境污染问题的日益严重&#xff0c;空气质量的监测与管理已经…

GitLab 降级安装出现 500 错误,如何解决?

本文分享 GitLab 中文版在降级的过程中出现 500 错误的修复方法。 写在前面 强烈不建议大家自行降级&#xff0c;如果真有降级需求&#xff0c;要么自己能力过硬&#xff0c;要么寻求专业服务【https://dl.gitlab.cn/cm33bsfv】&#xff0c;要不出问题很麻烦&#xff01; 问…

2024-11-16 串的存储结构

一、顺序存储。 1.首先定一个静态数组&#xff0c;然后定义i记录串的实际长度。&#xff08;缺点&#xff1a;长度不可变&#xff09; 2.使用malloc申请动态空间&#xff0c;定义指针指向串的地址。&#xff08;需手动ferr&#xff09; 方案一&#xff1a; 数组末尾记录长度 …

PCHMI串口接收实验

插入的唯一一行代码 config1.START((Control)this, System.Reflection.Assembly.GetExecutingAssembly().GetTypes(), null);