WebSocket 全面深度技术文档:原始协议、SockJS 模拟与 STOMP 发布/订阅体系

Spring WebSocket SpEL注入漏洞CVE-2018-1270:原理、复现与修复 表达式语言(Expression Language)在现代Web框架中广泛用于实现动态逻辑,例如Spring框架的SpEL(Spring Expression Language)为配置和运行时数据绑定提供了强大支持。其核心原理是将字符串表达式解析为可执行的指令,在特定上下文中进行求值,从而实现灵活的配置和数据处理。然而,当用户可控的输入未经充分验证直接传入表达式解析器时,就会引入严重的安全风险,即表达式注入漏洞。这类漏洞的技术价值在于攻击者可能利用其执行任意代码,实现从权限提升到远程命令执行(RCE)的突破 阅读详情

一、什么是 WebSocket?

WebSocket 是一种在单个 TCP 连接上实现全双工、持久化、低延迟双向通信的网络协议,由 IETF 标准化为 RFC 6455(2011 年),是 HTML5 的核心组成部分。

✅ 官方定义:
WebSocket 是一种在单个 TCP 连接上进行全双工通信的协议。WebSocket 通信协议在 2011 年由 IETF 定义为 RFC 6455,并由 IANA 注册端口号。

🌐 核心特性

特性说明
全双工客户端与服务器可同时发送和接收数据,无需等待对方响应
持久连接握手成功后连接长期保持,避免 HTTP 的频繁建连开销
低开销数据帧最小仅 2 字节(控制帧),无 HTTP 头部冗余
基于 TCP保证数据可靠、有序传输
协议升级通过 HTTP/1.1 Upgrade 机制从 HTTP 升级而来,兼容代理和防火墙
文本/二进制支持 UTF-8 文本(JSON)和任意二进制数据(图片、音频)

二、WebSocket 的核心作用

WebSocket 的诞生是为了解决传统 HTTP 协议在实时通信中的根本缺陷:

场景HTTP 的问题WebSocket 的解决方案
实时聊天需客户端每秒轮询 → 浪费带宽、延迟高(1~5秒)服务器主动推送 → 延迟 < 50ms,零轮询
股票行情每秒数百次更新 → 服务器压力巨大一条连接推送所有数据 → 资源节省 90%+
在线游戏玩家动作需秒级同步双向实时同步,支持每秒 20+ 次状态更新
协同编辑多人编辑文档,光标位置需实时同步操作事件广播,毫秒级响应
IoT 设备监控设备上报状态 + 服务器下发指令一条连接双向通信,省去双通道
实时通知系统消息、订单状态更新推送而非轮询,用户体验提升

💡 一句话总结作用:
WebSocket 让 Web 应用具备了与原生 App 一样的实时交互能力,是构建现代实时 Web 应用的基石。


三、WebSocket 原始交互:底层消息传递机制详解

1. WebSocket 握手流程(HTTP → WebSocket 升级)

✅ 客户端发起升级请求(HTTP)
GET /chat HTTP/1.1
Host: example.com:8080
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Version: 13
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
  • Connection: Upgrade:请求协议升级
  • Upgrade: websocket:指定升级为 WebSocket 协议
  • Sec-WebSocket-Key:客户端生成的 16 字节随机值(Base64 编码),用于防伪造
  • Sec-WebSocket-Version: 13:协议版本(RFC 6455)
✅ 服务端响应(101 Switching Protocols)
HTTP/1.1 101 Switching Protocols
Connection: Upgrade
Upgrade: websocket
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

🔐 Sec-WebSocket-Accept 计算方式:

String key = "dGhlIHNhbXBsZSBub25jZQ==";
String magic = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
String accept = Base64.encodeToString(
    SHA1(key + magic), 
    Base64.NO_WRAP
); // 结果:s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

✅ 握手成功后,TCP 连接从 HTTP 协议切换为 WebSocket 协议,后续通信不再使用 HTTP。


2. WebSocket 消息帧格式(二进制协议)

WebSocket 通信使用**二进制帧(Frame)**结构,每个帧包含:

字段长度说明
FIN1 bit是否为最后一帧(单消息可分片)
RSV1, RSV2, RSV33 bits保留位,用于扩展协议(通常为 0)
Opcode4 bits操作码,定义帧类型(见下表)
Mask1 bit是否启用掩码(客户端→服务端必须为 1)
Payload Length7/7+16/7+64 bits负载长度(0~125、126、127)
Masking-Key4 bytes仅当 Mask=1 时存在(用于防缓存攻击)
Payload Data可变实际数据(文本或二进制)
📌 Opcode 常见类型
Opcode名称说明
0x0Continuation分片消息的后续帧
0x1TextUTF-8 文本消息(如 JSON)
0x2Binary二进制数据(如图片、文件)
0x8Close关闭连接
0x9Ping心跳检测(服务端需回复 Pong)
0xAPong心跳响应
✅ 示例:发送一条文本消息 "Hello, WebSocket!"
81 8D 3F 1A 2C 4E 12 05 0B 1F 0C 0B 18 05 0D 1D 0D 1C 18
字段值说明
8110000001FIN=1, Opcode=0x1(Text)
8D10001101Mask=1, Payload Length=13(0x0D)
3F 1A 2C 4EMasking-Key客户端生成的 4 字节密钥
12 05 0B 1F 0C 0B 18 05 0D 1D 0D 1C 18Payload(异或后)原始数据 "Hello, WebSocket!" 经掩码异或处理

🔍 解码过程:对每个字节与 Masking-Key[i % 4] 进行 XOR 运算,还原明文。


3. 原始 WebSocket 客户端示例(JavaScript)

