WebSocket详解及Spring Boot+Java高级实战用法示例

 更新时间:2026年09月02日 09:58:10   作者:技术破壁人  
WebSocket是基于TCP的一种新的应用层网络协议,它提供了一个全双工的通道,允许服务器和客户端之间实时双向通信,这篇文章主要介绍了WebSocket详解及Spring Boot+Java高级实战用法的相关资料,需要的朋友可以参考下

一、为什么需要 WebSocket?

传统的 Web 应用基于 HTTP 协议,是请求-响应模式:

  • 客户端发请求 → 服务端处理 → 返回结果 → 连接关闭
  • 如果服务端有新数据(如聊天消息、订单状态变更),无法主动推给客户端

为实现“实时通信”,早期常用 轮询(Polling) 或 长轮询(Long Polling)

  • 轮询:每隔几秒发一次请求 → 浪费带宽、延迟高
  • 长轮询:请求挂起直到有数据 → 仍基于 HTTP,连接频繁建立/断开

✅ WebSocket 的优势

  • 全双工通信:客户端和服务端可随时互相发送数据
  • 单 TCP 连接:握手后复用连接,低开销
  • 低延迟、高效率:适合实时场景(聊天、股票、游戏、监控)

二、WebSocket 协议简析

  • 握手阶段:客户端通过 HTTP 发起 Upgrade 请求

    GET /chat HTTP/1.1
    Host: server.example.com
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
    Sec-WebSocket-Version: 13
    
  • 服务端响应

    HTTP/1.1 101 Switching Protocols
    Upgrade: websocket
    Connection: Upgrade
    Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
    
  • 连接建立:后续通信使用 WebSocket 帧(Frame),不再是 HTTP

🔑 关键:一次握手,长期通信

三、Spring Boot 整合 WebSocket(基础版)

场景:简易在线聊天室

1. 添加依赖(pom.xml)

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

2. 启用 WebSocket 支持

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        // 注册 STOMP 端点,允许跨域
        registry.addEndpoint("/ws")
                .setAllowedOriginPatterns("*")
                .withSockJS(); // 兼容不支持 WebSocket 的浏览器(降级到 xhr-streaming)
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        // 客户端订阅前缀
        registry.enableSimpleBroker("/topic", "/queue"); // 服务端推送前缀
        registry.setApplicationDestinationPrefixes("/app"); // 客户端发送消息前缀
    }
}

💡 使用 STOMP(Simple Text Oriented Messaging Protocol)作为子协议,简化消息路由。

3. 编写控制器(处理客户端消息)

@Controller
public class ChatController {

    @MessageMapping("/chat.sendMessage") // 客户端发送到 /app/chat.sendMessage
    @SendTo("/topic/public")             // 广播给所有订阅 /topic/public 的客户端
    public ChatMessage sendMessage(ChatMessage chatMessage) {
        return chatMessage;
    }

    @MessageMapping("/chat.addUser")
    @SendTo("/topic/public")
    public ChatMessage addUser(@Payload ChatMessage chatMessage,
                               SimpMessageHeaderAccessor headerAccessor) {
        // 将用户名加入会话
        headerAccessor.getSessionAttributes().put("username", chatMessage.getSender());
        chatMessage.setContent("加入聊天室");
        return chatMessage;
    }
}

4. 定义消息实体

@Data
@NoArgsConstructor
@AllArgsConstructor
public class ChatMessage {
    private String content;
    private String sender;
    private MessageType type; // enum: CHAT, JOIN, LEAVE

    public enum MessageType {
        CHAT, JOIN, LEAVE
    }
}

5. 前端连接(HTML + JS)

<script src="https://cdn.jsdelivr.net/npm/sockjs-client@1/dist/sockjs.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/stompjs@2.3.3/lib/stomp.min.js"></script>

<script>
const stompClient = Stomp.over(new SockJS('/ws'));

stompClient.connect({}, () => {
    console.log('连接成功');

    // 订阅公共频道
    stompClient.subscribe('/topic/public', (payload) => {
        const msg = JSON.parse(payload.body);
        document.getElementById('chat').innerHTML += 
            `<div><b>${msg.sender}</b>: ${msg.content}</div>`;
    });

    // 发送消息
    function sendMessage() {
        const content = document.getElementById('message').value;
        stompClient.send("/app/chat.sendMessage", {}, JSON.stringify({
            content: content,
            sender: '张三',
            type: 'CHAT'
        }));
    }
});
</script>

