SpringBoot2.1+WebSocket:详解及注意事项

2023-05-16

SpringBoot对WebSocket集成十分完美,直接上步骤。

引入Maven依赖

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

启用WebSocket以及注意事项(关于启动类的操作)

  1. 在启动类上添加注解@EnableWebSocket
  2. 使用内置Tomcat需要添加BeanServerEndpointExporter
  3. 如果同时使用了定时任务则需要添加BeanTaskScheduler
  4. 代码如下(包含详细注释):
@EnableWebSocket
@MapperScan("com.chase.repository")
@SpringBootApplication
public class AccesslogApplication extends SpringBootServletInitializer {
	//打包时注册启动类
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(AccesslogApplication.class);
    }
    /**
     * 使用 websockt注解的时候,使用@EnableScheduling注解后,即开启定时任务
     * 启动的时候一直报错,增加这个bean 则报错解决。
     */
    @Bean
    public TaskScheduler taskScheduler(){
        ThreadPoolTaskScheduler taskScheduler = new ThreadPoolTaskScheduler();
        taskScheduler.setPoolSize(10);
        taskScheduler.initialize();
        return taskScheduler;
    }
    public static void main(String[] args) {
        SpringApplication.run(AccesslogApplication.class, args);
    }
	/**
     * 如果直接使用springboot的内置容器,而不是使用独立的servlet容器,就要注入ServerEndpointExporter,外部容器则不需要。
     */
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }

}

ServerEndpoint(相当于服务器端)

  1. 类添加上两个注解@Component@ServerEndpoint("/accesslog/ws/{username}")
  2. /accesslog/ws/{username}路径可以自定义,username用于区别每个不同的用户
  3. WebSocket四个事件,分别对应四个注解@OnOpen(建立连接)、@OnMessage(收到客户端消息)、@OnClose(连接关闭)、 @OnError(发生异常)
  4. WebSocket推送采用Session,username用于区分不同用户的Session。在建立连接的时候会将该用户的Session和username信息存入ConcurrentHashMap(保证多线程安全同时方便利用map.get(username)进行推送到指定用户),推送时只需要根据相应的username即可实现推送。推送方法和存储的map由一个工具类来实现(当然你也可以有更简单的实现方式),推送工具类见下一点。
  5. 小熙踩到过一个坑,SpringBoot项目设置了server.servlet.context-path=/accesslog后会直接导致WebSocket连接失败,猜测是由于路径的问题,但百思不得其解,尝试修改亦无果,望路过大神解惑一二。
@Component
@ServerEndpoint("/accesslog/ws/{username}")
public class ChatRoomServerEndpoint {
    @OnOpen
    public void openSession(@PathParam("username") String username, Session session) {
        ONLINE_USER_SESSIONS.put(username, session);
        String message = "[" + username + "] 客户端信息!";
        sendMessageAll("服务器连接成功!");
        sendMessage(session,"");
        System.out.println("连接成功"+message);
    }
    @OnMessage
    public void onMessage(@PathParam("username") String username, String message) {
        System.out.println("服务器收到:"+"[" + username + "] : " + message);
        sendMessageAll("[" + username + "] : " + message);
    }

    @OnClose
    public void onClose(@PathParam("username") String username, Session session) {
        //当前的Session 移除
        ONLINE_USER_SESSIONS.remove(username);
        //并且通知其他人当前用户已经断开连接了
        sendMessageAll("[" + username + "] 断开连接!");
        try {
            session.close();
        } catch (IOException e) {
        }
    }
    @OnError
    public void onError(Session session, Throwable throwable) {
        try {
            session.close();
        } catch (IOException e) {
        }
    }
}

推送工具类

  1. 推送工具类定义了两个静态方法,单用户推送和全用户推送(全用户推送就是对 ConcurrentHashMap中的所有用户进行推送)
  2. websocket session发送文本消息使用了RemoteEndpoint.Basic basic = session.getBasicRemote(); basic.sendText(message);同步发送的方式。
  3. 在进行推送的时候直接调用该工具类即可。见推送示例。
  4. WebSocket Session发送消息的两个方法getAsyncRemote()和getBasicRemote()的区别,见文章目录。
public class WebSocketUtils {
    public static final Map<String, Session> ONLINE_USER_SESSIONS = new ConcurrentHashMap<>();
	// 单用户推送
    public static void sendMessage(Session session, String message) {
        if (session == null) {
            return;
        }
        final RemoteEndpoint.Basic basic = session.getBasicRemote();
        if (basic == null)
        {
            return;
        }
        try {
            basic.sendText(message);
        } catch (IOException e) {
            System.out.println("sendMessage IOException "+ e);
        }
    }
	// 全用户推送
    public static void sendMessageAll(String message) {
        ONLINE_USER_SESSIONS.forEach((sessionId, session) -> sendMessage(session, message));
    }
}