<!DOCTYPE html>
<html>
<head>
    <title>原始 WebSocket 示例</title>
</head>
<body>
    <h2>原始 WebSocket 通信</h2>
    <button onclick="connect()">连接</button>
    <button onclick="sendMessage()">发送消息</button>
    <button onclick="disconnect()">断开</button>
    <div id="messages"></div>

    <script>
        let ws = null;

        // 1. 建立连接
        function connect() {
            // 使用 wss:// 保证安全(生产环境必须)
            ws = new WebSocket('ws://localhost:8080/ws');

            // 监听连接打开
            ws.onopen = function(event) {
                console.log('✅ WebSocket 连接已建立');
                appendMessage('连接成功:服务器已就绪');
            };

            // 监听消息接收
            ws.onmessage = function(event) {
                const message = event.data;
                console.log('📥 收到消息:', message);
                appendMessage('收到:' + message);
            };

            // 监听连接关闭
            ws.onclose = function(event) {
                console.log('🔌 WebSocket 连接已关闭,代码:' + event.code);
                appendMessage('连接已断开(代码:' + event.code + ')');
            };

            // 监听错误
            ws.onerror = function(error) {
                console.error('❌ WebSocket 错误:', error);
                appendMessage('连接错误:' + error.message);
            };
        }

        // 2. 发送消息
        function sendMessage() {
            if (ws && ws.readyState === WebSocket.OPEN) {
                const msg = prompt('输入要发送的消息:');
                if (msg) {
                    ws.send(msg); // 发送文本消息(自动编码为 UTF-8)
                    appendMessage('发送:' + msg);
                }
            } else {
                alert('请先连接服务器!');
            }
        }

        // 3. 断开连接
        function disconnect() {
            if (ws) {
                ws.close(); // 发送 Close 帧(Opcode=0x8)
                ws = null;
            }
        }

        // 4. 显示消息到页面
        function appendMessage(text) {
            const div = document.createElement('div');
            div.textContent = text;
            div.style.margin = '5px 0';
            div.style.padding = '8px';
            div.style.border = '1px solid #ddd';
            div.style.borderRadius = '4px';
            document.getElementById('messages').appendChild(div);
        }
    </script>
</body>
</html>

4. 原始 WebSocket 服务端示例(Java + Spring Boot)

package com.example.websocketdemo;

import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;

/**
 * 原始 WebSocket 服务端端点
 * 
 * @ServerEndpoint("/ws"):注册 WebSocket 端点路径
 * 每个连接创建一个独立实例(@ServerEndpoint 是线程不安全的,需注意共享资源)
 */
@Component
@ServerEndpoint("/ws")
public class RawWebSocketEndpoint {

    // 存储所有活跃连接(线程安全)
    private static final CopyOnWriteArraySet<RawWebSocketEndpoint> clients = new CopyOnWriteArraySet<>();

