SpringBoot使用WebSocket实现前后端消息推送

WebSocket是HTML5开始提供的一种在单个TCP连接上进行全双工通讯的协议。它使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。与正常的http请求不同的是,WebSocket在浏览器和服务器之间只需要完成一次握手即可创建了一个持久性的长连接,通过这个长连接实现双向的数据传输。

本文只介绍后端SpringBoot如何使用WebSocket,前端部分使用:WebSocket在线测试地址 中的工具进行效果测试。

项目pom文件中引入WebSocket相关的依赖

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

SpringBoot是通过ServerEndpointExporter来扫描@ServerEndpoint注解声明的类从而实现WebSocket相关服务的,所以我们需要手动配置一个config类来在Spring上下文中提供ServerEndpointExporter。需要注意的是,如果你使用的是外部容器部署(如SpringBoot打war包并使用外置tomcat部署 等)则不需要这个config类来提供ServerEndpointExporter,因为此时SpringBoot默认将扫描@ServerEndpoint注解的行为交给了外部容器处理。

/**
 * Spring上下文注入ServerEndpointExporter
 */
@Configuration
public class WebSocketConfig {

    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }

}

接下来就是编写WebSocket的服务类及相关的业务逻辑了,我这里做了一个简单的样例

/**
 * 测试WebSocket
 */
@ServerEndpoint("/test")
@Component
@Slf4j
public class TestWebSocket {

    /**
     * concurrent包的线程安全Set
     * 用来存放每个客户端的连接会话session
     */
    private static CopyOnWriteArraySet<Session> sessionSet = new CopyOnWriteArraySet<>();

    /**
     * 在客户初次连接时触发
     * 这里会为客户端创建一个session,这个session并不是我们所熟悉的httpsession
     *
     * @param session
     */
    @OnOpen
    public void onOpen(Session session) {
        log.info(session.getId() + "加入!当前在线人数为" + getOnlineCount());
        sessionSet.add(session);
        groupMessage(session.getId() + "加入!当前在线人数为" + getOnlineCount());
    }

    /**
     * 在客户端与服务器端断开连接时触发
     */
    @OnClose
    public void onClose(Session session) {
        log.info(session.getId() + "连接关闭!当前在线人数为" + getOnlineCount());
        sessionSet.remove(session);
        groupMessage(session.getId() + "连接关闭!当前在线人数为" + getOnlineCount());
    }

    /**
     * 收到客户端消息后调用的方法
     *
     * @param session
     * @param message
     */
    @OnMessage
    public void onMessage(Session session, String message) {
        log.info(session.getId() + "说" + message);
        groupMessage(session.getId() + "说" + message);
    }

    /**
     * 给某个会话发送消息
     *
     * @param session
     * @param message
     */
    public static void sendMessage(Session session, String message) {
        try {
            session.getBasicRemote().sendText(message);
        } catch (IOException e) {
            log.error(e.getMessage());
        }
    }

    /**
     * 给当前在线的人群发消息
     *
     * @param message
     */
    public static void groupMessage(String message) {
        for (Session session : sessionSet) {
            sendMessage(session, message);
        }
    }

    /**
     * 获取当前连接数
     *
     * @return
     */
    public static synchronized int getOnlineCount() {
        return sessionSet.size();
    }

}

WebSocket会根据具体的行为分别调用@OnOpen、@OnClose、@OnMessage等注解标记的方法,消息的发送以及连接都是通过Session对象实现的(这里的Session跟我们之前普通web服务里面的HttpSession不同

为了实现群发消息,我这里通过一个线程安全的CopyOnWriteArraySet来讲每个连接的Session存储起来,方便后续消息的推送。

测试效果(上述样例中WebSocket的连接地址为:ws://127.0.0.1:8080/test 跟正常的web请求地址基本类似,只是开头的http换成了ws)

完整的demo见:https://gitee.com/floow/blog-demo demo17