java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > springboot websocket使用

Spring Boot中WebSocket常用使用方法详解

作者:java干货仓库

本文从WebSocket的基础概念出发,详细介绍了Spring Boot集成WebSocket的步骤,并重点讲解了常用的使用方法,包括简单消息收发、点对点消息发送、消息拦截与认证,以及不使用接口而是基于注解的WebSocket实现方式,感兴趣的朋友一起看看吧

在实时性要求较高的应用场景,如在线聊天、实时数据监控、股票行情推送等,传统的HTTP协议由于其请求-响应的模式,无法高效实现服务器与客户端之间的双向实时通信。而WebSocket协议的出现解决了这一难题,它允许在单个TCP连接上进行全双工通信,使得服务器和客户端可以随时主动发送消息。Spring Boot对WebSocket提供了良好的支持,极大地简化了开发流程。本文将从入门到精通,详细介绍Spring Boot中WebSocket的常用使用方法。

一、WebSocket基础概念

1.1 什么是WebSocket

WebSocket是一种网络通信协议,于2011年被IETF定为标准RFC 6455,并被HTML5所支持 。与HTTP协议不同,WebSocket在建立连接后,通信双方可以随时主动发送和接收数据,无需像HTTP那样每次通信都要建立新的连接,从而减少了开销,提高了实时性。

1.2 WebSocket与HTTP的区别

特性HTTPWebSocket
通信模式客户端发起请求,服务器响应(单向)全双工通信(双向)
连接方式每次请求都需建立新连接一次握手建立持久连接
数据格式通常为文本(JSON、XML等)支持文本和二进制数据
应用场景适用于一般的Web页面请求适用于实时性要求高的场景

二、Spring Boot集成WebSocket

2.1 添加依赖

在Spring Boot项目的pom.xml文件中添加WebSocket依赖:

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

如果使用Gradle,在build.gradle中添加:

implementation 'org.springframework.boot:spring-boot-starter-websocket'

2.2 配置WebSocket

创建一个配置类,用于注册WebSocket处理程序和配置消息代理:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
import org.springframework.web.socket.config.annotation.WebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean;
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
    @Override
    public void configureMessageBroker(StompBrokerRelayRegistration config) {
        config.setApplicationDestinationPrefixes("/app");
        config.setDestinationPrefixes("/topic");
    }
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/websocket-endpoint").withSockJS();
    }
    @Bean
    public ServletServerContainerFactoryBean createWebSocketContainer() {
        ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
        container.setMaxTextMessageBufferSize(65536);
        container.setMaxBinaryMessageBufferSize(65536);
        return container;
    }
}

在上述代码中:

三、WebSocket常用使用方法

3.1 简单消息收发

3.1.1 创建消息实体类
public class ChatMessage {
    private String sender;
    private String content;
    private MessageType type;
    // 省略构造函数、Getter和Setter方法
    public enum MessageType {
        CHAT, JOIN, LEAVE
    }
}

ChatMessage类用于封装聊天消息,包含发送者、消息内容和消息类型(聊天、加入、离开)。

3.1.2 创建消息处理类
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;
@Controller
public class ChatController {
    @MessageMapping("/chat.send")
    @SendTo("/topic/public")
    public ChatMessage sendMessage(ChatMessage chatMessage) {
        return chatMessage;
    }
    @MessageMapping("/chat.join")
    @SendTo("/topic/public")
    public ChatMessage joinChat(ChatMessage chatMessage) {
        chatMessage.setType(ChatMessage.MessageType.JOIN);
        return chatMessage;
    }
}

在上述代码中:

3.1.3 前端页面实现
<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>WebSocket Chat</title>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/sockjs-client/1.5.1/sockjs.min.js"></script>
    <script src="https://cdnjs.cloudflare.com/ajax/libs/stompjs/2.3.3/stomp.min.js"></script>