    // 当连接建立时调用
    @OnOpen
    public void onOpen(Session session) {
        clients.add(this);
        System.out.println("✅ 新客户端连接,当前在线人数:" + clients.size());
        try {
            // 主动发送欢迎消息
            session.getBasicRemote().sendText("🎉 欢迎连接原始 WebSocket 服务!");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    // 当收到消息时调用
    @OnMessage
    public void onMessage(String message, Session session) {
        System.out.println("📥 收到消息:" + message);

        // 广播给所有客户端(群发)
        for (RawWebSocketEndpoint client : clients) {
            try {
                client.session.getBasicRemote().sendText("广播:" + message);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    // 当连接关闭时调用
    @OnClose
    public void onClose() {
        clients.remove(this);
        System.out.println("🔌 客户端断开,当前在线人数:" + clients.size());
    }

    // 当发生错误时调用
    @OnError
    public void onError(Session session, Throwable error) {
        System.err.println("❌ WebSocket 错误:" + error.getMessage());
        error.printStackTrace();
    }

    // 注入当前 Session(用于发送消息)
    private Session session;

    @OnOpen
    public void onOpen(Session session) {
        this.session = session; // 保存 session 实例
        clients.add(this);
        // ... 其他逻辑
    }
}

⚠️ 原始 WebSocket 的缺点:

  • 无标准消息格式(需自行设计 JSON 协议)
  • 无发布/订阅模型(需手动实现频道管理)
  • 无心跳机制(需手动实现 Ping/Pong)
  • 无用户认证(需在握手阶段传递 Token)
  • 无重连机制(需前端自行实现)

四、通过 SockJS 进行 WebSocket 模拟(降级兼容)

1. 为什么需要 SockJS?

尽管 WebSocket 是现代标准,但在以下场景中可能无法使用:

场景问题
企业防火墙拦截 Upgrade 头
老旧浏览器IE9、Android 4.4 以下不支持
代理服务器不支持 WebSocket 协议升级
CDN/负载均衡不支持长连接

💡 SockJS 的使命:
在不支持 WebSocket 的环境中,自动降级为兼容性更好的通信方式,但对外提供完全一致的 JavaScript API。

2. SockJS 支持的降级传输方式(优先级从高到低)

传输方式说明兼容性
WebSocket原生协议(首选)现代浏览器
xhr-streaming使用 XMLHttpRequest 长连接IE9+
xhr-polling使用 XMLHttpRequest 轮询IE8+
jsonp-polling使用 JSONP 跨域轮询IE6+(仅用于跨域)

✅ SockJS 客户端会自动选择最优可用通道,开发者无需关心。

3. SockJS 与 WebSocket 的对比

特性WebSocketSockJS
协议原生 WebSocket模拟 WebSocket API
延迟极低(<10ms)略高(轮询 1~5s)
带宽极低较高(每次请求有 HTTP 头)
兼容性现代浏览器IE6+、老旧设备
服务端依赖需 WebSocket 服务需 SockJS 服务端库
API 一致性new WebSocket()new SockJS(url) → 完全相同接口

4. SockJS 客户端示例(兼容所有浏览器)

<!DOCTYPE html>
<html>
<head>
    <title>SockJS 模拟 WebSocket</title>
    <!-- 引入 SockJS 客户端库 -->
    <script src="https://cdn.jsdelivr.net/npm/sockjs-client@1.6.1/dist/sockjs.min.js"></script>
</head>
<body>
    <h2>SockJS 模拟 WebSocket(兼容 IE8+)</h2>
    <button onclick="connect()">连接</button>
    <button onclick="sendMessage()">发送</button>
    <button onclick="disconnect()">断开</button>
    <div id="messages"></div>

    <script>
        let sock = null;

        function connect() {
            // 使用 SockJS 替代 WebSocket
            // SockJS 会自动选择最佳传输方式(WebSocket → xhr-streaming → xhr-polling)
            sock = new SockJS('http://localhost:8080/sockjs');

            sock.onopen = function() {
                console.log('✅ SockJS 连接成功(传输方式:' + sock.protocol + ')');
                appendMessage('连接成功(传输:' + sock.protocol + ')');
            };

            sock.onmessage = function(event) {
                console.log('📥 收到:', event.data);
                appendMessage('收到:' + event.data);
            };

            sock.onclose = function() {
                console.log('🔌 SockJS 连接已关闭');
                appendMessage('连接已断开');
            };

            sock.onerror = function(error) {
                console.error('❌ SockJS 错误:', error);
                appendMessage('连接错误:' + error.message);
            };
        }

        function sendMessage() {
            if (sock && sock.readyState === SockJS.OPEN) {
                const msg = prompt('输入消息:');
                if (msg) {
                    sock.send(msg); // 与 WebSocket API 完全一致
                    appendMessage('发送:' + msg);
                }
            } else {
                alert('请先连接!');
            }
        }

        function disconnect() {
            if (sock) {
                sock.close();
                sock = null;
            }
        }

        function appendMessage(text) {
            const div = document.createElement('div');
            div.textContent = text;
            div.style.margin = '5px 0';
            div.style.padding = '8px';
            div.style.border = '1px solid #ccc';
            div.style.borderRadius = '4px';
            document.getElementById('messages').appendChild(div);
        }
    </script>
</body>
</html>

5. SockJS 服务端配置(Spring Boot)

package com.example.websocketdemo.config;

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 // 启用 WebSocket 支持
public class SockJSConfig implements WebSocketConfigurer {

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        // 注册 SockJS 端点
        // 1. 使用 .addHandler() 注册处理器
        // 2. 使用 .withSockJS() 启用 SockJS 降级
        registry.addHandler(new RawWebSocketHandler(), "/sockjs")
                .setAllowedOriginPatterns("*") // 允许所有来源(开发用)
                .withSockJS(); // 启用 SockJS 兼容模式
    }
}

⚠️ 注意:RawWebSocketHandler 必须实现 WebSocketHandler 接口,而非 @ServerEndpoint。

package com.example.websocketdemo.handler;

import org.springframework.stereotype.Component;
import org.springframework.web.socket.*;
import org.springframework.web.socket.handler.TextWebSocketHandler;

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

@Component
public class RawWebSocketHandler extends TextWebSocketHandler {

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

    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        sessions.add(session);
        System.out.println("✅ SockJS 客户端连接成功,当前在线:" + sessions.size());
        session.sendMessage(new TextMessage("🎉 欢迎使用 SockJS 服务!"));
    }

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        System.out.println("📥 收到消息:" + message.getPayload());

        // 广播给所有连接
        for (WebSocketSession s : sessions) {
            if (s.isOpen()) {
                s.sendMessage(new TextMessage("广播:" + message.getPayload()));
            }
        }
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
        sessions.remove(session);
        System.out.println("🔌 客户端断开,当前在线:" + sessions.size());
    }
}

✅ SockJS 的优势:

  • API 与 WebSocket 完全一致,前端代码无需修改即可切换
  • 自动降级,确保老旧浏览器可用
  • 企业级兼容,金融、政务系统首选方案

五、通过 STOMP 作为 WebSocket 的子协议实现发布/订阅消息传递

1. 什么是 STOMP?

STOMP(Simple Text Oriented Messaging Protocol)是一种轻量级、基于文本的消息协议,最初为消息中间件(如 ActiveMQ、RabbitMQ)设计。它在 WebSocket 之上运行,提供发布/订阅(Pub/Sub) 和 点对点 消息模型。

✅ STOMP 协议特点:

  • 基于文本(可读性强)
  • 支持 SEND, SUBSCRIBE, UNSUBSCRIBE, CONNECT, DISCONNECT 等命令
  • 每条消息有 Headers 和 Body
  • 与 WebSocket 结合后,成为企业级实时通信标准

2. STOMP 消息格式示例

✅ 客户端发送消息(SEND)
SEND
destination:/app/chat
content-type:application/json
content-length:28

{"from":"张三","content":"你好!"}
✅ 服务器推送消息(MESSAGE)
MESSAGE
destination:/topic/public
subscription:sub-0
message-id:0
content-type:application/json
content-length:45

{"from":"系统","content":"张三:你好!","timestamp":"14:30:05"}
✅ 客户端订阅主题(SUBSCRIBE)
SUBSCRIBE
id:sub-0
destination:/topic/public
✅ 客户端断开(DISCONNECT)
DISCONNECT
receipt:123

💡 每条 STOMP 消息由 命令 + 头部 + 空行 + 负载 组成,以 \n 分隔。

3. STOMP 与原始 WebSocket 的对比

维度原始 WebSocketSTOMP over WebSocket
协议二进制帧文本命令(可读)
消息语义自定义 JSON标准化(SEND/SUBSCRIBE)
发布/订阅需手动实现频道原生支持(/topic, /queue)
用户认证手动传递 Token支持 login/passcode 头
消息确认无支持 receipt
客户端库原生 WebSocketstompjs(官方库)
适用场景高性能流企业级应用(聊天、通知)

4. STOMP 服务端配置(Spring Boot)

package com.example.websocketdemo.config;

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 // 启用 STOMP 消息代理
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    /**
     * 注册 STOMP 端点(客户端连接入口)
     * 
     * 为什么需要端点?
     *   - 客户端通过此 URL 建立 WebSocket(或 SockJS)连接
     *   - 服务端监听该路径,处理协议升级
     * 
     * withSockJS():启用 SockJS 兼容降级(推荐生产使用)
     */
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/chat")
                .setAllowedOriginPatterns("*")
                .withSockJS(); // 启用 SockJS 降级
    }

