webSocket

2022/08/10 rpc 共 32406 字,约 93 分钟
闷骚的程序员

1. 什么是webSocket?

webSocket和http通信协议类似,都是基于TCP协议扩展出来的通信协议

WebSocket简介

WebSocket 是一种全双工的通信协议,通过单个TCP连接提供持久化的双向通信能力。与HTTP不同,WebSocket在建立连接后,不需要每次通信都重新发送HTTP头部信息,从而减少了网络开销和延迟。

WebSocket特点

  1. 全双工通信:只要客户端和服务端成功建立了webSocket连接之后,客户端和服务器就可以随时互相发送消息。
  2. 低延迟:比HTTP轮询效率更高,webSocket是客户端和服务端建立的TCP长连接,为了避免连接中断,轮询发送心跳探测,类似于“ping-pong”探测。
  3. 状态保持:连接建立后可长时间保持,不需要频繁握手,这正是因为webSocket建立的是TCP长连接导致的现象。
  4. 跨语言支持:广泛支持各种语言和框架。

在Spring Boot微服务中使用WebSocket

Spring Boot 提供了对 WebSocket 的良好支持,可以轻松集成 WebSocket 功能。以下是实现步骤和具体编码案例:


1. 添加依赖

确保在 pom.xml 中添加以下依赖:

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

2. 编写 WebSocket 配置

使用 @Configuration 配置 WebSocket。

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
public class WebSocketConfig implements WebSocketConfigurer {

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(new MyWebSocketHandler(), "/ws") // 注册WebSocket处理器
                .setAllowedOrigins("*"); // 允许跨域
    }
}

3. 编写 WebSocket 处理器

实现 WebSocketHandler 接口来处理 WebSocket 消息。

import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import org.springframework.web.socket.TextMessage;

public class MyWebSocketHandler extends TextWebSocketHandler {

    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        System.out.println("WebSocket连接已建立: " + session.getId());
    }

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        System.out.println("收到消息: " + message.getPayload());
        // 响应客户端
        session.sendMessage(new TextMessage("服务器已收到你的消息: " + message.getPayload()));
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        System.out.println("WebSocket连接已关闭: " + session.getId());
    }
}

4. 创建前端页面进行测试

编写一个简单的HTML页面测试 WebSocket 功能:

<!DOCTYPE html>
<html>
<head>
    <title>WebSocket Test</title>
</head>
<body>
    <h1>WebSocket测试</h1>
    <button onclick="connect()">连接</button>
    <button onclick="sendMessage()">发送消息</button>
    <button onclick="disconnect()">断开连接</button>

    <script>
        let socket;

        function connect() {
            socket = new WebSocket("ws://localhost:8080/ws");
            socket.onopen = function () {
                console.log("WebSocket连接已建立");
            };
            socket.onmessage = function (event) {
                console.log("收到消息: " + event.data);
            };
            socket.onclose = function () {
                console.log("WebSocket连接已关闭");
            };
        }

        function sendMessage() {
            if (socket) {
                socket.send("Hello Server!");
            }
        }

        function disconnect() {
            if (socket) {
                socket.close();
            }
        }
    </script>
</body>
</html>

实战案例:实现一个实时消息推送系统

需求

  • 实现一个系统,用户可以通过浏览器向服务器发送消息,服务器将消息推送给所有连接它的webSocket客户端。

代码实现

  1. 使用 SimultaneousWebSocketHandler 处理多客户端连接 webSocketHandler是客户端和服务端进行互发消息的关键组件,不论是客户端发送的消息,还是服务端向客户端发送的消息,都是通过webSocketHandler处理器负责处理的

    import org.springframework.web.socket.TextMessage;
    import org.springframework.web.socket.WebSocketSession;
    import org.springframework.web.socket.handler.TextWebSocketHandler;
    
    import java.util.Collections;
    import java.util.Set;
    import java.util.concurrent.ConcurrentHashMap;
    
    public class SimultaneousWebSocketHandler extends TextWebSocketHandler {
    
        private final Set<WebSocketSession> sessions = Collections.newSetFromMap(new ConcurrentHashMap<>());
    
        // 当webSocket客户端和服务端成功建立连接之后回调的方法,一般用于将“客户端和服务端”建立的webSocketSession对象进行保存,以便后续互发消息
        @Override
        public void afterConnectionEstablished(WebSocketSession session) throws Exception {
            sessions.add(session);
            System.out.println("新客户端连接: " + session.getId());
        }
    
        // 当“客户端和服务端”进行互发消息时,收到消息后回调的方法
        @Override
        protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
            System.out.println("收到消息: " + message.getPayload());
            // 广播消息给所有连接的客户端
            for (WebSocketSession s : sessions) {
                s.sendMessage(new TextMessage("广播消息: " + message.getPayload()));
            }
        }
    
        // 当“客户端和服务端”的连接断开时回调的方法
        @Override
        public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
            sessions.remove(session);
            System.out.println("客户端断开: " + session.getId());
        }
    }
    
  2. 修改 WebSocketConfig 配置

    @Configuration
    // 用于开启webSocket配置
    @EnableWebSocket
    public class WebSocketConfig implements WebSocketConfigurer {
    
        // 进行webSocket连接配置,用于注册webSocket的消息处理器,拦截路径,是否支持跨域等
        @Override
        public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
            registry.addHandler(new SimultaneousWebSocketHandler(), "/broadcast")
                    .setAllowedOrigins("*");
        }
    }
    
  3. 测试

    在前端页面连接 /broadcast 路径,多个客户端可以互相发送和接收广播消息。 一旦客户端页面连接’/broadcast’路径,则客户端和服务端就成功建立了webSocket连接,此后不论是客户端主动向服务端发送消息,还是服务端主动向客户端发送消息,都可以实现消息互发。


进阶功能

  1. 使用 STOMP 实现消息队列支持: 配合 Spring WebSocket 和 STOMP 协议,可以实现更复杂的消息队列功能。这意味着当服务端主动向所有连接的webSocket客户端发送消息时,可以先将消息缓存到MQ中,然后通过消费MQ再将消息推送给所有连接的webSocket客户端。

  2. 添加认证: 可以通过拦截器或认证机制限制 WebSocket 的访问权限。

  3. 结合 Redis 实现分布式支持: 使用 Redis Pub/Sub 广播消息,支持多个服务实例共享消息。


以上是 WebSocket 在 Spring Boot 微服务中的基础和实战案例,深入学习可以根据实际需求扩展,如与 Kafka 集成实现实时大数据处理等。

2. webSocket协议和http协议的异同点