客户端JS

<script type="text/javascript">
    $(document).ready(function(){
        var urlPrefix ='ws://localhost:8080/accesslog/ws/';
        var ws = null;
        
        var joinfun = function(){
            var username = "小熙";
            var url = urlPrefix + username;
            ws = new WebSocket(url);
            ws.onopen = function () {
                console.log("建立 websocket 连接...");
            };
            ws.onmessage = function(event){
                //服务端发送的消息
                $('#message_content').append(event.data+'\n');
               // 接到消息之后 任君处置
            };
            ws.onclose = function(){
                $('#message_content').append('用户['+username+'] 断开连接!');
                console.log("关闭 websocket 连接...");
            }
        };
        joinfun();//自动连接
        
		// 重新连接
        $('#user_add').click(function(){
           joinfun();
        });

        //客户端发送消息到服务器
        $('#user_send_all').click(function(){
            var msg = $('#in_room_msg').val();
            if(ws){
                ws.send(msg);
            }
        });
        // 断开连接
        $('#user_back').click(function(){
            if(ws){
                ws.close();
            }
        });
    });
</script>

推送示例

private void configureTasks() {
	//全用户发送
	sendMessageAll("全体通知!关注熙乎博客!”);

	//单用户发送消息
	for (Map.Entry<String, Session> ma: ONLINE_USER_SESSIONS.entrySet()
		) {
			if ("熙乎".equals(ma.getKey())) {
				sendMessage(ma.getValue(), "小熙你好!我是马云!");
			}
	}
}

文章目录

      • 引入Maven依赖
      • 启用WebSocket以及注意事项(关于启动类的操作)
      • ServerEndpoint(相当于服务器端)
      • 推送工具类
      • 客户端JS
      • 推送示例
      • 打包注意事项
      • WebSocket Session发送消息的两个方法getAsyncRemote()和getBasicRemote()的区别

打包注意事项

Maven导入的WebSocket的jar包会与SpringBoot内置tomcat中的WebSocket的jar包冲突,打包时把SpringBoot内置tomcat的jar包给忽略掉即可。

报错如下:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'serverEndpointExporter' defined in class path resource [xxxxxx.class]: Invocation of init method failed; nested exception is java.lang.IllegalStateException: javax.websocket.server.ServerContainer not available
  • 方法一:

在打包时使用 mvn clean package -DskipTests 就可以完美打包(跳过测试)

  • 方法二:

在pom文件的的中加入如下配置,即可直接打包成功

<plugin>
	<groupId>org.apache.maven.plugins</groupId>
	<artifactId>maven-surefire-plugin</artifactId>
	<configuration>
		<skip>true</skip>
	</configuration>
</plugin>

WebSocket Session发送消息的两个方法getAsyncRemote()和getBasicRemote()的区别

websocket session发送文本消息有两个方法:getAsyncRemote()非阻塞式的(异步)和getBasicRemote()阻塞式的(同步)。
直接上结论:

  • 如果一次性发送全部消息,两者基本没有差异,只是单纯同步和异步的区别。
  • 如果需要一次发送部分消息,则避免使用getBasicRemote()
  • 推荐使用getAsyncRemote()

两者区别如果觉得小熙描述不够,深入了解可以参考以下博客:
Is WebSocket Session really thread safe?

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

SpringBoot2.1+WebSocket:详解及注意事项 的相关文章

随机推荐

  • 解决linux指令“没有那个文件或目录”

    今天用shuf指令想打乱一个txt文件中各行的内容 xff0c 结果终端提示 没有那个文件或目录 一般这种情况肯定是路径写错了 xff0c 仔细检查之后发现 txt后面多打了一个空格 将空格删除 xff0c 问题解决 上面是正常的txt文件
  • Ubuntu配置阿里云的apt源

    root 64 zjy span class token comment cd etc apt span root 64 zjy etc apt span class token comment cp sources list source
  • hadoop自动故障转移集群搭建详解

    安装包地址 https archive apache org dist hadoop common stable 选择hadoop 3 3 2 tar gz包下载 环境 操作系统 span class token punctuation s
  • TX2 入坑总结(持续更新)

    1 新机无法使用hdmi转VGA连接显示器 xff0c 就算是换成1080p的显示器也不行 xff08 之前的树莓派可是啥都不挑各种连 xff09 更新内核后就可以连接1080p的显示器了 xff08 意外收获 xff09 xff0c 而且
  • 简单概念-BaseLine,PipeLine

    BaseLine 你训练一个模型 xff0c 获得了80 的准确率 xff0c 你觉得很高吗 xff1f 不能这么说 xff0c 因为你没有与别人的东西作对比 只有与当前的state of the art的算法比较才能有意义 xff0c 这
  • FreeRTOS中的变量,函数命名规则(u.v.x.p什么意思)

    写在前面 初学FreeRTOS时 xff0c 每次打开函数列表都一脸懵逼 xff1f 根本不知道这什么prv v ux是什么意思 xff0c 虽然平时使用也不需要知道这么多东西 xff0c 因为它不怎么影响开发 xff0c 但是理解总比疑惑
  • 水准网平差 WinForm C#软件

    水准网平差 span class token class name WinForm span C 软件 版权所有 需求源码请咨询 qq xff1a 849495327 仅供学习交流 界面UI设计 导入数据窗体 showViewdata cs
  • 基于K-d tree的Lidar点云检索C#

    版权所有 咨询请加qq 849495327 Form2 xff1a using System using System Collections Generic using System ComponentModel using System
  • C# 影像特征点提取

    版权所有 详情咨询qq 849495327 内容和要求如下 xff1a 任务 xff1a 提取一幅数字影像中的特征点 内容 xff1a 使用Moravec算子编写程序 xff0c 从一幅数字影像中自动提取出50个以上的特征点 要求 xff1
  • 解决朱有鹏开发板--主机--虚拟机不能互相PING通问题

    解决朱有鹏开发板 主机 虚拟机不能互相PING通问题 前言 xff1a 查遍整个网络 xff0c 没有人发表这个问题 xff0c 在视频中老朱得到的结论是UBOOT有问题 xff0c 结果 xff0c 经过实践发现 xff0c 是可以同时P
  • 一个简单的三层架构之用户登陆和注册在数据库中的存储

    一个简单的三层架构之用户登陆和注册在数据库中的存储 本人为一小白菜鸟 xff0c 在不断学习的过程中将自己学习所得的经验分享给大家 今天为大家介绍的是一个小实现 xff0c 一个三层架构的小实现 xff0c 关于登陆界面用户名输入与注册与数
  • 有哪些比较好的关于GIS和遥感的公众号?

    有哪些比较好的关于GIS和遥感的公众号 xff1f 作为一个正在学习的GISer和遥感学习者 xff0c 有哪些比较好的关于GIS和遥感的公众号呢 xff0c 下面来给大家分享一些我知道的 xff0c 当然又很多途径 xff0c 欢迎大家一
  • 一个简单的三层架构之仓库管理系统的入库出库

    一个简单的三层架构之仓库管理系统的入库出库 今天来讲一下一个简单的三层架构的程序 xff0c 一个仓库管理系统的简单的出库入库操作 xff0c 还有供应商的信息录入 欢迎大家添加 仓库管理系统群 xff08 matlab c java均可
  • C# 图像处理(一)

    下面是一个学习的过程 xff0c 一个小的图像处理软件的C 编程过程 xff0c 是我最近学习的过程 大家可以一起共同学习 欢迎大家加群 图像处理交流群 xff08 C opencv matlab xff09 672854897 1 打开图
  • Invalid bound statement (not found):的原因和解决方法

    在常见数据库异常中这算是比较简单的一类了 xff0c 细致一点就能很好的避免此类问题 xff0c 以下是我总结出容易出错的地方 xff0c 欢迎补充 检查Mapper xml中的方法名和Dao类方法是否一致 xff0c 若使用注解则检查SQ
  • Spring Boot 访问静态页面!

    SpringBoot访问静态页面 一 静态页面放在templates下面 xff08 多个页面可以添加相应的文件夹 xff0c 方便管理 xff09 xff1a 二 配置Controller 这里有两点值得注意 xff1a 其一 xff1a
  • Java动态代理的实现原理

    概述 AOP用到了两种动态代理来实现织入功能 xff1a jdk动态代理cglib动态代理比较 xff1a jdk动态代理是由java内部的反射机制来实现的 xff0c cglib动态代理底层则是借助asm来实现的 反射机制在生成类的过程中
  • MyBati 原理 - 【012】

    1 根据配置文件创建SQLSessionFactory 2 获取SQLSession的实现类DefaultSQLSession 3 getMapper返回接口代理对象 4 查询流程 5 查询流程总结 6 运行原理总结
  • JDK Tomcat MySQL一键安装

    文章目录 材料准备免安装版JDK免安装版Tomcat免安装版MySQL微软Visual C 43 43 运行库组件打包工具Inno Setup 统一目录创建所需文件 xff08 先创建好清楚整体流程 xff09 整体结构文件内容测试 打包步
  • SpringBoot2.1+WebSocket:详解及注意事项

    SpringBoot对WebSocket集成十分完美 xff0c 直接上步骤 引入Maven依赖 lt WebSocket gt lt dependency gt lt groupId gt org springframework boot