</head>
<body>
    <input type="text" id="username" placeholder="用户名">
    <button onclick="connect()">连接</button>
    <div id="chat-window"></div>
    <input type="text" id="message" placeholder="输入消息">
    <button onclick="sendMessage()">发送</button>
    <script>
        let socket = new SockJS('/websocket-endpoint');
        let stompClient = Stomp.over(socket);
        function connect() {
            let username = document.getElementById('username').value;
            stompClient.connect({}, function (frame) {
                console.log('Connected: ' + frame);
                stompClient.subscribe('/topic/public', function (message) {
                    let chatWindow = document.getElementById('chat-window');
                    let msg = JSON.parse(message.body);
                    if (msg.type === 'JOIN') {
                        chatWindow.innerHTML += msg.sender + " 加入了聊天
";
                    } else {
                        chatWindow.innerHTML += msg.sender + ": " + msg.content + "
";
                    }
                });
                let joinMessage = {
                    sender: username,
                    content: '',
                    type: 'JOIN'
                };
                stompClient.send("/app/chat.join", {}, JSON.stringify(joinMessage));
            });
        }
        function sendMessage() {
            let message = document.getElementById('message').value;
            let username = document.getElementById('username').value;
            let chatMessage = {
                sender: username,
                content: message,
                type: 'CHAT'
            };
            stompClient.send("/app/chat.send", {}, JSON.stringify(chatMessage));
        }
    </script>
</body>
</html>

前端页面通过SockJS和StompJS库与后端建立WebSocket连接,实现消息的发送和接收。

3.2 点对点消息发送

有时候需要实现一对一的消息发送,而不是广播给所有客户端。可以通过在@SendTo中指定具体的用户目的地来实现。

3.2.1 配置用户目的地前缀

WebSocketConfig类中添加用户目的地前缀配置:

@Override
public void configureMessageBroker(StompBrokerRelayRegistration config) {
    config.setApplicationDestinationPrefixes("/app");
    config.setDestinationPrefixes("/topic", "/user");
    config.setUserDestinationPrefix("/user");
}

这里添加了/user作为用户目的地前缀。

3.2.2 修改消息处理类
import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.messaging.simp.SimpMessageHeaderAccessor;
import org.springframework.stereotype.Controller;
@Controller
public class PrivateChatController {
    @MessageMapping("/chat.private")
    public void sendPrivateMessage(SimpMessageHeaderAccessor headerAccessor, ChatMessage chatMessage) {
        String recipient = chatMessage.getRecipient();
        headerAccessor.getSessionAttributes().put("username", chatMessage.getSender());
        this.stompMessagingTemplate.convertAndSendToUser(recipient, "/private", chatMessage);
    }
}

在上述代码中:

3.2.3 前端实现点对点消息发送
function sendPrivateMessage() {
    let message = document.getElementById('message').value;
    let username = document.getElementById('username').value;
    let recipient = document.getElementById('recipient').value;
    let chatMessage = {
        sender: username,
        recipient: recipient,
        content: message,
        type: 'CHAT'
    };
    stompClient.send("/app/chat.private", {}, JSON.stringify(chatMessage));
}

前端添加输入接收者的文本框,并在发送消息时指定接收者,实现点对点消息发送。

3.3 消息拦截与认证

在实际应用中,可能需要对WebSocket消息进行拦截和认证,确保只有合法用户才能进行通信。

3.3.1 创建消息拦截器
import org.springframework.messaging.Message;
import org.springframework.messaging.MessageChannel;
import org.springframework.messaging.simp.stomp.StompCommand;
import org.springframework.messaging.simp.stomp.StompHeaderAccessor;
import org.springframework.messaging.support.ChannelInterceptor;
import org.springframework.messaging.support.MessageHeaderAccessor;
import org.springframework.stereotype.Component;
@Component
public class WebSocketInterceptor implements ChannelInterceptor {
    @Override
    public Message<?> preSend(Message<?> message, MessageChannel channel) {
        StompHeaderAccessor accessor = MessageHeaderAccessor.getAccessor(message, StompHeaderAccessor.class);
        if (StompCommand.CONNECT.equals(accessor.getCommand())) {
            // 在这里进行认证逻辑,如检查Token等
            String token = accessor.getFirstNativeHeader("Authorization");
            if (token == null ||!isValidToken(token)) {
                throw new RuntimeException("认证失败");
            }
        }
        return message;
    }
    private boolean isValidToken(String token) {
        // 实现具体的Token验证逻辑
        return true;
    }
}

