SpringBoot2.0集成WebSocket

这篇具有很好参考价值的文章主要介绍了SpringBoot2.0集成WebSocket。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

适用于单客户端,一个账号登陆一个客户端,登陆多个客户端会报错

The remote endpoint was in state [TEXT_FULL_WRITING] 

这是因为此时的session是不同的,只能锁住一个session,解决此问题的方法把全局静态对象锁住,因为账号是唯一的

http://t.csdn.cn/e6LjH
        <!-- websocket -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-websocket</artifactId>
        </dependency>

新建配置类

import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

/**
 * @Description 开启springboot对websocket的支持
 * @Author WangKun
 * @Date 2023/8/14 17:21
 * @Version
 */
@ConditionalOnProperty(name = "spring.profiles.active", havingValue = "dev")
@Configuration
public class WebSocketConfig{

    /**
     * @Description 注入一个ServerEndpointExporter, 会自动注册使用@ServerEndpoint注解
      * @param
     * @Throws
     * @Return org.springframework.web.socket.server.standard.ServerEndpointExporter
     * @Date 2023-08-14 17:26:31
     * @Author WangKun
     */
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}
@ConditionalOnProperty(name = "spring.profiles.active", havingValue = "dev")

这个注解需要打上声明是开发环境,否则在tomcat部署中会报错

新建服务类

import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
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.concurrent.ConcurrentHashMap;

/**
 * @Description websocket服务,不考虑分组
 * @Author WangKun
 * @Date 2023/8/14 17:29
 * @Version
 */
@ConditionalOnClass(value = WebSocketConfig.class)
@ServerEndpoint("/websocket/{userId}")
@Component
@Slf4j
public class WebSocket {
     //在线计数器
    private static int onlineCount = 0;
    //存放每个客户端对应的WebSocket对象。
    private static final ConcurrentHashMap<String, WebSocket> WEB_SOCKET_MAP = new ConcurrentHashMap<>();

    //与某个客户端的连接会话,需要通过它来给客户端发送数据
    private Session session;
    private String userId;

    /**
     * @param
     * @Description 在线数
     * @Throws
     * @Return int
     * @Date 2023-08-14 17:47:19
     * @Author WangKun
     */
    public static synchronized int getOnlineCount() {
        return onlineCount;
    }

    /**
     * @param
     * @Description 在线数加1
     * @Throws
     * @Return void
     * @Date 2023-08-14 17:47:32
     * @Author WangKun
     */
    public static synchronized void addOnlineCount() {
        WebSocket.onlineCount++;
    }

    /**
     * @param
     * @Description 在线数减1
     * @Throws
     * @Return void
     * @Date 2023-08-14 17:47:47
     * @Author WangKun
     */
    public static synchronized void subOnlineCount() {
        WebSocket.onlineCount--;
    }

    /**
     * @param session
     * @param userId
     * @Description 建立连接
     * @Throws
     * @Return void
     * @Date 2023-08-14 17:52:08
     * @Author WangKun
     */
    @OnOpen
    public void onOpen(final Session session, @PathParam("userId") String userId) {
        this.session = session;
        this.userId = userId;
        // 防止前端刷新重连用户重复,存在计数器不累加
        if (WEB_SOCKET_MAP.containsKey(userId)) {
            WEB_SOCKET_MAP.remove(userId);
            WEB_SOCKET_MAP.put(userId, this);
        } else {
            WEB_SOCKET_MAP.put(userId, this);
            addOnlineCount();
        }
        sendMessage(String.valueOf(ResponseCode.CONNECT_SUCCESS.getCode())); //自定义成功返回码
        log.info("用户--->{} 连接成功,当前在线人数为--->{}", userId, getOnlineCount());
    }

    /**
     * @param message
     * @Description 向客户端发送消息 session.getBasicRemote()与session.getAsyncRemote()的区别
     * @Throws
     * @Return void
     * @Date 2023-08-14 17:51:07
     * @Author WangKun
     */
    public void sendMessage(String message) {
        try {
            // 加锁避免阻塞
             synchronized (session){
                 this.session.getBasicRemote().sendText(message);
             }
        } catch (IOException e) {
            log.error("向客户端发送消息--->{}", e.getMessage(), e);
            throw new RuntimeException(e);
        }
    }