WebSocket与HTTP的对比优势

WebSocket和HTTP是两种不同的通信协议,各有适用场景。以下是WebSocket相对于HTTP的主要优势:

1. 双向通信

  • HTTP:基于请求-响应模型,客户端必须发起请求后,服务器才能返回响应。服务器不能主动向客户端推送数据。
  • WebSocket:全双工通信,客户端和服务器建立webSocket连接后,客户端和服务器可以实时互相发送数据,无需请求触发。

2. 持久连接

  • HTTP:连接通常是短暂的,请求完成后连接关闭,后续请求需要重新建立连接(除非使用 HTTP/2 的长连接)。
  • WebSocket:在建立连接后,可以保持长时间的活动连接,减少多次建立连接的开销。

3. 更低的延迟

  • HTTP:每次通信都需要附带较大的头部信息,尤其是对于轮询的场景,产生额外的流量和延迟。
  • WebSocket:头部信息较小,且连接建立后不会频繁开销,特别适合低延迟场景。

4. 高效的实时通信

  • WebSocket适用于实时更新、高频交互的场景,例如:
    • 实时聊天(如微信、Slack)
    • 实时游戏(多人在线对战)
    • 股票价格推送、订单状态更新

5. 服务器资源节约

  • HTTP轮询会持续发送请求,增加服务器负载。
  • WebSocket使用一个持久连接,可以降低服务器资源消耗。

如何通过POSTMAN请求调试WebSocket

WebSocket本身是一种独立的协议,建立连接时需要使用 HTTP 协议完成握手,因此可以使用 POSTMAN 或其他 HTTP 工具调试 WebSocket 的握手过程。

步骤1:查看握手请求

WebSocket 握手使用 HTTP 协议的升级机制,首先发送一个 HTTP 请求头(通常是 GET 请求),并带上 Upgrade 头部字段:

GET /ws HTTP/1.1
Host: example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13

调试工具

  • 可以使用 PostmancURL 工具发送模拟请求。
  • 示例:
curl -i -N \
  -H "Connection: Upgrade" \
  -H "Upgrade: websocket" \
  -H "Host: localhost:8080" \
  -H "Origin: http://localhost" \
  -H "Sec-WebSocket-Version: 13" \
  -H "Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==" \
  http://localhost:8080/ws

步骤2:调试WebSocket消息

由于 POST 请求本质是用于 HTTP 而非 WebSocket,因此调试 WebSocket 消息的内容需要基于 WebSocket 客户端或专用工具,比如:

  1. 浏览器控制台
    • 在 Chrome/Firefox 开发者工具中,打开 Network 标签。
    • 过滤 WS(WebSocket)流量,检查消息的发送和接收内容。
  2. 专用调试工具
    • Postman 支持 WebSocket 的消息调试:
      • 打开 Postman,选择 WebSocket 选项。
      • 输入 WebSocket URL(例如 ws://localhost:8080/ws)。
      • 建立连接后,手动发送或接收消息。
  3. 使用 WebSocket 客户端工具
    • websocat:一个命令行 WebSocket 客户端。
      websocat ws://localhost:8080/ws
      

完整WebSocket调试示例

1. 服务端代码

一个简单的Spring Boot WebSocket服务器:

import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;

public class EchoWebSocketHandler extends TextWebSocketHandler {

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        System.out.println("收到消息: " + message.getPayload());
        session.sendMessage(new TextMessage("服务器回应: " + message.getPayload()));
    }
}

2. 配置服务端

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(new EchoWebSocketHandler(), "/ws")
                .setAllowedOrigins("*");
    }
}

3. 使用 Postman 调试 WebSocket 消息

  1. 打开 Postman,输入 WebSocket URL ws://localhost:8080/ws
  2. 点击 “Connect” 建立连接。
  3. 发送一条消息,例如 "Hello WebSocket!",查看服务器的响应。

总结

  • WebSocket 的优势在于实时、低延迟、双向通信,适合实时交互的场景。
  • 通过 POST 调试主要用来检查握手过程,而消息调试需要使用专门的 WebSocket 客户端工具。
  • 推荐工具如 Postman、Chrome 开发者工具和 websocat,方便测试和调试 WebSocket 应用。

3. webSocket在springboot服务中的使用场景

WebSocket在Spring Boot中的适用场景

WebSocket 是一种为实时通信设计的协议,它和 RESTful API 之间存在本质区别。RESTful API 通常用于请求-响应模式的操作,而 WebSocket 适用于需要高频率、低延迟实时双向通信的场景。以下是 WebSocket 在 Spring Boot 服务中常见的适用场景,以及其与 REST 的对比和应用场景分析。


1. WebSocket 的适用场景

(1) 实时消息推送

WebSocket 非常适合需要服务器主动向客户端推送消息的场景。例如:

  • 聊天系统:如即时通讯工具(微信、Slack)。
  • 通知推送:如订单状态更新、告警通知。
  • 评论系统:实时更新用户评论。

原因:传统的 RESTful API 和 HTTP 轮询需要客户端不断发送请求检查更新,而 WebSocket 可以在事件发生时立即推送给客户端,减少延迟和资源消耗。


(2) 实时协作

多人在线协作工具需要实时同步状态,例如:

  • 协作编辑:Google Docs、在线白板。
  • 游戏状态同步:如多人在线游戏中的位置、动作数据实时同步。

原因:实时协作需要多个客户端之间快速交换状态,WebSocket 的全双工通信可以满足高频数据交换的需求。


(3) 实时数据流

需要持续传输实时数据的场景,例如:

  • 实时行情:股票、加密货币等市场行情的实时数据推送。
  • 实时监控:如设备状态、服务器性能监控。

原因:WebSocket 可以维持长连接,避免频繁建立 HTTP 连接导致的资源浪费和延迟。


(4) IoT设备通信

物联网设备需要与服务端持续通信,例如:

  • 智能家居:设备状态的实时同步与控制。
  • 远程监控:设备上传实时数据,如传感器信息。

原因:WebSocket 提供轻量级、低延迟的通信方式,非常适合带宽有限的设备。


(5) 实时通知与更新

例如:

  • 电商平台:订单状态、配送进度的实时推送。
  • 社交网络:点赞、评论等操作的实时通知。

原因:与轮询相比,WebSocket 能够显著减少延迟和带宽使用。


(6) 实时交互和流媒体

例如:

  • 在线客服:实时聊天和问题解决。
  • 在线直播:弹幕、互动评论实时显示。
  • 在线考试/问答:实时答题系统。

原因:这些场景需要低延迟且稳定的双向通信,HTTP 很难实现类似的体验。