    /**
     * 配置消息代理(Message Broker)
     * 
     * enableSimpleBroker:使用内存代理(适用于单机部署)
     *   - /topic:广播主题(所有订阅者收到)
     *   - /queue:点对点队列(仅一个消费者收到)
     * 
     * setApplicationDestinationPrefixes:客户端发送消息的前缀
     *   - 客户端发送 /app/chat → 映射到 @MessageMapping("/chat")
     * 
     * setUserDestinationPrefix:用户私有消息前缀
     *   - 用于向特定用户推送:/user/{username}/queue/messages
     */
    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        // 客户端可订阅的主题前缀(广播/队列)
        registry.enableSimpleBroker("/topic", "/queue");

        // 客户端发送消息的前缀(必须以 /app 开头)
        registry.setApplicationDestinationPrefixes("/app");

        // 用户私有消息前缀(用于 @SendToUser)
        registry.setUserDestinationPrefix("/user");
    }
}

5. STOMP 消息控制器(@MessageMapping)

package com.example.websocketdemo.controller;

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

import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;

/**
 * STOMP 消息控制器
 * 
 * @MessageMapping("/chat"):接收客户端发送到 /app/chat 的消息
 * @SendTo("/topic/public"):广播到 /topic/public 主题
 * @SendToUser:向特定用户发送私有消息(需登录)
 */
@Controller
public class ChatController {