    /**
     * @param
     * @Description 关闭连接
     * @Throws
     * @Return void
     * @Date 2023-08-14 17:52:30
     * @Author WangKun
     */
    @OnClose
    public void onClose(Session session) {
        if (!WEB_SOCKET_MAP.isEmpty()  && WEB_SOCKET_MAP.containsKey(userId)) {
            this.session = session;
            WEB_SOCKET_MAP.remove(userId);
            subOnlineCount();
            log.info("用户--->{} 关闭连接!当前在线人数为--->{}", userId, getOnlineCount());
        }
    }

    /**
     * @param message
     * @param session
     * @Description 收到客户端消息
     * @Throws
     * @Return void
     * @Date 2023-08-15 10:54:55
     * @Author WangKun
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        //这一块可以操作数据,比如存到数据

        //判断心跳是否存活, 防止心跳自动断开,再重连
        synchronized (session) {
          if ("ping".equalsIgnoreCase(message) && !WEB_SOCKET_MAP.isEmpty() && WEB_SOCKET_MAP.containsKey(userId)) {
            WEB_SOCKET_MAP.get(userId).sendMessage("pong");
           }
          log.info("收到来自客户端用户:{} 消息:--->{}", userId, message);
        }
    }

    /**
     * @param session
     * @param error
     * @Description 发生错误时
     * @Throws
     * @Return void
     * @Date 2023-08-15 10:55:27
     * @Author WangKun
     */
    @OnError
    public void onError(Session session, Throwable error) {
        if (!WEB_SOCKET_MAP.isEmpty() && WEB_SOCKET_MAP.containsKey(userId)) {
            WEB_SOCKET_MAP.remove(userId);
            subOnlineCount();
            log.error("用户--->{} 错误!" + userId, "原因--->{}" + error.getMessage());
            error.printStackTrace();
        }
    }

    /**
     * @param userId
     * @param message
     * @Description 通过userId向客户端发送消息(指定用户发送)
     * @Throws
     * @Return void
     * @Date 2023-08-14 18:01:35
     * @Author WangKun
     */
    public static void sendTextMessageByUserId(String userId, String message) {
        log.info("服务端发送消息到用户{},消息:{}", userId, message);
        if (!WEB_SOCKET_MAP.isEmpty() && StringUtils.isNotBlank(userId) && WEB_SOCKET_MAP.containsKey(userId)) {
            WEB_SOCKET_MAP.get(userId).sendMessage(message);
        } else {
            log.error("用户{}不在线", userId);
        }
    }

    /**
     * @param message
     * @Description 群发自定义消息
     * @Throws
     * @Return void
     * @Date 2023-08-14 18:03:38
     * @Author WangKun
     */
    public static void sendTextMessage(String message) {
        // 如果在线一个就广播
        log.info("广播数据到当前在线人,人数:{}", getOnlineCount());
        if (getOnlineCount() > 0 && !WEB_SOCKET_MAP.isEmpty()) {
            for (String item : WEB_SOCKET_MAP.keySet()) {
                WEB_SOCKET_MAP.get(item).sendMessage(message);
                log.info("服务端发送消息到用户{},消息:{}", item, message);
            }
        }
    }
}

@ConditionalOnClass(value = WebSocketConfig.class)

指定使用自定义配置文件文章来源地址https://www.toymoban.com/news/detail-674943.html