2. 为什么 WebSocket 不适合 RESTful API 的场景

RESTful API 的设计原则是请求-响应模式,适用于以下场景:

  • CRUD 操作:创建、读取、更新、删除数据。
  • 低频请求:如用户信息查询、提交表单等。
  • 无状态交互:每个请求都独立,不需要维护长时间的连接。

WebSocket 的特点使其在这些场景中显得不适用:

  1. 资源浪费:WebSocket 需要维持长连接,对于低频交互的请求,资源利用率低。
  2. 复杂性增加:开发 WebSocket 通信比 RESTful API 复杂得多,尤其是消息格式、连接管理等需要更多的设计。
  3. 状态管理挑战:WebSocket 是有状态的协议,需要额外处理连接的断开、恢复和心跳等问题。

3. 什么时候选择 WebSocket?什么时候选择 REST?

需求适合 WebSocket适合 REST
实时性高度实时性需求无实时性需求
通信频率高频率、持续交互低频请求
消息方向双向通信(客户端和服务器同时发送)单向通信(客户端请求,服务器响应)
资源开销长时间保持连接,减少握手开销每次请求需要独立连接
复杂度需要处理连接状态和消息格式较为简单,基于HTTP

4. WebSocket 与其他技术的比较

WebSocket vs HTTP Polling(轮询)

  • 轮询:客户端定期发起请求检查更新。
    • 优点:简单实现,无需额外协议支持。
    • 缺点:延迟高、开销大。
  • WebSocket:服务器可主动推送消息,无需客户端请求。
    • 优点:低延迟、节约资源。

WebSocket vs HTTP Long Polling(长轮询)

  • 长轮询:客户端发起请求,服务器保持连接直到有新消息。
    • 优点:较普通轮询效率高,兼容性好。
    • 缺点:每次新消息需要重新建立连接。
  • WebSocket:持续保持连接,实时性更高。

WebSocket vs Server-Sent Events(SSE)

  • SSE:服务器向客户端推送数据的单向通信协议。
    • 优点:基于HTTP,浏览器支持好,适合简单的实时推送。
    • 缺点:仅支持单向通信。
  • WebSocket:支持双向通信,功能更强大。

5. WebSocket在Spring Boot微服务中的案例

场景:实时订单状态更新

  1. 客户端下单后,服务器通过 WebSocket 推送订单状态变化。
  2. 服务端通过消息队列或数据库监听订单状态变化,将最新状态推送给客户端。

实现步骤:

  1. 建立 WebSocket 服务,支持订单状态消息推送。
  2. 监听订单状态变化,并通过 WebSocket 广播到相关客户端。

代码实现

WebSocket 配置:

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(new OrderStatusWebSocketHandler(), "/order-status")
                .setAllowedOrigins("*");
    }
}

WebSocket 消息处理:

public class OrderStatusWebSocketHandler extends TextWebSocketHandler {
    private final Set<WebSocketSession> sessions = ConcurrentHashMap.newKeySet();

    @Override
    public void afterConnectionEstablished(WebSocketSession session) {
        sessions.add(session);
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) {
        sessions.remove(session);
    }

    public void sendOrderStatusUpdate(String orderId, String status) throws IOException {
        for (WebSocketSession session : sessions) {
            session.sendMessage(new TextMessage("订单 " + orderId + " 状态更新: " + status));
        }
    }
}

实时状态推送: 当订单状态改变时调用 sendOrderStatusUpdate 方法广播给客户端。


总结

  • WebSocket 的最佳场景
    • 高度实时、高频交互需求。
    • 需要服务器主动向客户端推送数据。
    • 需要双向通信的场景。
  • WebSocket 的劣势
    • 实现和维护的复杂度较高。
    • 对长连接管理有更高要求。

选择 WebSocket 时,应根据实际需求权衡开发复杂度和实时通信需求。

4. 客户端如何与webSocket服务端建立通信?

在 WebSocket 通信中,客户端需要主动建立与服务端的连接,并通过 WebSocket 协议进行通信。以下是完整的客户端实现步骤,以及如何通过 JavaScript 或其他语言的客户端代码订阅服务端的 WebSocket 通信。


1. 服务端 WebSocket 案例回顾

服务端代码已配置 WebSocket 的消息处理逻辑。假设服务端 WebSocket 的访问路径为 /order-status

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(new OrderStatusWebSocketHandler(), "/order-status")
                .setAllowedOrigins("*");
    }
}

2. 客户端如何订阅 WebSocket

客户端需要:

  1. 创建到服务端的 WebSocket 连接。
  2. 处理服务端推送的消息。
  3. 根据需求向服务端发送消息。

(1) 使用 JavaScript 作为客户端

以下是 JavaScript 客户端的完整代码,可以嵌入 HTML 页面中。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>WebSocket Client</title>
</head>
<body>
    <h1>WebSocket 实时订单状态</h1>
    <p id="status">连接状态:未连接</p>
    <ul id="messages"></ul>

    <script>
        // 1. 创建 WebSocket 连接
        const socket = new WebSocket("ws://localhost:8080/order-status");

        const statusEl = document.getElementById("status");
        const messagesEl = document.getElementById("messages");

        // 2. 监听连接打开事件
        socket.addEventListener("open", () => {
            statusEl.textContent = "连接状态:已连接";

            // 发送消息给服务端(可选)
            socket.send(JSON.stringify({ action: "subscribe", userId: "123" }));
        });

        // 3. 监听服务端推送消息事件
        socket.addEventListener("message", (event) => {
            const message = event.data; // 获取服务端推送的消息
            const li = document.createElement("li");
            li.textContent = `服务端消息: ${message}`;
            messagesEl.appendChild(li);
        });

        // 4. 监听连接关闭事件
        socket.addEventListener("close", () => {
            statusEl.textContent = "连接状态:已关闭";
        });

        // 5. 监听错误事件
        socket.addEventListener("error", (error) => {
            console.error("WebSocket 错误:", error);
        });
    </script>
</body>
</html>

(2) 使用 Java 作为客户端

如果你需要用 Java 编写一个 WebSocket 客户端,可以使用 Java 标准库中的 WebSocket API。例如:

import java.net.URI;
import javax.websocket.*;

@ClientEndpoint
public class WebSocketClient {
    private Session session;

    // 处理服务端消息
    @OnMessage
    public void onMessage(String message) {
        System.out.println("服务端消息: " + message);
    }

    // 处理连接建立
    @OnOpen
    public void onOpen(Session session) {
        this.session = session;
        System.out.println("连接成功");
        sendMessage("{\"action\":\"subscribe\",\"userId\":\"123\"}");
    }