    /**
     * 接收客户端发送的聊天消息并广播
     * 
     * 客户端发送:/app/chat → 此方法接收
     * 返回值自动序列化为 JSON → 广播到 /topic/public
     */
    @MessageMapping("/chat")
    @SendTo("/topic/public")
    public ChatMessage sendMessage(ChatMessage message) {
        message.setTimestamp(LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
        System.out.println("📥 收到消息:" + message);
        return message; // 自动发送到 /topic/public
    }

    /**
     * 当客户端订阅 /app/hello 时,自动推送欢迎消息
     * 与 @MessageMapping 不同:这是“订阅触发”,非“发送触发”
     */
    @SubscribeMapping("/app/hello")
    public ChatMessage handleHello() {
        ChatMessage msg = new ChatMessage();
        msg.setFrom("系统");
        msg.setContent("🎉 欢迎使用 STOMP 聊天室!");
        msg.setTimestamp(LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
        return msg;
    }

    /**
     * 用户登录后,向其私有队列发送通知
     * 需要客户端在 CONNECT 时提供 login 头(如用户名)
     */
    @MessageMapping("/user.login")
    @SendToUser("/queue/notifications")
    public ChatMessage handleUserLogin(ChatMessage message, SimpMessageHeaderAccessor headerAccessor) {
        String username = message.getFrom();
        ChatMessage response = new ChatMessage();
        response.setFrom("系统");
        response.setContent(username + " 已上线!");
        response.setTimestamp(LocalDateTime.now().format(DateTimeFormatter.ofPattern("HH:mm:ss")));
        return response;
    }
}

6. STOMP 客户端示例(使用 stompjs)

<!DOCTYPE html>
<html>
<head>
    <title>STOMP over WebSocket 聊天室</title>
    <script src="https://cdn.jsdelivr.net/npm/sockjs-client@1.6.1/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 聊天室(支持发布/订阅)</h2>
    <input type="text" id="username" placeholder="输入用户名" />
    <button onclick="connect()">连接</button>
    <button onclick="disconnect()">断开</button>
    <input type="text" id="message-input" placeholder="输入消息..." />
    <button onclick="sendMessage()">发送</button>
    <div id="messages"></div>

    <script>
        let stompClient = null;
        let username = null;

        // 连接 STOMP 服务
        function connect() {
            username = document.getElementById('username').value.trim() || '游客' + Math.floor(Math.random() * 100);

            // 创建 SockJS 连接(兼容所有浏览器)
            const socket = new SockJS('http://localhost:8080/chat');

            // 创建 STOMP 客户端
            stompClient = Stomp.over(socket);

            // 连接(可选:携带登录信息)
            stompClient.connect({}, function (frame) {
                console.log('✅ STOMP 连接成功:' + frame);

                // 1. 订阅公共聊天频道(广播)
                stompClient.subscribe('/topic/public', function (message) {
                    const msg = JSON.parse(message.body);
                    appendMessage(msg.from, msg.content, msg.timestamp, 'other');
                });

                // 2. 订阅用户私有通知(仅自己收到)
                stompClient.subscribe('/user/queue/notifications', function (message) {
                    const msg = JSON.parse(message.body);
                    appendMessage(msg.from, msg.content, msg.timestamp, 'system');
                });

                // 3. 订阅欢迎消息(@SubscribeMapping)
                stompClient.subscribe('/app/hello', function (message) {
                    const msg = JSON.parse(message.body);
                    appendMessage(msg.from, msg.content, msg.timestamp, 'system');
                });

                // 4. 发送登录消息(触发 @MessageMapping("/user.login"))
                stompClient.send("/app/user.login", {}, JSON.stringify({from: username}));

                document.getElementById('message-input').placeholder = '你好,' + username + ',输入消息...';
            }, function (error) {
                console.error('❌ 连接失败:', error);
                appendMessage('系统', '连接失败,请检查服务端是否启动', '', 'system');
            });
        }

        // 发送消息
        function sendMessage() {
            const input = document.getElementById('message-input');
            const content = input.value.trim();
            if (!content || !stompClient || !stompClient.connected) return;

            // 发送消息到 /app/chat(映射到 @MessageMapping("/chat"))
            stompClient.send("/app/chat", {}, JSON.stringify({
                from: username,
                content: content,
                timestamp: ""
            }));

            // 显示自己发送的消息
            appendMessage(username, content, new Date().toLocaleTimeString(), 'me');
            input.value = '';
        }

        // 断开连接
        function disconnect() {
            if (stompClient && stompClient.connected) {
                stompClient.disconnect(function () {
                    console.log('👋 已断开 STOMP 连接');
                });
                stompClient = null;
            }
        }

        // 显示消息到页面
        function appendMessage(from, content, timestamp, type) {
            const div = document.createElement('div');
            div.style.margin = '5px 0';
            div.style.padding = '8px';
            div.style.borderRadius = '6px';
            div.style.maxWidth = '70%';

            if (type === 'me') {
                div.style.backgroundColor = '#e3f2fd';
                div.style.marginLeft = 'auto';
            } else if (type === 'system') {
                div.style.backgroundColor = '#f5f5f5';
                div.style.color = '#666';
                div.style.fontStyle = 'italic';
            } else {
                div.style.backgroundColor = '#f0f0f0';
            }

            div.innerHTML = `<strong>${from}</strong> (${timestamp})<br>${content}`;
            document.getElementById('messages').appendChild(div);
            document.getElementById('messages').scrollTop = document.getElementById('messages').scrollHeight;
        }

        // 回车发送
        document.getElementById('message-input').addEventListener('keypress', function (e) {
            if (e.key === 'Enter') sendMessage();
        });

        // 页面加载后自动连接
        window.onload = function () {
            connect();
        };
    </script>
</body>
</html>

7. STOMP 消息模型图解

┌─────────────────┐     ┌───────────────────────┐     ┌───────────────────┐
│   客户端 A       │     │      服务端           │     │   客户端 B        │
│                 │     │                       │     │                   │
│ SUBSCRIBE /topic/public │◄───┐  (STOMP Broker)  │───►│ SUBSCRIBE /topic/public │
│                 │     │                       │     │                   │
│ SEND /app/chat  │───► │ @MessageMapping("/chat") │◄───│ SEND /app/chat    │
│ {"from":"A"}    │     │  → 广播到 /topic/public │     │ {"from":"B"}      │
└─────────────────┘     └─────────┬─────────────┘     └───────────────────┘
                                  │
                                  ▼
                        ┌───────────────────────┐
                        │   /topic/public 消息    │
                        │   {"from":"A", ...}   │
                        └─────────┬─────────────┘
                                  │
                                  ▼
                        ┌───────────────────────┐
                        │   客户端 A 收到消息     │
                        │   客户端 B 收到消息     │
                        └───────────────────────┘

✅ 关键优势:

  • 解耦:客户端只关心订阅/发送主题,不关心其他客户端
  • 可扩展:增加新客户端无需修改服务端逻辑
  • 标准化:使用 destination、subscription、id 等标准头

六、完整技术栈对比总结

技术适用场景优点缺点
原始 WebSocket高性能流(游戏、视频)极低延迟、极小开销无标准协议、需自定义、难调试
SockJS企业级兼容(IE8+)100% API 兼容、自动降级带宽较高、延迟略高
STOMP over WebSocket企业级实时应用(聊天、通知)标准化、发布/订阅、易维护、可调试比原始 WebSocket 多一层封装
SockJS + STOMP生产环境首选兼容性 + 标准化 = 最佳实践配置稍复杂

✅ 推荐组合:
SockJS(兼容) + STOMP(语义) + Spring Boot(后端)
是构建生产级、跨平台、企业级实时 Web 应用的黄金组合。


七、生产环境最佳实践

实践说明
始终使用 WSS生产环境必须使用 wss://(WebSocket Secure)
启用 SockJS即使你不需要 IE 支持,也建议启用,提高网络穿透成功率
使用 STOMP避免自定义 JSON 协议,STOMP 已是行业标准
设置心跳在 configureMessageBroker 中配置 setHeartbeat 防止连接被防火墙断开
用户认证在 connect 时通过 login 和 passcode 头传递 JWT Token
消息持久化重要消息(如订单通知)应存入数据库,防止断线丢失
集群部署使用 Redis 作为 STOMP 消息代理(enableStompBrokerRelay)实现跨节点广播
监控连接数使用 SimpMessagingTemplate + @EventListener 监听连接事件

示例:配置心跳(防止连接被代理关闭)

@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
    registry.enableSimpleBroker("/topic", "/queue")
            .setHeartbeatValue(new long[]{10000, 10000}); // 客户端和服务端每10秒互相发送 Ping/Pong
    registry.setApplicationDestinationPrefixes("/app");
    registry.setUserDestinationPrefix("/user");
}

八、总结:WebSocket 生态全景图

┌────────────────────────────────────────────────────────────────────┐
│                         WebSocket 生态系统                           │
├────────────────────────────────────────────────────────────────────┤
│  1. 原始 WebSocket (RFC 6455) → 二进制帧,高性能,难维护               │
│  2. SockJS → 模拟 WebSocket API,兼容 IE6+,自动降级                 │
│  3. STOMP → 文本协议,发布/订阅,标准化消息模型                      │
│  4. SockJS + STOMP → 生产环境黄金组合(兼容+标准)                   │
│  5. Spring Boot Starter WebSocket → 自动配置,开箱即用                │
│  6. Redis + STOMP → 集群部署,跨节点广播                             │
└────────────────────────────────────────────────────────────────────┘

✅ 最终建议:

