WebSocket 在 Spring Boot 中的高级应用指南

WebSocket 在 Spring Boot 中的高级应用指南

深入理解WebSocket协议
深入理解STOMP协议

1. 概述

WebSocket 是一种基于 TCP 的全双工通信协议,允许服务器和客户端之间进行持续的双向通信。与传统的 HTTP 请求-响应模型不同,WebSocket 是一个持久的连接,可以在服务器和客户端之间进行实时数据交换,特别适用于需要频繁更新的场景,比如实时聊天、在线游戏、金融市场数据等。

在 Spring Boot 中,WebSocket 有多种实现方式,开发者可以根据具体的业务需求选择合适的方式。本文将详细介绍以下三种 WebSocket 的实现方式:

  1. 基于注解的 JSR 356 标准实现。
  2. 基于 Spring 的 WebSocketHandler 接口实现。
  3. 基于 STOMP 协议的实现。

通过这些不同的方式,开发者能够灵活地实现实时通信,满足各种场景下的需求。


2. 基于注解的 JSR 356 实现

JSR 356 是 Java 的标准 WebSocket API,它允许开发者使用注解来处理 WebSocket 的连接、消息传递和关闭等事件。JSR 356 的实现方式非常直观,适合轻量级的 WebSocket 应用。

2.1 配置与实现

依赖添加:

pom.xml 中添加 WebSocket 相关的依赖:

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

WebSocket 配置类:

为了让 Spring Boot 支持 WebSocket,我们需要配置一个 ServerEndpointExporter,它会自动注册所有标注了 @ServerEndpoint 的类。

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

@Configuration
public class WebSocketConfig {

    // 启用 WebSocket 支持,自动扫描并注册 @ServerEndpoint 注解的类
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

WebSocket 服务端实现类:

使用 @ServerEndpoint 注解来指定 WebSocket 端点路径,处理客户端的连接、消息和关闭事件。

import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;

@ServerEndpoint("/ws")
public class WebSocketServer {

    // 存储所有的 WebSocket 客户端连接
    private static final CopyOnWriteArraySet<WebSocketServer> webSocketSet = new CopyOnWriteArraySet<>();
    private Session session; // 当前会话的连接

    /**
     * 客户端连接建立时调用
     * @param session WebSocket 会话
     */
    @OnOpen
    public void onOpen(Session session) {
        this.session = session;
        webSocketSet.add(this); // 将新的连接加入集合
        System.out.println("新连接建立: " + session.getId());
        sendMessage("连接成功");
    }

    /**
     * 客户端发送消息时调用
     * @param message 客户端发来的消息
     */
    @OnMessage
    public void onMessage(String message) {
        System.out.println("收到消息: " + message + " 来自: " + session.getId());

        // 广播消息给所有连接的客户端
        for (WebSocketServer webSocket : webSocketSet) {
            try {
                webSocket.sendMessage("服务器收到消息: " + message);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 客户端断开连接时调用
     * @param session WebSocket 会话
     */
    @OnClose
    public void onClose(Session session) {
        webSocketSet.remove(this); // 连接关闭时移除
        System.out.println("连接关闭: " + session.getId());
    }

    /**
     * 发送消息给客户端
     * @param message 消息内容
     * @throws IOException 发送失败异常
     */
    private void sendMessage(String message) throws IOException {
        this.session.getBasicRemote().sendText(message);
    }
}

客户端页面:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WebSocket JSR 356</title>
</head>
<body>
    <h2>WebSocket JSR 356 客户端</h2>
    <button onclick="connect()">连接</button>
    <button onclick="disconnect()">断开连接</button>
    <br><br>
    <input type="text" id="message" placeholder="输入消息">
    <button onclick="sendMessage()">发送</button>

    <h3>消息:</h3>
    <ul id="messages"></ul>

    <script>
        var socket;

        function connect() {
            socket = new WebSocket("ws://localhost:8080/ws");
            socket.onmessage = function (event) {
                var messages = document.getElementById("messages");
                var message = document.createElement("li");
                message.appendChild(document.createTextNode(event.data));
                messages.appendChild(message);
            };
        }

        function disconnect() {
            if (socket) {
                socket.close();
            }
        }

        function sendMessage() {
            var messageInput = document.getElementById("message").value;
            socket.send(messageInput);
        }
    </script>
</body>
</html>

2.2 特点与适用场景

  • 简单直接:通过注解的方式,开发者可以轻松实现 WebSocket 的连接管理和消息处理。
  • 低复杂度:没有引入额外的协议,适合点对点通信的简单场景。
  • 适用场景:适合开发简单的实时聊天、在线客服等应用。

3. 基于 WebSocketHandler 接口的实现

Spring 提供了 WebSocketHandler 接口,用于更灵活地处理 WebSocket 连接。相比于注解方式,这种方式更适合处理复杂的业务逻辑。

3.1 配置与实现

WebSocket 配置类:

我们需要创建一个配置类,实现 WebSocketConfigurer 接口,并注册自定义的 WebSocketHandler

import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    private final MyWebSocketHandler myWebSocketHandler;

    public WebSocketConfig(MyWebSocketHandler myWebSocketHandler) {
        this.myWebSocketHandler = myWebSocketHandler;
    }

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        // 注册自定义的 WebSocketHandler
        registry.addHandler(myWebSocketHandler, "/ws").setAllowedOrigins("*");
    }
}

WebSocketHandler 实现类:

我们需要实现 TextWebSocketHandler 类,处理文本消息的收发。

import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

import java.io.IOException;
import java.util.concurrent.CopyOnWriteArrayList;

public class MyWebSocketHandler extends TextWebSocketHandler {

