Springboot Websocket 实现

发布于:2025-08-08 ⋅ 阅读:(143) ⋅ 点赞:(0)

Springboot Websocket 实现

在 Spring Boot 中集成 WebSocket 可以快速实现实时双向通信功能,常用场景包括聊天系统、实时通知、在线协作等。Spring 提供了对 WebSocket 的原生支持,结合 @ServerEndpoint 注解或 Spring WebSocket 抽象层可快速开发。

核心原理

WebSocket 是一种在单个 TCP 连接上实现全双工通信的协议,允许客户端和服务器之间进行实时、双向的数据传输。与传统的 HTTP 协议相比,它解决了频繁轮询带来的性能问题,适用于实时聊天、实时数据更新、在线游戏等场景。

  1. 建立连接:HTTP 握手升级
    WebSocket 连接的建立依赖于 HTTP 协议的“握手升级”机制:

    • 客户端发送一个特殊的 HTTP 请求,声明要升级到 WebSocket 协议:
      GET /chat HTTP/1.1
      Host: example.com
      Upgrade: websocket  # 声明要升级的协议
      Connection: Upgrade  # 表示要升级连接
      Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==  # 随机字符串,用于验证服务器
      Sec-WebSocket-Version: 13  # 支持的 WebSocket 版本
      
    • 服务器确认升级后,返回 101 状态码(切换协议),并通过 Sec-WebSocket-Accept 字段验证客户端:
      HTTP/1.1 101 Switching Protocols
      Upgrade: websocket
      Connection: Upgrade
      Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=  # 由客户端的 Key 计算而来
      
    • 握手成功后,TCP 连接保持打开状态,后续通信不再使用 HTTP 协议,而是基于 WebSocket 帧格式。
  2. 数据传输:帧格式
    WebSocket 数据以“帧(Frame)”为单位传输,帧格式包含:

    • 操作码(Opcode):标识帧类型(如文本帧 0x1、二进制帧 0x2、关闭帧 0x8 等)。
    • 掩码(Mask):客户端发送的帧必须包含掩码(随机密钥),用于防止代理缓存污染。
    • 有效载荷(Payload):实际传输的数据(文本或二进制)。

    示例:客户端发送文本消息 Hello 时,会被封装为一个文本帧,服务器接收后解析帧得到原始数据。

  3. 全双工通信
    连接建立后,客户端和服务器可以同时双向发送数据,无需等待对方响应:

    • 客户端可以随时向服务器发送消息(如用户输入的聊天内容)。
    • 服务器也可以主动向客户端推送数据(如实时股价更新、新消息通知)。
  4. 连接关闭
    任何一方可发送“关闭帧”主动关闭连接,另一方确认后释放 TCP 连接。

与 HTTP 的区别

特性 HTTP WebSocket
通信方式 单向(客户端请求 → 服务器响应) 双向(客户端 ↔ 服务器 实时交互)
连接状态 无状态(每次请求独立,需重新建立连接) 持久连接(一次握手后保持连接)
数据格式 基于文本的请求头+响应体 二进制帧格式(更轻量)
适用场景 普通网页请求、API 调用 实时聊天、实时数据推送、在线协作

优势

  • 低延迟:避免 HTTP 频繁握手的开销,数据传输更高效。
  • 减少带宽:帧格式比 HTTP 头更简洁,尤其适合频繁小数据传输。
  • 实时性:服务器可主动推送数据,无需客户端轮询(如 setInterval 不断发请求)。

典型应用场景

  • 即时通讯(如微信网页版、在线客服)
  • 实时数据展示(如股票行情、监控仪表盘)
  • 多人协作工具(如在线文档共同编辑)
  • 在线游戏(实时同步玩家操作)

WebSocket 协议的出现,极大地优化了实时通信场景的性能,成为现代 Web 应用不可或缺的技术。

package cn.netkiller.websocket;

import cn.netkiller.record.WebsocketMessage;
import com.google.gson.Gson;
import jakarta.websocket.*;
import jakarta.websocket.server.PathParam;
import jakarta.websocket.server.ServerEndpoint;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.slf4j.Marker;
import org.slf4j.MarkerFactory;
import org.springframework.stereotype.Component;

import java.io.IOException;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.ConcurrentHashMap;

@Component
@ServerEndpoint(value = "/websocket/{appId}/{device}")
@Slf4j
public class WebsocketEndpoint {
   
   

    private static final ConcurrentHashMap<String, Session> sessions = new ConcurrentHashMap<String, Session>();
    private static final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
    private static final Marker Android = MarkerFactory.getMarker("Android");
    private final Gson gson = new Gson();
    //    private Session session;
    private String device;

    // 连接打开
    @SneakyThrows
    @OnOpen
    public void onOpen(Session session, EndpointConfig endpointConfig,
                       @PathParam("appId") String appId, @PathParam("device") String device
    ) {
   
   
        // 保存 session 到对象
//        this.session = session;
        this.device = device;
        sessions.put(device, session);
//        session.getBasicRemote().sendObject();
//        String jsonString = gson.toJson(new WebsocketMessage("OnOpen", device, "", new Date()));
//        session.getBasicRemote().sendText(jsonString);
        log.info("[websocket] onOpen:session={} device={}", session.getId(), device);
    }

    // 收到消息
    @OnMessage
    public void onMessage(String message) throws IOException {
   
   

        log.info("[websocket] onMessage:session={},message={}", sessions.get(device).getId(), message);

        WebsocketMessage websocketMessage = gson

网站公告

今日签到

点亮在社区的每一天
去签到