WebSocket协议在Java中的整合

news2024/11/17 19:56:56

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 {

    @Bean
    public 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 {


    @Override
    public 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 {

    //开一个线程安全的Map
    private static final Map<String,Session> onlineUsers=new ConcurrentHashMap<>();

    private HttpSession httpSession;


     /**
     * 广播系统消息
     * @param message
     */
    private void broadcastAllUsers(String message){
        try {
        //遍历map
        Set<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
     */
    @OnOpen
    public 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
     */
    @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 session=onlineUsers.get(toName);
        String user=(String) httpSession.getAttribute("user");
        String message1 = MessageUtils.getMessage(false, user,mess);
        session.getBasicRemote().sendText(message1);

    }




     /**
     * 断开WebSocket连接时被调用
     * @param session
     */
    @OnClose
    public 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.coloradmin.cn/o/2242373.html

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

相关文章

http自动发送请求工具(自动化测试http请求)

点击下载《http自动发送请求工具(自动化测试http请求)》 前言 在现代软件开发过程中&#xff0c;HTTP 请求的自动化测试是确保应用程序稳定性和可靠性的关键环节。为了满足这一需求&#xff0c;我开发了一款功能强大且易于使用的自动化 HTTP 请求发送工具。该工具基于 C# 开发…

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

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

WinDefender Weaker

PPL Windows Vista / Server 2008引入 了受保护进程的概念&#xff0c;其目的不是保护您的数据或凭据。其最初目标是保护媒体内容并符合DRM &#xff08;数字版权管理&#xff09;要求。Microsoft开发了此机制&#xff0c;以便您的媒体播放器可以读取例如蓝光&#xff0c;同时…

LabVIEW前面板最大化显示与像素偏差分析 有源程序附件

LabVIEW前面板最大化显示与像素偏差分析 有源程序附件 LabVIEW前面板最大化显示与像素偏差分析 有源程序附件 - 北京瀚文网星科技有限公司 这个VI用于将LabVIEW程序的前面板最大化地显示在指定显示器上&#xff0c;实现步骤如下&#xff1a; 1. 获取所有显示器的信息 首先&…

【C++】深入理解 C++ 优先级队列、容器适配器与 deque:实现与应用解析

个人主页: 起名字真南的CSDN博客 个人专栏: 【数据结构初阶】 &#x1f4d8; 基础数据结构【C语言】 &#x1f4bb; C语言编程技巧【C】 &#x1f680; 进阶C【OJ题解】 &#x1f4dd; 题解精讲 目录 前言&#x1f4cc; 1. 优先级队列、容器适配器和 deque 概述✨1.1 什么是优…

LogViewer NLog, Log4Net, Log4j 文本日志可视化

LogViewer 下载 示例&#xff1a;NLog文本日志可视化软件&#xff0c;并且能够实时监听输出最新的日志 nlog.config 通过udp方式传输给LogViewer (udp://ip:port) <?xml version"1.0" encoding"utf-8" ?> <nlog xmlns"http://www.nlog-…

安卓开发作业

整体效果: 安卓小作业 [TOC](页面配置) 整体框架有4个fragment页面,聊天,朋友,发现,设置. 配置如下: bash <?xml version"1.0" encoding"utf-8"?> <LinearLayout xmlns:android"http://schemas.android.com/apk/res/android" xm…

【QML】QML多线程应用(WorkerScript)

1. 实现功能 QML项目中&#xff0c;点击一个按键后&#xff0c;运行一段比较耗时的程序&#xff0c;此时ui线程会卡住。如何避免ui线程卡住。 2. 单线程&#xff08;会卡住&#xff09; 2.1 界面 2.2 现象 点击delay btn后&#xff0c;执行耗时函数&#xff08;TestJs.func…

深度学习中的Pixel Shuffle和Pixel Unshuffle:图像超分辨率的秘密武器

在深度学习的计算机视觉任务中&#xff0c;提升图像分辨率和压缩特征图是重要需求。Pixel Shuffle和Pixel Unshuffle是在超分辨率、图像生成等任务中常用的操作&#xff0c;能够通过转换空间维度和通道维度来优化图像特征表示。本篇文章将深入介绍这两种操作的原理&#xff0c;…

pom中无法下载下来的类外部引用只给一个jar的时候

比如jar在桌面上放着,操作步骤如下&#xff1a; 选择桌面&#xff0c;输入cmd ,执行mvn install:install-file -DgroupIdcom -DartifactIdaspose-words -Dversion15.8.0 -Dpackagingjar -Dclassifierjdk11 -Dfilejar包名称 即可把jar包引入成功。

群控系统服务端开发模式-应用开发-前端图片格式功能开发

一、添加视图 在根目录下src文件夹下views文件夹下param文件夹下grade文件夹下&#xff0c;新建index.vue&#xff0c;代码如下 <template><div class"app-container"><div class"filter-container" style"float:left;"><…

【Web前端】Promise的使用

Promise是异步编程的核心概念之一。代表一个可能尚未完成的操作&#xff0c;并提供了一种机制来处理该操作最终的成功或失败。具体来说&#xff0c;Promise是由异步函数返回的对象&#xff0c;能够指示该操作当前所处的状态。 当Promise被创建时&#xff0c;它会处于“待定”&a…

EEG+EMG学习系列 (2) :实时 EEG-EMG 人机界面的下肢外骨骼控制系统

[TOC]( EEGEMG学习系列(2):实时 EEG-EMG 人机界面的下肢外骨骼控制系统) 论文地址&#xff1a;https://ieeexplore.ieee.org/abstract/document/9084126 论文题目&#xff1a;Real-Time EEG–EMG Human–Machine Interface-Based Control System for a Lower-Limb Exoskeleton …

Spring Authorization Server OAuth2.1

Spring Authorization Server介绍 Spring Authorization Server 是一个框架&#xff0c;它提供了 OAuth 2.1 和 OpenID Connect 1.0 规范以及其他相关规范的实现。 它建立在 Spring Security 之上&#xff0c;为构建 OpenID Connect 1.0 身份提供者和 OAuth2 授权服务器产品提供…

《生成式 AI》课程 第3講 CODE TASK 任务3:自定义任务的机器人

课程 《生成式 AI》课程 第3講&#xff1a;訓練不了人工智慧嗎&#xff1f;你可以訓練你自己-CSDN博客 我们希望你创建一个定制的服务机器人。 您可以想出任何您希望机器人执行的任务&#xff0c;例如&#xff0c;一个可以解决简单的数学问题的机器人0 一个机器人&#xff0c…

Python知识点精汇!字符串:定义、截取(索引)和其内置函数

目录 一、字符串的定义 二、字符串的截取 1.截取干啥的 2.怎么用截取 3.打印多次 4.两个字符串拼接在一起 三、字符串内置函数 1.查询函数&#xff1a; &#xff08;1&#xff09;find(str,start,end) &#xff08;2&#xff09;index&#xff08;str,start,end&#…

创建vue+electron项目流程

一个vue3和electron最基本的环境搭建步骤如下&#xff1a;// 安装 vite vue3 vite-plugin-vue-setup-extend less normalize.css mitt pinia vue-router npm create vuelatest npm i vite-plugin-vue-setup-extend -D npm i less -D npm i normalize.css -S &#xff0…

从0开始机器学习--Day27--主成分分析方法

主成分分析方法(Principal components analysis) 在降维算法中&#xff0c;比较普遍的是使用主成分分析方法&#xff08;PCA&#xff09; PCA算法简单示例 如图&#xff0c;假设我们有一个二维的特征&#xff0c;想要将其降为一维&#xff0c;简单的方法是寻找一条直线&#…

无效的目标发行版17和无法连接Maven进程问题

起因&#xff1a;我clean了一个模块的Maven想要重新下&#xff0c;他就开始报错。两次了都是这样。如果和我一样一开始都是好好的&#xff0c;直接找Maven的设置&#xff0c;在运行程序改&#xff0c;jre变成了11.它自己变成了我其他的jdk

【Android、IOS、Flutter、鸿蒙、ReactNative 】启动页

Android 设置启动页 自定义 splash.xml 通过themes.xml配置启动页背景图 IOS 设置启动页 LaunchScreen.storyboard 设置为启动页 storyboard页面绘制 Assets.xcassets 目录下导入图片 AppLogo Flutter 设置启动页 Flutter Android 设置启动页 自定义 launch_background.xm…