文章目录
Springboot整合WebSocket
本文实现如何将WebSocket在SpringBoot中整合并且使用,SpringBoot中WebSocket组件已经非常完善,使用起来并不困难。
WebSocket使用方式有很多,这里主要展示常用的两种:
- 点对点式,通过userId进行消息的收发
- 订阅式,通过topic进行消息订阅、取消订阅、消息群发
本文源码:
- Github源代码地址:https://github.com/xunfeng224/Springboot/tree/main/springboot-WebSocket
- Gitee源代码地址:https://gitee.com/xfeng520/Springboot/tree/main/springboot-WebSocket
引入
WebSocket 是一种基于 TCP 协议的全双工通信协议,它允许客户端和服务器之间建立持久的、双向的通信连接。相比传统的 HTTP 请求 - 响应模式,WebSocket 提供了实时、低延迟的数据传输能力。通过 WebSocket,客户端和服务器可以在任意时间点互相发送消息,实现实时更新和即时通信的功能。WebSocket 协议经过了多个浏览器和服务器的支持,成为了现代 Web 应用中常用的通信协议之一。它广泛应用于聊天应用、实时数据更新、多人游戏等场景,为 Web 应用提供了更好的用户体验和更高效的数据传输方式。
项目结构
整合流程
添加依赖
在 pom.xml
中添加 spring-boot-starter-websocket
依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
创建WebSocket操作类
此类为WebSocket服务端端点,WebSocket功能实现及处理都在此类中完成。
注解@ServerEndpoint(“/websocket/{userId}”)中"/websocket/{userId}"为ws链接url,其中userId参数用于点对点式
该类方法虽然多,但是都很简单,主要原理为通过ConcurrentHashMap保存客户端连接信息,并使用Session对客户端进行消息发送
可分为三大部分:
- @OnMessage、@OnOpen、@OnClose、@OnError注解所修饰的方法,在端点处理客户端的连接、断开、消息等事件时会自动调用
- 点对点式WebSocker实现,流程为,在@OnOpen建立连接时获取到客户端id即userId,将userId对应的session存入Map,后续通过userId获取session进行消息发送,即可实现服务端对客户端通信
- 订阅式WebSocket实现,客户端需要发送一条SocketMessage进行Topic订阅,在SocketMessage.Header中消息类型messageType=“SUBSCRIBE”,订阅主题topic=“[指定的主题]”,在@OnMessage接受到此消息时,服务端将Topic与订阅该Topic的session集合存储起来,后续通过Topic获取session集合,向该Topic订阅者群发消息
package com.xunfeng.example.websocket;
import com.alibaba.fastjson.JSONException;
import com.alibaba.fastjson.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.StringUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* 服务端 WebSocket 端点
* 接口路径 ws://localhost:8080/websocket/userId;
* 注意:此处端口为SpringBoot服务端口,即server.port=8080
*/
@Component
@Slf4j
@ServerEndpoint("/websocket/{userId}")
public class WebSocket {
/**
* 与某个客户端的连接会话,需要通过它来给客户端发送数据
*/
private Session session;
/**
* 订阅
*/
public static final String SUBSCRIBE = "subscribe";
/**
* 取消订阅
*/
public static final String UNSUBSCRIBE = "unsubscribe";
/**
* 订阅主题 ->会话id列表 以Topic订阅形式进行消息推送
*/
public static ConcurrentHashMap<String, Set<Session>> topicSessionMap = new ConcurrentHashMap<>();
/**
* 用户ID
*/
private String userId;
/**
* concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
* 虽然@Component默认是单例模式的,但springboot还是会为每个websocket连接初始化一个bean,所以可以用一个静态set保存起来。
* 注:底下WebSocket是当前类名
*/
private static final CopyOnWriteArraySet<WebSocket> WEB_SOCKETS = new CopyOnWriteArraySet<>();
/**
* 用来存在线连接用户信息
*/
private static final ConcurrentHashMap<String, Session> SESSION_POOL = new ConcurrentHashMap<String, Session>();
/**
* 链接成功调用的方法
*/
@OnOpen
public v