springmvc websocket 的用法

什么是 WebSocket?

WebSocket 是一种网络通信协议,它实现了客户端(通常是浏览器)与服务器之间的全双工、双向、长连接的通信通道。它解决了传统 HTTP 协议在实时通信场景下的痛点:

  • HTTP 的短板 :HTTP 协议是无状态、单向的。客户端发起请求,服务器响应后连接就关闭。要实现实时数据推送(如聊天消息、实时行情),只能通过低效的"轮询"(客户端不断向服务器发送请求询问是否有新数据),这浪费了大量带宽和服务器资源。
  • WebSocket 的优势 :WebSocket 在初次握手后,会建立一个持久化的长连接 。在这个连接上,服务器和客户端可以随时、主动地向对方发送数据,实现了真正的实时、低延迟、高效的双向通信。

WebSocket 的通信原理

WebSocket 的通信过程可以分为两个阶段:握手连接数据传输

1. 握手连接 (Handshake)

WebSocket 并非凭空建立连接,它巧妙地利用了 HTTP 协议来完成初始握手,以此兼容现有的网络基础设施(如防火墙、代理服务器)。

  • 客户端请求 (HTTP Upgrade Request) :客户端首先发送一个标准的 HTTP 请求,但这个请求包含特殊的头信息,表明它希望将协议升级(Upgrade) 为 WebSocket。

    GET /chat HTTP/1.1

    Host: server.example.com

    Upgrade: websocket

    Connection: Upgrade

    Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ== // 一个随机生成的Base64编码的密钥

    Sec-WebSocket-Version: 13

    Origin: http://example.com

    • Upgrade: websocketConnection: Upgrade:表明客户端希望升级协议。
    • Sec-WebSocket-Key:一个随机密钥,用于安全验证。
    • Sec-WebSocket-Version:指定协议版本(13是当前最广泛使用的版本)。
  • 服务器响应 (HTTP Switching Protocols) :服务器如果支持 WebSocket,会返回一个特殊的 HTTP 响应(状态码 101)。

    HTTP/1.1 101 Switching Protocols

    Upgrade: websocket

    Connection: Upgrade

    Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo= // 由客户端的Key计算得出的值

    • 状态码 101 表示协议切换成功。
    • Sec-WebSocket-Accept 是服务器使用标准算法对客户端的 Sec-WebSocket-Key 处理后的结果,用于验证连接的有效性,防止意外的跨协议攻击。

一旦握手成功,TCP连接保持不变,但通信协议从此从 HTTP 切换到了 WebSocket 协议。

2. 数据传输 (Data Transfer)

握手完成后,连接保持打开状态,进入全双工通信阶段。

  • 数据帧 (Frames):数据被分割成一个个的"帧(Frame)"进行传输。WebSocket 协议定义了如何封装数据帧,帧头包含操作码(Opcode)来指明这是文本数据、二进制数据、还是控制帧(如连接关闭、心跳ping/pong)。
  • 双向通信 :在此通道上,服务器可以不再等待客户端请求,直接主动推送(Push)数据给客户端。客户端也可以随时发送数据给服务器。所有通信都在同一个TCP连接上完成,开销极小(帧头只有2-10字节),效率远高于HTTP。

3. 连接关闭

任何一方都可以发起关闭连接的请求,发送一个关闭帧(Close Frame),另一方回应后,连接终止。


WebSocket 的使用场景

WebSocket 适用于所有需要高实时性、低延迟的Web应用:

  1. 实时聊天应用:最经典的场景。如微信网页版、在线客服系统、群聊工具,消息需要瞬间送达。
  2. 多人在线游戏:网页游戏需要实时同步玩家位置、状态和动作。
  3. 实时数据推送
    • 金融财经:股票、期货的实时价格变动、K线图。
    • 体育博彩:实时赔率更新。
    • 监控系统:实时服务器性能指标、日志流。
  4. 协同工具:如在线文档(Google Docs)、协同绘图工具,实时看到他人的编辑光标和操作。
  5. 物联网 (IoT):实时显示和控制智能设备的状态。

使用 Spring MVC 实现一个简单的 WebSocket 应用

我们将使用 Spring 框架提供的 spring-websocket 模块来实现一个简单的广播式聊天室。

1. 添加依赖 (Maven)

pom.xml 中添加依赖:

xml 复制代码
<!-- WebSocket -->
<dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-websocket</artifactId>
   <version>5.3.23</version> <!-- 请使用你的Spring版本 -->
</dependency>
<!-- 为了简化,使用内置的STOMP代理。生产环境建议用RabbitMQ或ActiveMQ -->
<dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-messaging</artifactId>
   <version>5.3.23</version>
</dependency>

2. 启用 WebSocket 支持

创建一个Java配置类 WebSocketConfig