    // 处理连接关闭
    @OnClose
    public void onClose() {
        System.out.println("连接关闭");
    }

    // 处理错误
    @OnError
    public void onError(Throwable throwable) {
        System.err.println("连接错误: " + throwable.getMessage());
    }

    // 发送消息
    public void sendMessage(String message) {
        session.getAsyncRemote().sendText(message);
    }

    public static void main(String[] args) throws Exception {
        WebSocketContainer container = ContainerProvider.getWebSocketContainer();
        URI uri = new URI("ws://localhost:8080/order-status");
        container.connectToServer(WebSocketClient.class, uri);
    }
}

3. WebSocket 调试工具

在开发中,你可以使用 WebSocket 调试工具快速测试连接和消息推送。

(1) 使用浏览器控制台调试

  1. 打开浏览器的开发者工具(F12)。
  2. Console 中输入以下代码:
     const socket = new WebSocket("ws://localhost:8080/order-status");
     socket.addEventListener("message", (event) => console.log("收到消息:", event.data));
     socket.addEventListener("open", () => console.log("WebSocket 已连接"));
     socket.addEventListener("close", () => console.log("WebSocket 已关闭"));
    

(2) 使用工具测试

  • Postman
    1. 打开 Postman 的 WebSocket 工具。
    2. 输入 ws://localhost:8080/order-status
    3. 点击连接,观察推送消息。
  • 专用 WebSocket 调试工具:如 websocket.org 提供的在线调试工具。

4. 客户端与服务端交互的注意事项

  1. 消息格式
    • 建议使用 JSON 格式发送和接收消息,方便扩展和解析。
  2. 连接断开重连
    • 客户端需要实现断开后的自动重连机制,确保网络不稳定时能够自动恢复连接。
  3. 鉴权
    • 如果需要安全性,建议通过查询参数或消息体传递认证信息(如 token)。
    • 示例:ws://localhost:8080/order-status?token=your_token_here
  4. 心跳检测
    • 定期发送心跳消息,防止连接被服务端关闭:
      setInterval(() => socket.send(JSON.stringify({ type: "ping" })), 30000);
      

总结

通过上述步骤,客户端可以轻松订阅服务端的 WebSocket 服务。无论是使用 JavaScript 还是 Java 客户端,你都可以根据具体场景灵活实现实时通信功能。

5. 客户端和webSocket服务器建立连接的时效性如何?

是的,WebSocket 是一种基于 TCP 的全双工长连接协议。一旦客户端和服务端之间的 WebSocket 连接成功建立,连接会持续存在,除非显式关闭或者因其他原因断开。


1. WebSocket 是长连接

与传统的 HTTP 不同,WebSocket 是基于 单次握手,长时间保持连接 的设计。WebSocket 的运行机制如下:

  • 连接建立
    • 客户端通过 HTTP 发起一次 WebSocket 握手请求(使用 UpgradeConnection: upgrade 头部)。
    • 服务端响应握手,切换协议为 WebSocket,连接建立。
  • 数据传输
    • 在连接建立后,客户端和服务端可以随时互相发送消息,无需每次都重新建立连接。
    • WebSocket 连接是双向的,既可以由服务端主动向客户端推送数据,也可以由客户端向服务端发送数据。
  • 连接关闭
    • 连接会持续存在,除非:
      1. 客户端或服务端显式调用关闭连接的方法。
      2. 网络中断或其他不可恢复的异常。
      3. 服务端超时关闭空闲连接。

2. WebSocket 长连接的优势

  • 减少延迟
    • 一次握手后,客户端和服务端可以直接通信,无需多次建立和关闭连接(相比 HTTP 的短连接)。
  • 实时性
    • 服务端可以随时主动推送数据到客户端,适合需要实时更新的场景(如消息通知、游戏、股票行情)。
  • 节省资源
    • 因为减少了频繁的连接建立和关闭,节省了资源消耗(特别是在高并发场景下)。

3. 长连接保持的注意事项

长时间保持连接可能会受到一些问题的影响,需要妥善处理:

(1) 网络中断或意外断开

如果客户端和服务端之间的连接断开,客户端需要实现自动重连机制,例如:

JavaScript 自动重连示例

function createWebSocket() {
    const socket = new WebSocket("ws://localhost:8080/order-status");

    // 连接成功事件
    socket.addEventListener("open", () => {
        console.log("WebSocket 已连接");
    });

    // 连接关闭事件
    socket.addEventListener("close", () => {
        console.log("WebSocket 已断开,尝试重新连接...");
        setTimeout(createWebSocket, 3000); // 3 秒后重试
    });

    // 错误处理
    socket.addEventListener("error", (error) => {
        console.error("WebSocket 错误:", error);
        socket.close(); // 遇到错误时关闭连接,触发重连机制
    });

    return socket;
}

const socket = createWebSocket();

(2) 服务端心跳检测

服务端通常会对长时间没有通信的连接进行超时关闭。为避免误判,可以定期发送心跳消息:

JavaScript 心跳消息示例

setInterval(() => {
    if (socket.readyState === WebSocket.OPEN) {
        socket.send(JSON.stringify({ type: "ping" }));
    }
}, 30000); // 每 30 秒发送一次心跳

服务端可以对心跳消息进行检查,超时未收到心跳的连接可以主动关闭。

(3) 并发控制

在高并发场景中,服务端需要限制 WebSocket 连接数,避免资源耗尽。可以通过以下措施控制:

  • 设置每个用户的最大连接数。
  • 定期清理空闲连接。
  • 使用消息队列(如 Kafka、Redis)缓冲数据,避免服务端直接处理过多连接。

(4) 使用协议扩展

在实际应用中,可以在 WebSocket 协议上定义自定义的消息协议,例如:

{
    "type": "message",
    "content": "Hello, world!",
    "timestamp": "2024-11-22T12:34:56Z"
}

客户端和服务端需要解析并处理这种消息格式。


4. 长连接的应用场景

WebSocket 的长连接特性使其特别适合以下场景:

  1. 实时消息通知
    • 聊天应用、协同办公工具等。
  2. 实时数据流
    • 股票行情、运动追踪、物流实时状态更新等。
  3. 实时交互
    • 游戏、在线考试、实时问答系统等。
  4. IoT(物联网)
    • 实时控制和状态反馈。

总结

WebSocket 是一种设计用于实时通信的长连接协议,适合需要高频次、低延迟通信的场景。在客户端与服务端建立 WebSocket 连接后,默认情况下连接会一直保持,除非遇到网络问题或显式关闭。要充分利用 WebSocket 的优势,开发中需要实现自动重连、心跳检测等机制,以确保连接的稳定性和可用性。