    private static final CopyOnWriteArrayList<WebSocketSession> sessions = new CopyOnWriteArrayList<>();

    /**
     * 连接建立后的回调方法
     * @param session WebSocket 会话
     */
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        sessions.add(session); // 保存新的连接
        session.sendMessage(new TextMessage("连接成功"));
        System.out.println("新连接建立: " + session.getId());
    }

    /**
     * 处理接收到的文本消息
     * @param session WebSocket 会话
     * @param message 接收到的消息
     */
    @Override
    public void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        String payload = message.getPayload();
        System.out.println("收到消息: " + payload);

        // 广播消息
        for (WebSocketSession webSocketSession : sessions) {
            if (webSocketSession.isOpen()) {
                webSocketSession.sendMessage(new TextMessage("服务器收到: " + payload));
            }
        }
    }

    /**
     * 连接关闭后的回调方法
     * @param session WebSocket 会话
     * @param status 关闭状态
     */
    @Override
    public void afterConnectionClosed(WebSocketSession session, org.springframework.web.socket.CloseStatus status) throws Exception {
        sessions.remove(session); // 移除关闭的连接
        System.out.println("连接关闭: " + session.getId());
    }
}

客户端页面:

客户端页面和之前的JSR 356方式类似,保持不变。

3.2 特点与适用场景

  • 更高的灵活性:相比于注解方式,使用 WebSocketHandler 可以更细粒度地控制 WebSocket 的行为,例如自定义处理多种消息类型(文本、二进制)。
  • 适用场景:适用于需要复杂的消息处理逻辑和扩展功能的应用,比如需要进行鉴权、拦截等。

4. 基于 STOMP 协议的实现

STOMP(Simple Text Oriented Messaging Protocol)是一个轻量级的消息传输协议,通常与 WebSocket 结合使用,支持发布/订阅模型,适用于多客户端间的消息分发和订阅。

4.1 配置与实现

依赖添加:

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

WebSocket 和 STOMP 配置类:

import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketStompConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        // 配置消息代理,处理前缀为 /topic 的消息
        config.enableSimpleBroker("/topic");
        // 配置应用程序前缀,发送消息的前缀
        config.setApplicationDestinationPrefixes("/app");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        // 注册 STOMP 端点,并启用 SockJS 支持
        registry.addEndpoint("/ws-stomp").withSockJS();
    }
}

消息控制器:

import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;

@Controller
public class WebSocketController {

    @MessageMapping("/sendMessage")
    @SendTo("/topic/messages")
    public String processMessageFromClient(String message) throws Exception {
        // 接收到客户端消息后,将其发送到 /topic/messages
        return "服务器收到: " + message;
    }
}

客户端页面:

使用 STOMP.js 来与服务器进行通信。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>STOMP WebSocket Test</title>
    <script src="https://cdn.jsdelivr.net/npm/sockjs-client@1.5.0/dist/sockjs.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/stompjs@2.3.3/lib/stomp.min.js"></script>
</head>
<body>
    <h2>STOMP WebSocket 客户端</h2>
    <button onclick="connect()">连接</button>
    <button onclick="disconnect()">断开连接</button>
    <br><br>
    <input type="text" id="message" placeholder="输入消息">
    <button onclick="sendMessage()">发送</button>

    <h3>消息:</h3>
    <ul id="messages"></ul>

    <script>
        var stompClient = null;

        function connect() {
            var socket = new SockJS('/ws-stomp');
            stompClient = Stomp.over(socket);
            stompClient.connect({}, function (frame) {
                console.log('连接成功: ' + frame);
                stompClient.subscribe('/topic/messages', function (messageOutput) {
                    showMessage(messageOutput.body);
                });
            });
        }

        function disconnect() {
            if (stompClient !== null) {
                stompClient.disconnect();
            }
            console.log("断开连接");
        }

        function sendMessage() {
            var messageInput = document.getElementById("message").value;
            stompClient.send("/app/sendMessage", {}, messageInput);
        }

        function showMessage(message) {
            var messages = document.getElementById("messages");
            var messageElement = document.createElement("li");
            messageElement.appendChild(document.createTextNode(message));
            messages.appendChild(messageElement);
        }
    </script>
</body>
</html>

4.2 特点与适用场景

  • 支持发布/订阅模型:STOMP 协议天生支持消息的发布和订阅,非常适合需要多客户端互动的场景。
  • 消息代理与路由:通过配置消息代理,可以轻松实现消息的广播和分发。
  • 适用场景:适用于多客户端的复杂通信场景,比如在线聊天室、股票行情推送等。

5. 三种实现方式的对比

特性基于注解(JSR 356)基于 WebSocketHandler基于 STOMP 协议
实现复杂度简单中等较高
消息路由支持消息路由、发布/订阅
灵活性较低
扩展性适合简单场景适合自定义处理逻辑适合复杂的多客户端场景
典型应用场景简单的实时通信应用复杂消息处理和控制发布/订阅模型、多人互动的实时应用

6. 结论

Spring Boot 提供了多种 WebSocket 实现方式,开发者可以根据具体需求选择合适的实现。对于简单的点对点通信,基于注解的 JSR 356 实现已经足够;对于需要更高灵活性和复杂业务处理的场景,WebSocketHandler 是一个更好的选择;而如果你需要实现发布/订阅模型,多客户端交互,基于 STOMP 协议的 WebSocket 实现是最佳选择。

开发者可以根据项目需求,合理使用 WebSocket,实现实时通信功能,让应用更加智能、高效。

推荐阅读

基于STOMP 协议的 WebSocket 实现传输长字符串

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

愤怒的代码

如果您有受益,欢迎打赏博主😊

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值