java 复制代码
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 // 启用WebSocket消息代理
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

  @Override
  public void registerStompEndpoints(StompEndpointRegistry registry) {
      // 注册一个STOMP端点,客户端将使用它来连接到我们的WebSocket服务器
      // withSockJS() 提供了降级方案,在不支持WS的浏览器中使用其他方式模拟
      registry.addEndpoint("/ws").withSockJS();
  }

  @Override
  public void configureMessageBroker(MessageBrokerRegistry registry) {
      // 设置消息代理的前缀
      // 以/app开头的消息将被路由到@MessageMapping注解的方法
      registry.setApplicationDestinationPrefixes("/app");
      
      // 以/topic开头的消息将被路由到消息代理,再广播给所有连接的客户端
      // (内置的简单内存消息代理)
      registry.enableSimpleBroker("/topic");
  }
}

3. 创建消息控制器

创建一个普通的Spring MVC控制器来处理消息:

java 复制代码
 import org.springframework.messaging.handler.annotation.MessageMapping;
import org.springframework.messaging.handler.annotation.SendTo;
import org.springframework.stereotype.Controller;

@Controller
public class ChatController {

   // 处理所有发送到 `/app/chat` 的消息
   @MessageMapping("/chat")
   
   // 将方法的返回值广播给所有订阅了 `/topic/messages` 的客户端
   @SendTo("/topic/messages")
   public ChatMessage sendMessage(ChatMessage message) {
       // 这里可以添加业务逻辑,如保存到数据库
       return message; // 直接将接收到的消息广播出去
   }
}


**4. 创建消息实体类**

public class ChatMessage {
   private String from;
   private String text;

   // 必须有无参构造函数
   public ChatMessage() {
   }

   public ChatMessage(String from, String text) {
       this.from = from;
       this.text = text;
   }
   // Getter and Setter 方法
   public String getFrom() { return from; }
   public void setFrom(String from) { this.from = from; }
   public String getText() { return text; }
   public void setText(String text) { this.text = text; }
}

5. 前端客户端 (HTML + JavaScript)

创建一个 index.html 页面:

html 复制代码
<!DOCTYPE html>
<html>
<head>
   <title>WebSocket Chat</title>
   <script src="https://cdn.jsdelivr.net/npm/sockjs-client@1/dist/sockjs.min.js"></script>
   <script src="https://cdnjs.cloudflare.com/ajax/libs/stomp.js/2.3.3/stomp.min.js"></script>
</head>
<body>
   <div id="messageArea"></div>
   <form>
       <input type="text" id="messageInput" placeholder="Type a message..." />
       <button type="button" onclick="sendMessage()">Send</button>
   </form>

   <script>
       // 建立连接
       const socket = new SockJS('/ws'); // 连接到我们注册的端点
       const stompClient = Stomp.over(socket);

       stompClient.connect({}, function (frame) {
           console.log('Connected: ' + frame);
           // 订阅广播地址,当服务器向/topic/messages发送消息时,这里的回调函数会被触发
           stompClient.subscribe('/topic/messages', function (messageOutput) {
               showMessage(JSON.parse(messageOutput.body));
           });
       });

       function sendMessage() {
           const from = "User"; // 在实际应用中,这应该是登录的用户名
           const text = document.getElementById('messageInput').value;
           // 向服务器发送消息,目的地是 /app/chat
           stompClient.send("/app/chat", {}, JSON.stringify({'from': from, 'text': text}));
           document.getElementById('messageInput').value = '';
       }

       function showMessage(message) {
           const messageArea = document.getElementById('messageArea');
           const messageElement = document.createElement('p');
           messageElement.textContent = message.from + ": " + message.text;
           messageArea.appendChild(messageElement);
       }
   </script>
</body>
</html>

总结与运行

  1. 将以上代码整合到你的Spring Boot或Spring MVC项目中。
  2. 启动应用服务器。
  3. 在浏览器中打开 http://localhost:8080/index.html(端口号根据你的配置调整)。
  4. 打开多个浏览器窗口,在一个窗口中发送消息,所有其他窗口都会实时收到广播的消息。

这个例子使用了 STOMP 子协议,它是在 WebSocket 之上提供了一个更高级的、基于帧的消息格式,类似于 HTTP,使得在客户端和服务器之间传递消息变得更加简单和结构化。

相关推荐
莫叫石榴姐1 小时前
ast 在 Dify 工作流中解析 JSON 格式数据的深度解析
大数据·网络·安全·json
科技块儿2 小时前
如何使用IP数据云数据库接入流量监控?
数据库·网络协议·tcp/ip
沉醉不知处2 小时前
远程连接虚拟机,设置网络后,ip不变
服务器·网络·tcp/ip
路溪非溪2 小时前
UBUS基本使用总结
linux·网络·arm开发·智能路由器
爱尔兰极光2 小时前
计算机网络--数据链路层
服务器·网络·计算机网络
全栈工程师修炼指南2 小时前
Nginx | HTTPS 加密传输:客户端与Nginx服务端 SSL 双向认证实践
运维·网络·nginx·https·ssl
init_23612 小时前
Hub-Spoke mpls配置
网络
诸神黄昏EX2 小时前
Android Qualcomm USB 专题系列【总篇:USB HAL架构】
android·linux·网络
zfj3212 小时前
websocket为什么需要在tcp连接成功后先发送一个标准的http请求,然后在当前tcp连接上升级协议成websocket
websocket·tcp/ip·http