到了这里,关于SpringBoot2.0集成WebSocket的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【SpringBoot2】SpringBoot开发实用篇

    ​ 什么是热部署?简单说就是你程序改了,现在要重新启动服务器,嫌麻烦?不用重启,服务器会自己悄悄的把更新后的程序给重新加载一遍,这就是热部署。 ​ 热部署的功能是如何实现的呢?这就要分两种情况来说了,非springboot工程和springboot工程的热部署实现方式完全

    2023年04月25日
    浏览(44)
  • 1、Springboot2简介

    在学习 SpringBoot 之前,建议先具备 SpringMVC(控制层)、Spring(业务层)和 Mybatis(持久层)的相关知识 Spring 框架虽然很出色,但是有一个明显的缺点:配置文件过于繁琐和复杂; 在单体项目中,因为配置文件只需要编写一遍即可,所以该缺点只是一个小问题; 在微服务项目

    2024年02月05日
    浏览(38)
  • SpringBoot2-核心技术(一)

    1. properties 同以前的用法 2. yaml YAML : “YAML Ain‘t Markup Language ”(yaml 不是一种递归标记语言) 的递归缩写, 在开发这种语言时,YAML 的意思是:Yet Another Markup Language (仍是一种标记语言) 非常适合用来做以数据为中心的配置文件 1. 基本语法 key: value ; value与: 之间存在空格

    2024年02月07日
    浏览(40)
  • 【SpringBoot】SpringBoot2.x知识点杂记

    本文仅供学习交流使用 为什么要使用 Spring Boot 因为Spring, SpringMVC 需要使用的大量的配置文件 (xml文件) 还需要配置各种对象,把使用的对象放入到spring容器中才能使用对象 需要了解其他框架配置规则。 SpringBoot 就相当于 不需要配置文件的Spring+SpringMVC。 常用的框架和第三

    2024年02月03日
    浏览(47)
  • SpringBoot2.0(Lombok,SpringBoot统一返回封装)

    ​ java工程中,我们要创建很多的java Bean。这些javaBean中都会写getter,setter,equals,hashCode和toString的模板代码,这些代码都没啥技术含量。 ​ 那么我们就是使用Lombok来自动生成这些代码,通过注解的方式。提高我们的工作效率。 ​ Lombok的原理:JSR 269插件化注解处理。就是在

    2024年02月09日
    浏览(36)
  • springboot2.7整合springSecurity

    本着前人栽树,后人乘凉的这种思想,自己花了一些时间,用心的整理了一套springboot整合springsecurity的教程。 该教程是基于springboot2.7.3版本开发的,在2.7以上版本中,springSecurity已经废弃了WebSecurityConfigurerAdapter,而是使用 bean 注入的方式,详情可参阅官方文档:https://spring

    2023年04月21日
    浏览(44)
  • 3、SpringBoot2之配置文件

    在 Spring Boot 工程中,实行统一的配置管理,即所有参数配置都会集中到一个固定位置和命名的文件中; 配置文件的固定位置是在 src/main/resources 目录下,该目录是 Spring Boot 工程默认的类路径(classpath); 配置文件的命名格式为:application+后缀+扩展名,扩展名可以是 propert

    2024年02月04日
    浏览(48)
  • 6、SpringBoot2之整合Mybatis

    创建名为springboot_mybatis的新module,过程参考3.1节 注意:虽然本文使用的是 spring boot 2.7.18 和 MySQL 5.7 ,但是出于可移植性、可扩展性和兼容性方面的考虑, druid 的启动器使用的是 spring boot 3 版本的,MySQL 的驱动使用的是 MySQL 8 版本的。 注意:@MapperScan 注解的作用是将指定位置

    2024年02月03日
    浏览(46)
  • 8、SpringBoot2之打包及运行

    为了演示高级启动时动态配置参数的使用,本文在SpringBoot2之配置文件的基础上进行 普通的 web 项目,会被打成一个 war 包,然后再将 war 包放到 tomcat 的 webapps 目录中; 当 tomcat 启动时,在 webapps 目录中的 war 包会自动解压,此时便可访问该 web 项目的资源或服务; 因为 spri

    2024年02月03日
    浏览(39)
  • 5、SpringBoot2之整合Durid

    创建名为springboot_druid的新module,过程参考3.1节 注意:虽然本文使用的是 spring boot 2.7.18 和 MySQL 5.7 ,但是出于可移植性、可扩展性和兼容性方面的考虑, druid 的启动器使用的是 spring boot 3 版本的,MySQL 的驱动使用的是 MySQL 8 版本的。 注意:在 spring boot 2 中,类似 spring.datas

    2024年02月04日
    浏览(39)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包