  • 学习路径:原始 WebSocket → SockJS → STOMP
  • 开发建议:永远使用 SockJS + STOMP 组合
  • 面试重点:理解握手过程、STOMP 消息格式、发布/订阅模型
  • 企业标准:STOMP over SockJS 是银行、政务、医疗系统首选方案

附录:完整项目结构(Spring Boot + SockJS + STOMP)

src/
├── main/
│   ├── java/
│   │   └── com/example/websocketdemo/
│   │       ├── WebSocketDemoApplication.java
│   │       ├── config/WebSocketConfig.java          // STOMP + SockJS 配置
│   │       ├── controller/ChatController.java       // @MessageMapping 控制器
│   │       └── model/ChatMessage.java               // 消息实体
│   └── resources/
│       └── static/
│           ├── chat.html                            // STOMP + SockJS 客户端
│           └── raw-websocket.html                   // 原始 WebSocket 客户端
│           └── sockjs-only.html                     // 仅 SockJS 客户端

✅ GitHub 模板推荐:
https://github.com/spring-projects/spring-framework/tree/main/spring-websocket/src/test/java/org/springframework/web/socket


📌 结语

WebSocket 不是“可选技术”,而是现代 Web 实时交互的基础设施。

  • 原始 WebSocket 是“引擎”,
  • SockJS 是“变速箱”,
  • STOMP 是“导航系统”。

掌握三者关系,你就能在任何网络环境下,构建出稳定、兼容、可扩展的实时应用。
从微信聊天到金融交易,从物联网到元宇宙,WebSocket 正在重新定义 Web 的边界。

💡 记住:
“用原始 WebSocket 做原型,用 SockJS + STOMP 做产品。”

Vue项目WebSocket安全加固实战:从认证授权到全链路防护 WebSocket作为一种在单个TCP连接上进行全双工通信的协议,为实时应用提供了高效的通信能力。其工作原理基于HTTP升级握手,建立持久连接后允许服务器主动推送数据,技术价值在于实现了低延迟、高并发的双向数据交换,广泛应用于在线聊天、实时数据大屏、协同编辑和金融行情推送等场景。然而,这种持久化连接也带来了独特的安全挑战,例如信息泄漏和连接劫持。在Vue单页面应用(SPA)中,由于连接生命周期长且状态管理集中,这些风险会被放大。因此,构建安全的WebSocket通信链路,需要前后端协同,实施包括强制WSS加 阅读详情

相关推荐

从PLC到MES:构建现代化电机工厂数据监控系统的技术实践

在工业自动化与智能制造领域,物联网(IoT)和微服务架构正成为驱动数字化转型的核心技术。其基本原理在于通过传感器、PLC等设备采集物理世界数据,经由工业网络汇聚至物联网平台,再通过微服务架构进行解耦、处理与分发,最终实现生产过程的透明化与智能化。这一技术体系的价值在于解决了传统制造中生产黑盒、质量控制滞后等核心痛点,为预测性维护、柔性生产等高级应用场景奠定了基础。本文聚焦于工业物联网数据流的落地实践,以电机工厂为具体场景,详细拆解了如何利用Spring Boot、Vue.js、MQTT及ECharts等技术

weixin_33923762的博客 300

StompJS使用文档总结

STOMP即Simple (or Streaming) Text Orientated Messaging Protocol,简单(流)文本定向消息协议,它提供了一个可互操作的连接格式,允许STOMP客户端与任意STOMP消息代理(Broker)进行交互。STOMP协议由于设计简单,易于开发客户端,因此在多种语言和多种平台上得到广泛地应用。 一、创建STOMP客户端 1、在web浏览器中使用普...

gw的博客 5540

SpringBoot智能图书馆座位预约系统设计与实现

微服务架构下的资源调度系统是现代分布式系统的关键技术,通过实时状态监控和智能分配算法可显著提升资源利用率。SpringBoot作为主流Java框架,其自动配置特性和丰富生态为系统开发提供了高效支持。结合WebSocket实时通信和Redis缓存技术,可构建高并发的座位预约系统,解决高校图书馆座位资源紧张问题。该系统采用Spring Data JPA与MyBatis混合持久层方案,支持多终端访问和智能推荐功能,典型应用场景还包括会议室管理、共享办公等资源分配领域。

weixin_30402343的博客 372

WebSocket—STOMP详解(官方原版)

WebSocket—STOMP

深圳市多克创新科技有限公司 1万+

sockjs.js完整

sockjs.js

sockjs文件

sockjs文件,配合stomp和spring websocket stomp协议使用,

终极STOMP.js实战指南:5分钟构建跨平台实时通信系统

在现代互联网应用开发中,实时通信已经成为不可或缺的核心需求。无论是即时聊天、实时数据推送还是物联网设备控制,都需要一个高效稳定的通信桥梁。STOMP.js作为一款专业的STOMP客户端库,能够轻松连接Web浏览器和Node.js应用程序,实现真正的跨平台实时通信。 ## 什么是STOMP.js及其核心价值 STOMP.js是一款专门为Web浏览器和Node.js应用设计的STOMP客户端库。它

gitblog_00003的博客 985

在vue中使用SockJS实现webSocket通信的过程

最近接到一个业务需求,需要做一个聊天信息的实时展示的界面,下面小编把实现过程记录下来,对vue中使用SockJS实现webSocket通信的相关知识感兴趣的朋友一起看看吧

STOMP.js终极实战指南:从零构建企业级实时通信系统

在当今快速发展的Web应用领域,实时通信已成为现代应用的核心需求。STOMP.js作为一款强大的STOMP客户端库,为Web浏览器和Node.js应用提供了完整的消息协议支持,让开发者能够轻松构建企业级的实时通信系统。本文将为你提供一份完整的STOMP.js实战指南,帮助你快速掌握这一关键技术。 ## 🚀 STOMP.js快速入门:核心概念解析 STOMP(Simple Text Orien

gitblog_00344的博客 1033

Verilog单周期CPU设计(超详细)

实验 单周期CPU一、设计目的与目标实验内容实验要求二、课程设计器材硬件平台软件平台三、 CPU逻辑设计总体方案指令模块MIPS指令格式指令处理流程数据通路总体结构图设计流程逻辑图四、模块详细设计PCAdd4PCINSTMEMDATAMEMSHIFTER32_L2SHIFTER_COMBINATIONMUX4X32MUX2X5EXT16T32MUX2X32CONUNITREGFILEALUSing...

Ace Cheney的博客 9万+

AFSim仿真系统中的图像处理器:从静态图像到视频流的目标识别技术解析

本文深入解析了AFSim仿真系统中WSF_IMAGE_PROCESSOR图像处理器的核心功能与技术实现。它详细阐述了该处理器如何从静态图像与视频流中完成目标检测、跟踪与识别,将原始像素数据转化为可供指挥系统使用的轨迹情报,并重点介绍了其基于约翰逊准则的目标识别进阶逻辑、关键参数配置以及脚本扩展能力,为构建高逼真度仿真应用提供了实践指导。

cola5的博客 213

翻译:SockJS-node文档(一)

什么是SockJS?  SockJS是一个提供Websocket通信的JavaScript库,目的是实现在浏览器与服务器之间低延迟、全双工、跨域通信,它提供跨浏览器的统一API,即使不支持HTML5 Websocket的浏览器也能通过SockJS实现Websocket通信,SockJS支持不同的后端脚本语音,包括NodeJS、Python、Java等,这里主要介绍SockJS-node,即支持N...

weixin_33873846的博客 650

最新版Node.js下载安装及环境配置教程(非常详细)

选择对应你系统的Node.js版本。

DN金猿的博客 12万+

30-seconds-of-react 源码解析:用 useMap Hook 让 Map 成为受控的组件状态

本文围绕 30-seconds-of-react 仓库中的 [useMap 片段](https://link.gitcode.com/i/c02b77ace360ddf36e4fb0b3fd55b5a1),剖析如何基于 `useState` 与 `useMemo` 封装出"有状态的 `Map` 对象 + 一组不可变操作函数",并通过完整可运行示例演示新增、删除、清空与序列化展示的实战写法。读完本文

gitblog_00142的博客 348

LSM6DS3TR-C IMU传感器入门:从MEMS原理到姿态解算实战

惯性测量单元(IMU)是现代智能设备实现运动感知的核心部件,其基础原理基于微机电系统(MEMS)技术。MEMS加速度计通过检测质量块在惯性力作用下的位移来测量线性加速度,而陀螺仪则利用科里奥利力效应感知角速度。这种技术使得微型化、低成本的6自由度运动测量成为可能,为机器人、无人机、可穿戴设备等提供了关键的空间姿态和运动状态信息。在实际工程中,开发者需要掌握I2C/SPI通信协议、传感器数据读取与校准,并通过互补滤波等算法融合加速度计和陀螺仪数据,实现稳定的姿态解算。LSM6DS3TR-C作为一款高性价比的6

weixin_28684655的博客 595

RK3568驱动XS9922B:AHD/CVBS双模摄像头兼容性实战

在嵌入式视频开发中,摄像头驱动的稳定性与兼容性往往决定项目交付质量。Linux内核的V4L2框架提供了统一的视频采集抽象,从v4l2_subdev子设备到media controller链路,再到用户态视频节点,每一层都需要精确配置。对于RK3568这类边缘计算平台,其MIPI CSI接口和自研ISP配合RKCIF,构成了完整的多路视频接入通路。当需要接入模拟视频解码芯片时,驱动设计就需同时考虑AHD高清协议与CVBS标清信号的模式切换、格式协商以及寄存器配置。XS9922B作为支持双模输入的解码芯片,可与

weixin_34216614的博客 346

Spring Boot + WebSocket 实现用户在线状态与活跃度追踪系统

在实时Web应用中,用户状态管理是提升交互体验的关键技术。其核心原理是通过WebSocket建立持久连接,结合心跳检测机制来判定用户的网络连通性,并利用用户行为追踪来区分活跃与空闲状态。这项技术的价值在于能够为社交、协同办公、在线客服等场景提供精准的在线感知能力,从而增强用户间的实时互动。本文聚焦于应用场景中的用户“发呆”状态识别,通过整合Spring Boot、Redis和Vue等技术栈,手把手构建一套高可用的状态追踪系统,为开发者提供从后端状态机设计到前端动态渲染的完整工程实践方案。

weixin_34320159的博客 323

基于YOLOv8的包装商品智能检测系统完整指南—含论文、代码

《基于YOLOv8的包装商品智能检测系统》摘要: 本文提出了一套完整的零售商品自动检测解决方案,利用YOLOv8深度学习框架实现高精度、高效率的商品识别。系统针对SKU110K大规模商品数据集(含11,762张高密度货架图像)进行优化,包含数据预处理、模型训练、推理检测和可视化界面全流程。详细介绍了从CSV标注到YOLO格式的转换方法、环境配置指南及技术架构设计。该系统可应用于智能零售、物流仓储等多个场景,有效解决人工盘点效率低、误差大的痛点。实验表明,该系统具备毫秒级检测速度和批量处理能力,为商业数字化转

FJN110的博客 205

JMeter压测SpringBoot WebSocket:从插件安装到结果分析的完整实践

WebSocket作为实现全双工通信的核心技术,其原理基于HTTP协议升级,通过在单个TCP连接上建立持久通道,支持服务端与客户端之间的实时双向数据交换。这一特性使其在在线聊天、实时通知、协同编辑等需要低延迟、高并发的应用场景中具有重要技术价值。为了确保WebSocket服务在高负载下的稳定性和性能,性能压测成为工程实践中不可或缺的环节。JMeter作为广泛使用的开源压测工具,通过安装WebSocket Samplers等插件,可以模拟海量并发连接与消息推送,有效评估服务的吞吐量、响应时间及资源消耗。本文聚

afhu47033的博客 476

RH850从0搭建Autosar开发环境【2X】- Davinci Configurator之EcuC模块配置详解(上)

RH850从0搭建Autosar开发环境【2X】- Davinci Configurator之EcuC模块配置详解(上)

weixin_42412049的博客 1529

2015年高教社杯全国大学生数学建模竞赛 A 题:《太阳影子定位》真题解析与 MATLAB 解决方案

🏆 本文已收录于专栏:《滚雪球学数学建模(含历年真题)》,持续更新最新及历年数学建模竞赛资料,每篇内容力求做到“问题讲透、模型讲清、代码可用、论文可参考”,帮助你从零开始逐步建立完整的数学建模知识体系。

**My Coding Family** 1356

WebSocket测试全攻略:从单元测试到性能压测的工程实践

WebSocket作为实现实时双向通信的核心技术,其工作原理基于HTTP协议升级机制,通过在单个TCP连接上实现全双工通信,为应用带来了低延迟、高效率的数据交换能力。这一技术价值在在线聊天、实时数据推送、协同编辑等场景中尤为突出。然而,WebSocket连接的稳定性、消息格式的正确性以及异常处理机制,直接关系到实时应用的可靠性。在工程实践中,开发者常面临连接管理、心跳机制、消息分片等挑战,需要借助专业的测试工具和方法来保障质量。本文聚焦于WebSocket测试的工程化解决方案,深入探讨了如何利用jest-w

weixin_30553777的博客 340

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

基于Redis GEO与Spring Boot实现高效地理位置触发功能

地理位置服务(LBS)是现代移动应用的核心技术之一,其原理是通过GPS、基站或Wi-Fi获取设备坐标,并利用高效算法进行邻近度计算。这项技术的价值在于能够实现基于位置的实时交互,广泛应用于社交互动、O2O引流和AR游戏等场景。在工程实践中,Redis GEO数据结构因其O(logN)的查询复杂度,成为处理动态位置数据的首选方案。本文聚焦于如何利用Spring Boot与Redis GEO构建一个高性能的“靠近触发”系统,通过定时任务与WebSocket实现实时事件通知,为类似社交应用中的趣味互动或地理围栏签

weixin_30299709的博客 376

蓝牙模块HM-10手册

蓝牙模块HM-10手册,该手册对蓝牙模块HM-10进行了详细的介绍和使用方法以及一些AT指令。

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

SSM+Vue酒店管理系统开发与毕业设计实践

企业级应用开发中,SSM框架(Spring+SpringMVC+MyBatis)因其成熟的事务管理能力和SQL优化特性,成为处理复杂业务逻辑的首选方案。结合Vue.js的响应式前端架构,可实现高效的前后端分离开发模式。这种技术组合特别适合酒店管理系统这类需要实时数据同步(如房态更新)和高并发事务处理(如预订冲突检测)的场景。通过WebSocket+Redis的发布订阅机制,系统能够实现毫秒级的房态信息推送,配合MyBatis的精细SQL控制,确保在高频查询场景下的性能稳定。毕业设计选择该课题,既能掌握主流企

weixin_34310369的博客 532

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

Stomp Over Websocket文档

前言 前两天整理了websocket的资料,今天就把上次没说完的Stomp.js好好说一说~ Stomp Over Webscoket参考文档:http://jmesnil.net/stomp-webs... 本文为参考文档的部分翻译,技术不佳,如有失误请指正。本文转载自个人博客:http://lsxj615.com/2016/08/...

weixin_33682790的博客 1106

WebSocket-SockJS

一、什么是SockJS SockJS是一个JavaScript库,提供跨浏览器JavaScript的API,创建了一个低延迟、全双工的浏览器和web服务器之间通信通道。 服务端:sockjs-node(https://github.com/sockjs/sockjs-node) 客户端:sockjs-clien(https://github.com/sockjs/sockjs-client)

李刚的学习专栏 3万+
上一篇: WebSocket 详细技术文档:原理、作用、特点与 HTTP 对比
下一篇: 基于 Spring Boot + WebSocket + RabbitMQ + Redis 的企业级实时聊天系统(含消息历史与时间戳智能提示)
龙茶清欢
博客等级 码龄7年 3720粉丝 375原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

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

打赏作者

龙茶清欢

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值