6. MQ服务器和消费端应用建立的是webSocket协议吗?

在 Spring Boot 服务与消息队列服务器(如 RabbitMQ、Kafka)建立连接时,应用端与 MQ 服务器之间并不使用 HTTP 或 WebSocket 协议。消息队列的通信协议和机制与这些网络协议不同。以下是具体的说明:


1. RabbitMQ 的连接机制

协议

RabbitMQ 默认使用 AMQP(Advanced Message Queuing Protocol) 协议。AMQP 是一个为消息中间件设计的开放标准协议,支持消息发布、路由、队列、确认等功能。

连接类型

  • TCP 长连接
    • Spring Boot 的 spring-boot-starter-amqp 通过 RabbitMQ 客户端库(如 spring-rabbit)与 RabbitMQ 服务器建立 TCP 长连接
    • 连接建立后,客户端和 RabbitMQ 会通过 TCP 持续通信,直到连接被显式关闭或因网络问题断开。

通信机制

  • 信道(Channel)
    • 在一个 TCP 连接上,可以通过 AMQP 的信道(Channel)实现多路复用。每个信道是一个虚拟连接,用于发送和接收消息。
    • 一个应用通常只创建少量 TCP 连接,但可以在这些连接上打开多个信道。

特点

  • RabbitMQ 的连接与 HTTP 无关,因此不是 HTTP 短连接或长连接。
  • RabbitMQ 不使用 WebSocket。其通信是基于 TCP 和 AMQP 协议的点对点或发布/订阅模式。

2. Kafka 的连接机制

协议

Kafka 使用 Kafka 协议,这是 Kafka 专属的高性能二进制协议,用于高吞吐量的消息传输。

连接类型

  • TCP 长连接
    • Kafka 客户端(生产者或消费者)通过 TCP 与 Kafka Broker 建立长连接。
    • Kafka 客户端通常保持与多个 Kafka Broker 的长连接,确保高可用性和负载均衡。

通信机制

  • 客户端与 Kafka 服务器通过分区(Partition)和主题(Topic)进行消息的生产和消费。
  • Kafka 使用高效的批量传输和压缩机制,提升数据传输性能。

特点

  • Kafka 连接是基于 TCP 的长连接。
  • Kafka 的通信协议与 HTTP 和 WebSocket 无关。

3. Spring Boot 与 MQ 服务器的连接实现

(1) RabbitMQ 消费者监听器

Spring Boot 的 @RabbitListener 用于监听 RabbitMQ 消息队列,背后的机制是:

  • Spring AMQP 框架通过 CachingConnectionFactory 创建一个 TCP 长连接。
  • 框架会管理连接池和信道,确保多个消费者可以复用同一个 TCP 连接。

示例代码:

@Component
public class RabbitMqListener {
    @RabbitListener(queues = "testQueue")
    public void receiveMessage(String message) {
        System.out.println("收到消息: " + message);
    }
}

(2) Kafka 消费者监听器

Spring Boot 的 @KafkaListener 用于监听 Kafka 消息队列,背后的机制是:

  • 使用 Kafka 客户端库通过 TCP 建立长连接,并加入指定的消费者组。
  • 消费者监听器会自动处理分区分配和消息拉取。

示例代码:

@Component
public class KafkaConsumer {
    @KafkaListener(topics = "testTopic", groupId = "testGroup")
    public void consume(String message) {
        System.out.println("收到消息: " + message);
    }
}

4. 对比 MQ 的连接与 HTTP/WebSocket

特性RabbitMQ/KafkaHTTP 短连接HTTP 长连接WebSocket 长连接
协议AMQP(RabbitMQ)、Kafka 协议HTTP/1.1HTTP/1.1WebSocket
底层连接TCP 长连接每次请求单独创建 TCP 连接持续的 TCP 长连接持续的 TCP 长连接
通信模式消息队列(发布/订阅、点对点)请求-响应请求-响应全双工通信
适用场景高吞吐量、异步消息简单请求保持活动连接的 REST 请求实时交互、低延迟通信
连接管理长连接、多信道复用每次请求都要重新建立连接连接建立后可多次复用连接建立后持续通信

5. 为什么使用 TCP 长连接而不是 HTTP/WebSocket?

  1. 性能
    • HTTP 的请求/响应模式增加了额外的头部信息,不适合高吞吐量场景。
    • WebSocket 虽然是长连接,但额外的协议转换(HTTP 升级到 WebSocket)增加了复杂性,而 RabbitMQ/Kafka 的协议更轻量、高效。
  2. 可靠性
    • RabbitMQ 和 Kafka 提供内置的消息确认和重试机制,保证消息的可靠投递。
    • WebSocket 并没有内置的可靠性机制。
  3. 专用性
    • RabbitMQ 和 Kafka 的协议专为消息队列场景设计,支持复杂的路由、分区、分组等功能,HTTP 和 WebSocket 不具备这些特性。

总结

在 Spring Boot 与 RabbitMQ 或 Kafka 通信时,客户端与 MQ 服务器之间使用的是 TCP 长连接,并基于 AMQP 或 Kafka 的专用协议。它们与 HTTP 或 WebSocket 的机制完全不同,设计目标也不同。MQ 的长连接适用于高并发、高吞吐量和实时消息处理的场景。

7. springboot客户端和MQ服务器建立TCP长连接后,保持通信是单工通信还是双工通信?

Spring Boot 客户端和 MQ(如 RabbitMQ 或 Kafka)服务器之间通过 TCP 长连接 保持通信,通信方式具体取决于消息队列的实现。但从通用的角度来看,大部分 MQ 系统通过长连接支持双工通信,即客户端与服务端都可以相互发送消息。


RabbitMQ 的通信机制

1. 通信方式

RabbitMQ 是基于 AMQP 协议 的,这种协议本质上支持 双工通信

  • 客户端 → 服务端:客户端可以向服务端发送消息(例如生产者发送消息到交换机)。
  • 服务端 → 客户端:服务端可以主动推送消息(例如向消费者发送队列中的消息)。

在 TCP 长连接的基础上,RabbitMQ 使用信道(Channel)实现逻辑上的多路复用,从而实现双向通信。

2. 具体实现

  • 生产者(Producer)
    • 通过 TCP 长连接,将消息发送到 RabbitMQ 的交换机(Exchange)。
  • 消费者(Consumer)
    • 通过 TCP 长连接和信道监听队列,RabbitMQ 主动推送消息给消费者。