✅ 效果:用户加入、发消息,所有在线用户实时看到。

四、高级实战用法

场景1:私聊(点对点通信)

❌ 反例:用 /topic 广播实现私聊 → 信息泄露!

✅ 正确做法:使用 用户专属队列

// 获取当前登录用户(需集成 Spring Security)
private String getCurrentUser(SimpMessageHeaderAccessor headerAccessor) {
    // 从 token / session 中解析
    return (String) headerAccessor.getSessionAttributes().get("username");
}

@MessageMapping("/chat.private")
public void sendPrivateMessage(@Payload ChatMessage message,
                               SimpMessageHeaderAccessor headerAccessor) {
    String currentUser = getCurrentUser(headerAccessor);
    String recipient = message.getRecipient(); // 假设消息中包含接收者

    // 构造用户专属队列路径
    String destination = "/queue/messages/" + recipient;
    
    // 使用 convertAndSendToUser 发送(自动加 /user 前缀)
    messagingTemplate.convertAndSendToUser(recipient, "/queue/messages", message);
}

⚠️ 注意:前端需订阅 /user/queue/messages(STOMP 会自动映射到用户会话)

// 前端订阅私信
stompClient.subscribe('/user/queue/messages', (payload) =&gt; {
    const msg = JSON.parse(payload.body);
    // 显示私聊消息
});

场景2:连接管理 & 在线人数统计

@Component
public class WebSocketEventListener {

    private static final Set&lt;String&gt; onlineUsers = ConcurrentHashMap.newKeySet();

    @EventListener
    public void handleWebSocketConnectListener(SessionConnectedEvent event) {
        // 用户上线(需结合认证)
        System.out.println("用户连接: " + event.getMessage().getHeaders());
    }

    @EventListener
    public void handleWebSocketDisconnectListener(SessionDisconnectEvent event) {
        StompHeaderAccessor headerAccessor = StompHeaderAccessor.wrap(event.getMessage());
        String username = (String) headerAccessor.getSessionAttributes().get("username");
        if (username != null) {
            onlineUsers.remove(username);
            // 广播在线人数
            messagingTemplate.convertAndSend("/topic/online", onlineUsers.size());
        }
    }
}

可用于“好友在线状态”、“客服排队人数”等场景。

场景3:消息持久化 & 离线消息

当用户离线时,消息不能丢!

方案:

  • 发送前检查用户是否在线(维护 Map<userId, sessionId>
  • 若不在线,将消息存入数据库(offline_messages 表)
  • 用户上线时,拉取离线消息并推送
public void sendPrivateMessage(ChatMessage message) {
    if (isUserOnline(message.getRecipient())) {
        messagingTemplate.convertAndSendToUser(...);
    } else {
        offlineMessageService.save(message); // 存 DB
    }
}

// 用户上线时
@EventListener
public void onUserOnline(SessionConnectedEvent event) {
    String userId = getCurrentUserId(event);
    List&lt;ChatMessage&gt; offlineMsgs = offlineMessageService.getAndDelete(userId);
    offlineMsgs.forEach(msg -&gt; 
        messagingTemplate.convertAndSendToUser(userId, "/queue/offline", msg)
    );
}

五、注意事项 & 常见坑点

问题解决方案
跨域问题setAllowedOriginPatterns("*")(生产环境应限制域名)
集群部署单机内存存储会话 → 多节点无法互通 → 需用 Redis + WebSocket 消息广播
连接断开重连前端监听 onclose,自动重连 + 重新订阅
消息丢失关键消息需 ACK 机制 + 重试(WebSocket 本身不保证可靠)
安全认证握手时校验 Token(通过 HandshakeInterceptor

示例:Token 认证拦截器

public class HttpHandshakeInterceptor implements HandshakeInterceptor {
    @Override
    public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response,
                                   WebSocketHandler wsHandler, Map&lt;String, Object&gt; attributes) {
        if (request instanceof ServletServerHttpRequest) {
            ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
            String token = servletRequest.getServletRequest().getParameter("token");
            if (isValidToken(token)) {
                attributes.put("userId", parseUserId(token));
                return true;
            }
        }
        return false; // 拒绝连接
    }

    @Override
    public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response,
                               WebSocketHandler wsHandler, Exception exception) {}
}