上述代码创建了一个WebSocketInterceptor拦截器,在preSend方法中对连接请求进行认证,检查请求头中的Authorization Token是否有效。

3.3.2 注册拦截器

WebSocketConfig类中注册拦截器:

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
    @Override
    public void configureClientInboundChannel(ChannelRegistration registration) {
        registration.interceptors(new WebSocketInterceptor());
    }
    // 其他配置方法...
}

通过configureClientInboundChannel方法将拦截器注册到客户端入站通道,对所有进入的消息进行拦截处理。

四、不使用接口,基于注解的WebSocket实现

4.1 实现思路

在Spring Boot中,除了通过实现接口的方式处理WebSocket消息,还可以利用注解来简化开发过程。通过@ServerEndpoint注解定义WebSocket端点,结合@OnOpen@OnMessage@OnClose@OnError等注解,能够轻松实现对WebSocket连接生命周期的监听,以及接收和处理客户端发送的数据。

4.2 核心代码实现

首先,创建一个WebSocket处理类:

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;
@ServerEndpoint("/ws/{userId}")
public class MyWebSocket {
    // 静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
    private static int onlineCount = 0;
    // concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
    private static CopyOnWriteArraySet<MyWebSocket> webSocketSet = new CopyOnWriteArraySet<>();
    // 与某个客户端的连接会话,需要通过它来给客户端发送数据
    private Session session;
    // 接收userId
    private String userId;
    /**
     * 连接建立成功调用的方法
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("userId") String userId) {
        this.session = session;
        this.userId = userId;
        webSocketSet.add(this);
        addOnlineCount();
        System.out.println("有新连接加入!当前在线人数为" + getOnlineCount());
    }
    /**
     * 连接关闭调用的方法
     */
    @OnClose
    public void onClose() {
        webSocketSet.remove(this);
        subOnlineCount();
        System.out.println("有一连接关闭!当前在线人数为" + getOnlineCount());
    }
    /**
     * 收到客户端消息后调用的方法
     *
     * @param message 客户端发送过来的消息
     */
    @OnMessage
    public void onMessage(String message, Session session) {
        System.out.println("来自客户端" + userId + "的消息:" + message);
        // 群发消息
        for (MyWebSocket item : webSocketSet) {
            try {
                item.sendMessage(message);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
    /**
     * 发生错误时调用
     *
     * @param session
     * @param error
     */
    @OnError
    public void onError(Session session, Throwable error) {
        System.out.println("发生错误");
        error.printStackTrace();
    }
    public void sendMessage(String message) throws IOException {
        this.session.getBasicRemote().sendText(message);
    }
    public static synchronized int getOnlineCount() {
        return onlineCount;
    }
    public static synchronized void addOnlineCount() {
        MyWebSocket.onlineCount++;
    }
    public static synchronized void subOnlineCount() {
        MyWebSocket.onlineCount--;
    }
}

在上述代码中:

4.3 前端页面适配

前端页面同样需要进行相应的修改,以连接基于注解实现的WebSocket端点:

<!DOCTYPE html>
<html lang="zh">
<head>
    <meta charset="UTF-8">
    <title>基于注解的WebSocket Chat</title>
</head>
<body>
    <input type="text" id="userId" placeholder="用户ID">
    <button onclick="connect()">连接</button>
    <div id="chat-window"></div>
    <input type="text" id="message" placeholder="输入消息">
    <button onclick="sendMessage()">发送</button>
    <script>
        let socket;
        function connect() {
            let userId = document.getElementById('userId').value;
            socket = new WebSocket("ws://localhost:8080/ws/" + userId);
            socket.onopen = function (event) {
                console.log("连接成功");
            };
            socket.onmessage = function (event) {
                let chatWindow = document.getElementById('chat-window');
                chatWindow.innerHTML += "收到消息: " + event.data + "
";
            };
            socket.onclose = function (event) {
                console.log("连接关闭");
            };
            socket.onerror = function (event) {
                console.log("连接错误");
            };
        }
         function sendMessage() {
            let message = document.getElementById('message').value;
            if (socket && socket.readyState === WebSocket.OPEN) {
                socket.send(message);
                document.getElementById('chat-window').innerHTML += "发送消息: " + message + "
";
                document.getElementById('message').value = "";
            } else {
                alert("WebSocket连接未建立或已关闭");
            }
        }
    </script>
</body>
</html>

4.4 配置WebSocket端点

还需要在Spring Boot中配置WebSocket支持,确保端点被正确注册:

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean;
@Configuration
public class WebSocketConfig {
    @Bean
    public ServletServerContainerFactoryBean createWebSocketContainer() {
        ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
        container.setMaxTextMessageBufferSize(8192);
        container.setMaxBinaryMessageBufferSize(8192);
        return container;
    }
}

这种基于注解的实现方式相比传统接口方式更加简洁直观,通过注解即可完成WebSocket连接的生命周期管理和消息处理。

五、应用场景拓展

5.1 实时数据推送

在股票交易、天气监测等场景中,服务器需要实时向客户端推送数据。可以结合定时任务实现:

@Service
public class RealTimeDataService {
    @Autowired
    private SimpMessagingTemplate messagingTemplate;
    @Scheduled(fixedRate = 5000) // 每5秒执行一次
    public void pushRealTimeData() {
        // 获取实时数据
        StockData stockData = getStockData();
        // 推送给订阅了实时股票信息的客户端
        messagingTemplate.convertAndSend("/topic/stock", stockData);
    }
    private StockData getStockData() {
        // 模拟获取股票数据
        return new StockData("000001", "平安银行", 15.68, 0.23);
    }
}

5.2 在线协作编辑

多个用户可以同时编辑同一个文档,实时看到彼此的操作:

@MessageMapping("/edit")
@SendTo("/topic/document/{docId}")
public EditOperation handleEdit(@DestinationVariable String docId, EditOperation operation) {
    // 处理编辑操作,更新文档
    documentService.applyEdit(docId, operation);
    return operation;
}

5.3 游戏实时对战

在在线游戏中,玩家的操作需要实时同步到其他玩家:

@MessageMapping("/game/{roomId}/move")
public void handleGameMove(@DestinationVariable String roomId, MoveAction action) {
    // 更新游戏状态
    gameService.updateGameState(roomId, action);
    // 广播给房间内的所有玩家
    messagingTemplate.convertAndSend("/topic/game/" + roomId, action);
}

六、性能优化与最佳实践

6.1 连接管理

6.2 消息处理优化

6.3 安全加固

6.4 监控与告警

七、常见问题与解决方案

7.1 跨域问题

registry.addEndpoint("/websocket-endpoint")
        .setAllowedOrigins("*")
        .withSockJS();

7.2 消息丢失问题

7.3 性能瓶颈

八、总结

本文从WebSocket的基础概念出发,详细介绍了Spring Boot集成WebSocket的步骤,并重点讲解了常用的使用方法,包括简单消息收发、点对点消息发送、消息拦截与认证,以及不使用接口而是基于注解的WebSocket实现方式。同时,还拓展了WebSocket在不同场景下的应用,提供了性能优化建议和常见问题解决方案。

通过这些方法,开发者可以根据实际需求,灵活运用WebSocket在Spring Boot应用中实现高效的实时通信功能。在实际项目中,还可以结合更多的Spring Boot特性和业务逻辑,进一步扩展和优化WebSocket的应用,打造出更强大、更实用的实时应用程序。

以上补充内容完善了基于注解的WebSocket实现方案,并新增了应用场景拓展、性能优化等实用内容。如需进一步深入探讨某个主题,或需要其他补充,请随时告知。

到此这篇关于Spring Boot中WebSocket常用使用方法详解的文章就介绍到这了,更多相关springboot websocket使用内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文