3. 示例代码

  • 生产者发送消息
    @RestController
    public class RabbitMQProducer {
    
        @Autowired
        private RabbitTemplate rabbitTemplate;
    
        @PostMapping("/send")
        public String sendMessage(@RequestBody String message) {
            rabbitTemplate.convertAndSend("testExchange", "testRoutingKey", message);
            return "Message sent: " + message;
        }
    }
    
  • 消费者监听消息
    @Component
    public class RabbitMQConsumer {
    
        @RabbitListener(queues = "testQueue")
        public void receiveMessage(String message) {
            System.out.println("Received message: " + message);
        }
    }
    

Kafka 的通信机制

1. 通信方式

Kafka 的协议本质上也是 双工的,但与 RabbitMQ 不同的是,Kafka 的消费者采用“拉取模式(pull)”来消费消息。以下是两种模式的区别:

  • 客户端 → 服务端
    • 生产者通过 TCP 长连接向 Kafka Broker 发送消息。
    • 消费者通过 TCP 长连接发送拉取请求。
  • 服务端 → 客户端
    • Kafka Broker 向生产者返回写入确认。
    • Kafka Broker 向消费者返回拉取的消息。

虽然消费者是“主动拉取消息”,但由于连接是持久化的,整个通信链路仍然基于 双工 TCP 长连接

2. 具体实现

  • 生产者发送消息
    @RestController
    public class KafkaProducer {
    
        @Autowired
        private KafkaTemplate<String, String> kafkaTemplate;
    
        @PostMapping("/send")
        public String sendMessage(@RequestBody String message) {
            kafkaTemplate.send("testTopic", message);
            return "Message sent: " + message;
        }
    }
    
  • 消费者监听消息
    @Component
    public class KafkaConsumer {
    
        @KafkaListener(topics = "testTopic", groupId = "testGroup")
        public void consume(String message) {
            System.out.println("Received message: " + message);
        }
    }
    

单工 vs 双工通信

| 特性 | RabbitMQ | Kafka | |——————————–|——————————————–|——————————————-| | 底层协议 | AMQP,支持双工通信 | Kafka 专属协议,支持双工通信 | | 通信方向 | - 生产者向服务端发送消息
- 服务端向消费者推送消息 | - 生产者向服务端发送消息
- 消费者从服务端拉取消息 | | 实现方式 | 完全双工(客户端和服务端均可发送数据) | 半双工式的操作表现,但基于双工 TCP 长连接 | | 连接维持 | 基于 TCP 长连接 | 基于 TCP 长连接 | | 典型场景 | 高实时性(如推送消息) | 高吞吐量(如批量处理日志、分析数据) |


总结

  1. RabbitMQKafka 都通过 TCP 长连接 保持通信,支持 双工通信
  2. 在实际通信中:
    • RabbitMQ 服务端可以主动推送消息到客户端(完全双工)。
    • Kafka 消费者采用“拉取模式”,但仍通过 TCP 长连接实现消息的发送与接收(支持双工)。
  3. 双工通信 使得 MQ 系统能够高效处理消息,同时支持高并发和低延迟场景。

8. java语言整合webSocket

Java 集成 WebSocket 的三种方式(Spring WebSocket、Tomcat WebSocket、Netty WebSocket)各有特点,适用于不同场景。以下是详细的区别、使用场景和对应的案例代码。


1. Spring WebSocket

Spring WebSocket 是基于 Spring 的高级封装,简化了 WebSocket 的开发,提供了强大的注解支持以及与 Spring 的无缝集成。

特点

  • 集成性强:与 Spring Boot 框架无缝集成。
  • 注解驱动:使用简单,提供 @ServerEndpoint 和消息处理机制。
  • 支持 STOMP:可扩展为基于消息代理(如 RabbitMQ)的发布/订阅模型。

使用场景

  • 构建与 Spring 应用深度集成的 WebSocket 服务。
  • 支持复杂的会话管理和消息处理。
  • 适合需要整合 STOMP 协议的场景。

案例:Spring WebSocket 服务

依赖

pom.xml 添加依赖:

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

服务端代码

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

@Component
public class MyWebSocketHandler extends TextWebSocketHandler {

    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        System.out.println("连接建立: " + session.getId());
    }

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        System.out.println("收到消息: " + message.getPayload());
        session.sendMessage(new TextMessage("服务端回复: " + message.getPayload()));
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        System.out.println("连接关闭: " + session.getId());
    }
}

配置

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
public class WebSocketConfig implements WebSocketConfigurer {

    private final MyWebSocketHandler myWebSocketHandler;

    public WebSocketConfig(MyWebSocketHandler myWebSocketHandler) {
        this.myWebSocketHandler = myWebSocketHandler;
    }

    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(myWebSocketHandler, "/websocket").setAllowedOrigins("*");
    }
}

测试

客户端连接 ws://localhost:8080/websocket


2. Tomcat WebSocket

Tomcat WebSocket 是 Java EE 标准的实现,基于 JSR 356 提供原生 WebSocket 支持,依赖于 Servlet 容器(如 Tomcat)。

特点

  • 标准实现:遵循 JSR 356 标准。
  • 简单直接:无需额外的框架,直接通过注解开发。
  • 性能较高:比 Spring WebSocket 更轻量。

使用场景

  • 需要直接使用原生 Java WebSocket API 的场景。
  • 应用依赖于标准的 Servlet 容器(如 Tomcat)。

案例:Tomcat WebSocket 服务

依赖

无需额外依赖,Tomcat 原生支持 WebSocket。

服务端代码

import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;

@ServerEndpoint("/websocket")
public class MyWebSocket {

    @OnOpen
    public void onOpen(Session session) {
        System.out.println("连接建立: " + session.getId());
    }

    @OnMessage
    public void onMessage(String message, Session session) throws Exception {
        System.out.println("收到消息: " + message);
        session.getBasicRemote().sendText("服务端回复: " + message);
    }

    @OnClose
    public void onClose(Session session, CloseReason reason) {
        System.out.println("连接关闭: " + session.getId() + ",原因: " + reason);
    }

    @OnError
    public void onError(Session session, Throwable throwable) {
        System.err.println("发生错误: " + throwable.getMessage());
    }
}

配置

MyWebSocket 注册到 Tomcat:

import org.springframework.boot.web.servlet.ServletContextInitializer;
import org.springframework.context.annotation.Configuration;

import javax.servlet.ServletContext;
import javax.servlet.ServletException;

@Configuration
public class WebSocketConfig implements ServletContextInitializer {

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        servletContext.addServlet("MyWebSocket", MyWebSocket.class);
    }
}