注册到配置中:

registry.addEndpoint("/ws")
        .addInterceptors(new HttpHandshakeInterceptor())
        .setAllowedOriginPatterns("*");

六、替代方案对比

技术优点缺点适用场景
WebSocket真正双向、低延迟需要保持长连接聊天、实时协作、游戏
SSE (Server-Sent Events)简单、基于 HTTP仅服务端→客户端实时通知、日志流
MQTT over WebSocket轻量、支持 QoS需要额外 BrokerIoT 设备通信
gRPC-Web高性能、强类型浏览器支持有限内部微服务调用

七、总结

WebSocket 是构建实时 Web 应用的核心技术,关键要点:

  • 使用 STOMP + Spring Message Broker 简化开发
  • 私聊用 /user/queue,广播用 /topic
  • 连接状态管理 实现在线人数、离线消息
  • 集群部署需 Redis 广播(如 spring-boot-starter-redis + RedisMessageListener
  • 安全第一:握手时校验身份,避免未授权连接

到此这篇关于WebSocket详解及Spring Boot+Java高级实战用法示例的文章就介绍到这了,更多相关Spring Boot WebSocket详解内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

相关文章

  • Mybatis使用@param注解四种情况解析

    Mybatis使用@param注解四种情况解析

    这篇文章主要介绍了Mybatis使用@param注解四种情况解析,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-08-08
  • java中输出pdf文件代码分享

    java中输出pdf文件代码分享

    这篇文章主要介绍了java中输出pdf文件代码分享,本文直接给出实现代码,需要的朋友可以参考下
    2015-03-03
  • mapstruct中的@Mapper注解的基本用法

    mapstruct中的@Mapper注解的基本用法

    在MapStruct中,@Mapper注解是核心注解之一,用于标记一个接口或抽象类为MapStruct的映射器(Mapper),本文给大家介绍mapstruct中的@Mapper注解的相关知识,感兴趣的朋友一起看看吧
    2025-06-06
  • 浅谈Java中ArrayList线程不安全怎么办

    浅谈Java中ArrayList线程不安全怎么办

    本文主要介绍了Java中ArrayList线程不安全怎么办,主要有三种解决的方法,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2021-08-08
  • Java实现的简单数字时钟功能示例

    Java实现的简单数字时钟功能示例

    这篇文章主要介绍了Java实现的简单数字时钟功能,涉及java日期时间及JFrame框架图形界面操作相关实现技巧,需要的朋友可以参考下
    2019-02-02
  • SpringBoot的reload加载器的方法

    SpringBoot的reload加载器的方法

    本篇文章主要介绍了SpringBoot的reload加载器的方法,小编觉得挺不错的,现在分享给大家,也给大家做个参考。一起跟随小编过来看看吧
    2018-03-03
  • Spring Data JPA系列JpaSpecificationExecutor用法详解

    Spring Data JPA系列JpaSpecificationExecutor用法详解

    这篇文章主要为大家介绍了Spring Data JPA系列JpaSpecificationExecutor用法详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-09-09
  • Java责任链模式模板代码分享

    Java责任链模式模板代码分享

    这篇文章主要介绍了Java责任链模式模板代码分享,具有一定借鉴价值,需要的朋友可以参考下
    2018-01-01
  • Java SDK获取和监听nacos配置信息的方法

    Java SDK获取和监听nacos配置信息的方法

    本文手把手教你解决Nacos配置获取为null的常见问题,提供Java示例代码快速上手配置中心,并演示如何监听配置变化实现自动更新,从新建Maven项目到运行成功,涵盖版本兼容性、端口映射等关键步骤,助你轻松集成Nacos,需要的朋友可以参考下
    2026-07-07
  • 如何基于java语言实现八皇后问题

    如何基于java语言实现八皇后问题

    这篇文章主要介绍了如何基于java语言实现八皇后问题,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2019-12-12

最新评论