3. Netty WebSocket

Netty 是一个异步事件驱动的网络框架,提供了底层的 WebSocket 实现,适用于高并发和高性能场景。

特点

  • 高性能:适合大规模并发连接场景。
  • 高度可定制:可自定义协议处理和消息分发逻辑。
  • 无需依赖 Servlet 容器:独立运行。

使用场景

  • 构建高性能实时通信服务(如游戏服务器、聊天系统)。
  • 对性能和协议自定义要求较高的场景。

案例:Netty WebSocket 服务

依赖

pom.xml 添加 Netty 依赖:

<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.71.Final</version>
</dependency>

服务端代码

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.codec.http.websocketx.*;
import io.netty.handler.stream.ChunkedWriteHandler;

public class NettyWebSocketServer {

    private final int port;

    public NettyWebSocketServer(int port) {
        this.port = port;
    }

    public void start() throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        try {
            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) {
                            ch.pipeline().addLast(new HttpServerCodec());
                            ch.pipeline().addLast(new HttpObjectAggregator(65536));
                            ch.pipeline().addLast(new ChunkedWriteHandler());
                            ch.pipeline().addLast(new WebSocketServerProtocolHandler("/websocket"));
                            ch.pipeline().addLast(new WebSocketHandler());
                        }
                    });

            ChannelFuture future = bootstrap.bind(port).sync();
            System.out.println("Netty WebSocket 服务器启动: ws://localhost:" + port + "/websocket");
            future.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws Exception {
        new NettyWebSocketServer(8080).start();
    }
}

class WebSocketHandler extends SimpleChannelInboundHandler<WebSocketFrame> {

    @Override
    protected void channelRead0(ChannelHandlerContext ctx, WebSocketFrame frame) {
        if (frame instanceof TextWebSocketFrame) {
            TextWebSocketFrame textFrame = (TextWebSocketFrame) frame;
            ctx.channel().writeAndFlush(new TextWebSocketFrame("服务端回复: " + textFrame.text()));
        } else if (frame instanceof CloseWebSocketFrame) {
            ctx.channel().close();
        }
    }
}

对比总结

特性Spring WebSocketTomcat WebSocketNetty WebSocket
协议支持WebSocket / STOMPWebSocketWebSocket / 自定义协议
性能中等较高
复杂度简单较简单较复杂
适用场景与 Spring 集成的应用标准 Java EE 应用高并发、低延迟场景

选择方式:

  • 使用 Spring WebSocket 时,推荐用于业务逻辑复杂但性能要求中等的场景。
  • 使用 Tomcat WebSocket 时,适合简单的标准实现。
  • 使用 Netty WebSocket 时,适合高性能实时通信场景。

9. @ServerEndPoint注解似乎是java层面的扩展注解,spring webSocket是不是对tomcat webSocket的二次封装?

是的,Spring WebSocket 是对 Tomcat WebSocket 的进一步封装,但不仅限于 Tomcat 的实现。它基于 Java WebSocket 标准(JSR 356),提供了与 Spring 框架无缝集成的能力,同时可以运行在支持 JSR 356 的任何 Servlet 容器(如 Tomcat、Jetty、Undertow)中。以下是它们之间的关系和区别的详细解释。


1. Spring WebSocket 与 Tomcat WebSocket 的关系

  • Tomcat WebSocket
    是 JSR 356(Java WebSocket API)标准的实现,通过 @ServerEndpoint 注解定义 WebSocket 服务端。它是标准的 Java EE API,独立于任何框架。

  • Spring WebSocket

    • 依赖于 Spring 框架,提供更高级的封装。
    • 可以基于 JSR 356 的原生实现,如 Tomcat 的 WebSocket API,也可以运行在其他容器中。
    • 除了支持基本的 WebSocket 功能,还提供 STOMP 协议支持、消息分发、会话管理等功能。

可以理解为:Spring WebSocket 是对 Tomcat WebSocket(或者其他 JSR 356 实现)的增强,特别是在与 Spring 应用整合和复杂场景处理方面。


2. Spring WebSocket 的增强功能

  1. 支持 STOMP 协议
    • STOMP 是一个简单的文本协议,支持订阅/发布模式。Spring WebSocket 可以借助 STOMP 结合消息代理(如 RabbitMQ、ActiveMQ)实现高级功能。
    • Tomcat 原生 WebSocket 不支持 STOMP。
  2. 更简单的开发体验
    • Spring WebSocket 使用 @MessageMapping@Controller 等注解,与 Spring MVC 的风格一致,简化开发。
    • Tomcat WebSocket 需要手动处理会话、消息分发等。
  3. 会话管理
    • Spring WebSocket 提供会话管理工具(如 SimpUserRegistry)来追踪连接用户。
    • Tomcat WebSocket 需要手动管理 Session 对象。
  4. 集成性
    • Spring WebSocket 与 Spring Security、Spring MVC、Spring Messaging 等组件无缝集成。
    • Tomcat WebSocket 不支持这些功能,需要手动扩展。

3. 使用 Tomcat WebSocket 和 Spring WebSocket 的区别

Tomcat WebSocket 示例

import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;

@ServerEndpoint("/websocket")
public class MyTomcatWebSocket {

    @OnOpen
    public void onOpen(Session session) {
        System.out.println("Tomcat WebSocket 连接建立: " + session.getId());
    }

    @OnMessage
    public void onMessage(String message, Session session) {
        System.out.println("收到消息: " + message);
        try {
            session.getBasicRemote().sendText("服务端回复: " + message);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @OnClose
    public void onClose(Session session, CloseReason reason) {
        System.out.println("连接关闭: " + session.getId() + ", 原因: " + reason);
    }

    @OnError
    public void onError(Session session, Throwable error) {
        System.err.println("连接错误: " + error.getMessage());
    }
}

Spring WebSocket 示例

  1. 消息处理方式更灵活: ```java import org.springframework.messaging.handler.annotation.MessageMapping; import org.springframework.messaging.handler.annotation.SendTo; import org.springframework.stereotype.Controller;

@Controller public class MySpringWebSocketController {

@MessageMapping("/message")
@SendTo("/topic/response")
public String handle(String message) {
    System.out.println("Spring WebSocket 收到消息: " + message);
    return "服务端回复: " + message;
} } ```
  1. STOMP 支持配置: ```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 public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
    config.enableSimpleBroker("/topic"); // 内存中的消息代理
    config.setApplicationDestinationPrefixes("/app");
}

@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
    registry.addEndpoint("/websocket").setAllowedOrigins("*").withSockJS();
} } ```

4. 使用场景对比

| 特性 | Tomcat WebSocket | Spring WebSocket | |—————————|—————————————|—————————————–| | 简单实时通信 | ✅ 简单易用,适合直接实现 | ✅ 更高层次抽象 | | 复杂协议(如 STOMP) | ❌ 不支持 | ✅ 内置支持 | | 会话管理 | ❌ 手动管理 | ✅ 提供工具类管理 | | 与 Spring 集成 | ❌ 无法集成,需要手动编写 | ✅ 无缝集成,与 MVC、Security 配合 | | 开发复杂度 | 中等,需要熟悉原生 WebSocket API | 简单,贴近 Spring MVC 开发习惯 | | 性能需求(高并发) | ✅ 性能较高,轻量化实现 | ❌ 性能适中,功能丰富有额外开销 |


5. 选择建议

  • Tomcat WebSocket
    • 适合轻量级应用,直接使用标准 WebSocket API。
    • 比如简单的聊天室、实时通知等。
  • Spring WebSocket
    • 当应用需要深度集成 Spring 时,选择 Spring WebSocket。
    • 适合复杂的 WebSocket 服务,如需要会话管理、消息广播、STOMP 支持的场景。

总结: Spring WebSocket 是对 Tomcat WebSocket 的增强,适合需要与 Spring 框架集成的复杂应用场景。

10. spring webSocket编码实战

如果使用的是 Spring WebSocket,你不需要在 controller 上标注 @ServerEndpoint 注解。@ServerEndpoint 是用于 Java EE 的 WebSocket API,它是用于在服务器端创建 WebSocket 端点的注解,但在 Spring WebSocket 中,我们通常使用的是 Spring 自己的 WebSocket 支持。

在 Spring WebSocket 中,你的 WebSocket 配置通常会使用 @EnableWebSocket@EnableWebSocketMessageBroker 注解来启用 WebSocket 功能。然后,你可以通过 @Controller@MessageMapping 来定义 WebSocket 控制器,而不需要使用 @ServerEndpoint

一个典型的 Spring WebSocket 控制器示例:

@Controller
public class WebSocketController {

    @MessageMapping("/chat")
    @SendTo("/topic/messages")
    public Message sendMessage(Message message) {
        return message;
    }
}

你还需要一个 WebSocket 配置类来配置消息代理和 WebSocket 端点:

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends WebSocketMessageBrokerConfigurer {
    
    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        config.enableSimpleBroker("/topic");
        config.setApplicationDestinationPrefixes("/app");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/ws").withSockJS();
    }
}

在这种方式下,你通过 @MessageMapping 来定义 WebSocket 消息处理方法,而不是使用 @ServerEndpoint

11. spring cloud中的webSocket

在 Spring Cloud 的微服务架构中,如果你的服务提供了一个 WebSocket 接口并且请求需要经过 Gateway 或多个服务链路,确实可以实现 WebSocket 的正常连接和双工通信,但需要满足以下条件和注意事项:


1. Gateway 的 WebSocket 支持

Spring Cloud Gateway 从 2.1 版本开始支持 WebSocket 协议的代理。你需要确保 Gateway 的配置正确地将 WebSocket 请求转发到目标服务。

Gateway 配置 WebSocket 转发示例:

spring:
  cloud:
    gateway:
      routes:
        - id: websocket-route
          uri: ws://your-websocket-service:port
          predicates:
            - Path=/ws/**
  • uri: 使用 ws://wss://(如果是 HTTPS)来指定目标 WebSocket 服务的地址。
  • Path: 指定匹配 WebSocket 请求的路径。

2. 确保所有链路支持 WebSocket

WebSocket 是基于 HTTP/1.1 升级的协议(Connection: UpgradeUpgrade: websocket),因此中间链路(如负载均衡器、反向代理等)必须支持 WebSocket 协议并正确代理。

  • 负载均衡器:确保配置支持 WebSocket。例如,Nginx 需要启用以下配置:
    location /ws/ {
        proxy_pass http://backend-service;
        proxy_http_version 1.1;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Host $host;
    }
    
  • 服务发现:确保 WebSocket 请求在服务发现中能正确路由。

3. 服务内的 WebSocket 配置

在你的服务中,你可以使用 Spring WebSocket 来配置 WebSocket 端点:

配置 WebSocket 服务端点:

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends WebSocketMessageBrokerConfigurer {
    
    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        // 配置 WebSocket 端点
        registry.addEndpoint("/ws").setAllowedOriginPatterns("*").withSockJS();
    }

    @Override
    public void configureMessageBroker(MessageBrokerRegistry config) {
        // 配置消息代理
        config.enableSimpleBroker("/topic", "/queue");
        config.setApplicationDestinationPrefixes("/app");
    }
}
  • addEndpoint("/ws"):定义了客户端连接的 WebSocket 端点。
  • setAllowedOriginPatterns("*"):设置跨域支持(在生产中建议限制到可信来源)。
  • withSockJS():可选,用于支持 SockJS 回退。

4. 跨域问题

如果你的客户端和服务在不同的域名或端口上运行,你需要配置跨域支持。Spring WebSocket 提供了相关配置:

@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
    registry.addEndpoint("/ws")
            .setAllowedOriginPatterns("*")  // 设置允许的来源
            .withSockJS();                  // 支持 SockJS
}

在生产环境中,不建议直接使用 "*",而是明确设置允许的来源域名。


5. 心跳与断线重连

WebSocket 是长连接协议,可能受到网络中断的影响。建议在客户端和服务端实现心跳机制,以检测连接状态并支持重连。

在 Spring WebSocket 中,STOMP 协议自动支持心跳,默认配置如下:

@Override
public void configureMessageBroker(MessageBrokerRegistry registry) {
    registry.enableSimpleBroker("/topic")
            .setHeartbeatValue(new long[]{10000, 10000});  // 10 秒心跳
}

6. 注意事项

  • 连接超时问题:如果链路中有超时限制(如 Gateway 或负载均衡器设置的连接超时),可能会导致 WebSocket 连接被意外断开。需要调整超时时间。
  • 限流与负载均衡:如果你的系统中有流控规则或负载均衡机制,确保这些机制对 WebSocket 请求适用。
  • 安全性:如果使用 HTTPS,确保整个链路支持 wss:// 并正确配置 SSL。

总结

通过正确的配置和链路支持,你的服务可以成功地提供 WebSocket 接口并实现双工通信。关键在于确保 Gateway 和所有中间组件正确处理 WebSocket 协议,特别是 Connection: UpgradeUpgrade: websocket 的转发。

文档信息

Search

    Table of Contents