最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringBoot集成WebSocket實(shí)現(xiàn)雙屏實(shí)時(shí)消息互推功能

 更新時(shí)間:2026年02月12日 09:32:11   作者:qq_29757467  
在項(xiàng)目開(kāi)發(fā)中,實(shí)時(shí)消息推送是高頻需求,比如雙屏聯(lián)動(dòng)、大屏監(jiān)控、在線聊天、訂單狀態(tài)推送等場(chǎng)景,本文以SpringBoot 2.7.x(最穩(wěn)定版本,零基礎(chǔ)友好)為基礎(chǔ),手把手教大家從 0 到 1 集成 WebSocket,實(shí)現(xiàn)左屏 / 右屏雙端實(shí)時(shí)消息互推功能,需要的朋友可以參考下

前言

在項(xiàng)目開(kāi)發(fā)中,實(shí)時(shí)消息推送是高頻需求,比如雙屏聯(lián)動(dòng)、大屏監(jiān)控、在線聊天、訂單狀態(tài)推送等場(chǎng)景。WebSocket 作為 HTML5 的核心特性,實(shí)現(xiàn)了瀏覽器與服務(wù)器的全雙工雙向通信,相比傳統(tǒng)的輪詢 / 長(zhǎng)輪詢方式,大幅降低服務(wù)端壓力,提升實(shí)時(shí)性和用戶體驗(yàn)。

本文以SpringBoot 2.7.x(最穩(wěn)定版本,零基礎(chǔ)友好)為基礎(chǔ),手把手教大家從 0 到 1 集成 WebSocket,實(shí)現(xiàn)左屏 / 右屏雙端實(shí)時(shí)消息互推功能。全程代碼可直接復(fù)制使用,兼顧Jar 包內(nèi)嵌 Tomcat和War 包外部 Tomcat兩種部署方式,解決部署沖突問(wèn)題,同時(shí)完善異常處理、連接管理、心跳檢測(cè)等生產(chǎn)級(jí)細(xì)節(jié),小白跟著步驟走就能跑通。

本文核心優(yōu)勢(shì)

  1. 零基礎(chǔ)友好:代碼全復(fù)制、步驟全拆解,無(wú)復(fù)雜配置,新手直接用;
  2. 部署無(wú)坑:自動(dòng)適配 Jar/War 包部署,無(wú)需手動(dòng)修改代碼,避免容器沖突;
  3. 生產(chǎn)級(jí)健壯:完善的異常處理、失效連接清理、心跳檢測(cè),防止內(nèi)存泄漏;
  4. 支持多端登錄:同一用戶多設(shè)備連接,所有端都能收到消息,避免 Session 覆蓋;
  5. 雙測(cè)試方式:在線工具快速驗(yàn)證 + 自定義 HTML 頁(yè)面,前端后端全打通;
  6. 配套全補(bǔ)全:統(tǒng)一響應(yīng)類、啟動(dòng)類改造等缺失代碼全部補(bǔ)全,無(wú)需額外找依賴。

一、環(huán)境準(zhǔn)備(新手必看)

1.1 基礎(chǔ)開(kāi)發(fā)環(huán)境

無(wú)需高版本,基礎(chǔ)環(huán)境即可運(yùn)行,推薦搭配:

  • JDK:1.8(兼容性最好,無(wú)版本問(wèn)題)
  • SpringBoot:2.7.10(本文統(tǒng)一版本,避免依賴沖突)
  • Maven:3.6.0+
  • 開(kāi)發(fā)工具:IDEA/Eclipse(推薦 IDEA,自帶 Maven 管理)
  • 測(cè)試工具:瀏覽器、WebSocket 在線測(cè)試工具

1.2 核心依賴

在pom.xml中引入 SpringBoot 官方的 WebSocket Starter 依賴,無(wú)需額外引入其他包,Spring 已做封裝:

<!-- SpringBoot集成WebSocket核心依賴 -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
<!-- 可選:SpringMVC基礎(chǔ)依賴(項(xiàng)目已引入可忽略) -->
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
</dependency>

二、核心配置類(解決 Jar/War 部署兼容)

SpringBoot 中使用@ServerEndpoint注解實(shí)現(xiàn) WebSocket 時(shí),必須注冊(cè)ServerEndpointExporter 讓 Spring 掃描并管理 WebSocket 端點(diǎn),但內(nèi)嵌 Tomcat(Jar 包)和外部 Tomcat(War 包) 對(duì)該 Bean 的要求不同:
Jar 包部署(內(nèi)嵌 Tomcat):需要手動(dòng)創(chuàng)建ServerEndpointExporter Bean;
War 包部署(外部 Tomcat):由容器自身初始化 WebSocket,手動(dòng)創(chuàng)建會(huì)導(dǎo)致 Bean 沖突。
因此我們通過(guò)Spring 條件注解@Conditional 實(shí)現(xiàn)動(dòng)態(tài)判斷,自動(dòng)適配兩種部署方式。

  • Jar 包部署(內(nèi)嵌 Tomcat):需要手動(dòng)創(chuàng)建ServerEndpointExporter Bean;
  • War 包部署(外部 Tomcat):由容器自身初始化 WebSocket,手動(dòng)創(chuàng)建會(huì)導(dǎo)致 Bean 沖突。

2.1 自定義條件判斷類

創(chuàng)建包c(diǎn)om.tydt.framework.config,編寫WebSocketAutoWired類,實(shí)現(xiàn)Condition接口,核心邏輯是判斷是否為內(nèi)嵌 Tomcat 環(huán)境:

/**
 * All rights reserved.
 */
package com.itl.framework.config;

import org.springframework.context.annotation.Condition;
import org.springframework.context.annotation.ConditionContext;
import org.springframework.core.type.AnnotatedTypeMetadata;
import org.springframework.util.ClassUtils;

/**
 * 類描述:WebSocket條件判斷類,控制ServerEndpointExporter是否創(chuàng)建
 * jar包部署(內(nèi)嵌Tomcat)返回true,war包部署(外部Tomcat)返回false
 * @author itl
 * @version 1.0
 * 
 * 修訂歷史:
 * 日期			修訂者		修訂描述
 * 2026-02-05	xxx		修復(fù)matches方法固定返回false問(wèn)題,實(shí)現(xiàn)jar/war包部署動(dòng)態(tài)判斷
 */
public class WebSocketAutoWired implements Condition {

    /**
     * 核心判斷方法:jar包部署(內(nèi)嵌Tomcat)為true; war包部署(外部Tomcat)為false
     */
    @Override
    public boolean matches(ConditionContext context, AnnotatedTypeMetadata metadata) {
        // 判斷類加載器中是否存在內(nèi)嵌Tomcat核心類 → 存在=jar包部署,不存在=war包部署
        return ClassUtils.isPresent(
                "org.apache.catalina.startup.Tomcat",
                context.getClassLoader()
        );
    }
}

2.2 WebSocket 核心配置類

編寫WebSocketConfig類,通過(guò)@Conditional關(guān)聯(lián)上面的條件判斷類,動(dòng)態(tài)創(chuàng)建ServerEndpointExporter:

/**
 *
 * All rights reserved.
 */
package com.itl.framework.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Conditional;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

/**
 * 類描述:WebSocket核心配置類
 * 動(dòng)態(tài)創(chuàng)建ServerEndpointExporter,解決內(nèi)嵌Tomcat/外部Tomcat部署兼容問(wèn)題
 * @author itl
 * @version 1.0
 * 新增條件注解,適配內(nèi)嵌/外部Tomcat
 */
@Configuration
public class WebSocketConfig {

    /**
     * 注冊(cè)WebSocket端點(diǎn)處理器,僅內(nèi)嵌Tomcat(jar包)時(shí)創(chuàng)建
     * 外部Tomcat(war包)由容器自身初始化,無(wú)需手動(dòng)創(chuàng)建
     */
    @Bean
    @Conditional(WebSocketAutoWired.class)
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

核心原理:項(xiàng)目啟動(dòng)時(shí),Spring 會(huì)根據(jù)WebSocketAutoWired的matches方法返回值,動(dòng)態(tài)決定是否創(chuàng)建ServerEndpointExporter Bean,從根本上解決 Jar/War 部署的沖突問(wèn)題。

三、WebSocket 工具類(連接管理 + 消息發(fā)送)

創(chuàng)建工具類WebSocketUtils,用于統(tǒng)一管理客戶端 Session 連接、發(fā)送消息、移除連接等操作,使用ConcurrentHashMap保證多線程下的線程安全,同時(shí)支持同一用戶多端連接(避免 Session 被覆蓋)。
包路徑:com.itl.common.utils

/**
 * All rights reserved.
 */
package com.itl.common.utils;

import java.util.Map;
import java.util.Set;
import java.util.Iterator;
import java.util.concurrent.ConcurrentHashMap;
import javax.websocket.Session;

/**
 * 類描述:WebSocket工具類,管理客戶端Session和消息發(fā)送
 * @author itl
 * 
 * 修訂歷史:
 * 日期			修訂者		修訂描述
 * 優(yōu)化Session管理,支持單用戶多連接;增加異常處理和Session有效性判斷
 */
public class WebSocketUtils {

    // 存儲(chǔ)客戶端連接:key=用戶ID,value=該用戶的所有Session連接(支持多端登錄)
    public static Map<String, Set<Session>> clients = new ConcurrentHashMap<>();

    /**
     * 添加客戶端連接
     * @param userId 用戶唯一標(biāo)識(shí)
     * @param session 客戶端會(huì)話
     */
    public static void add(String userId, Session session) {
        // 不存在則創(chuàng)建新的Set,存在則直接添加;ConcurrentHashMap.newKeySet()保證線程安全
        clients.computeIfAbsent(userId, k -> ConcurrentHashMap.newKeySet()).add(session);
    }

    /**
     * 處理客戶端發(fā)送的消息(可根據(jù)業(yè)務(wù)自定義)
     * @param userId 發(fā)送消息的用戶ID
     * @param message 消息內(nèi)容
     */
    public static void receive(String userId, String message) {
        // 示例:雙屏聯(lián)動(dòng),左屏消息推右屏,右屏消息推左屏
        if ("left".equals(userId)) {
            sendMessage("right", "左屏推送:" + message);
        } else if ("right".equals(userId)) {
            sendMessage("left", "右屏推送:" + message);
        }
        System.out.println("收到用戶[" + userId + "]的消息:" + message);
    }

    /**
     * 精準(zhǔn)移除某用戶的某一個(gè)Session連接(連接關(guān)閉/異常時(shí)調(diào)用)
     * @param userId 用戶唯一標(biāo)識(shí)
     * @param session 要移除的會(huì)話
     */
    public static void remove(String userId, Session session) {
        Set<Session> sessions = clients.get(userId);
        if (sessions != null) {
            sessions.remove(session);
            // 若該用戶無(wú)任何連接,移除key,避免空集合占用內(nèi)存
            if (sessions.isEmpty()) {
                clients.remove(userId);
            }
        }
    }

    /**
     * 移除某用戶的所有連接
     * @param userId 用戶唯一標(biāo)識(shí)
     */
    public static void remove(String userId) {
        clients.remove(userId);
    }

    /**
     * 向指定用戶發(fā)送消息
     * @param userId 接收消息的用戶ID
     * @param message 消息內(nèi)容
     * @return 成功發(fā)送的連接數(shù)
     */
    public static int sendMessage(String userId, String message) {
        Set<Session> sessions = clients.get(userId);
        // 無(wú)該用戶連接,直接返回0
        if (sessions == null || sessions.isEmpty()) {
            return 0;
        }
        int successCount = 0;
        Iterator<Session> it = sessions.iterator();
        while (it.hasNext()) {
            Session session = it.next();
            // 判斷Session是否有效(連接未關(guān)閉)
            if (!session.isOpen()) {
                it.remove(); // 移除失效Session,避免內(nèi)存泄漏
                continue;
            }
            try {
                // 異步發(fā)送消息(推薦),同步發(fā)送使用session.getBasicRemote().sendText(message)
                session.getAsyncRemote().sendText(message);
                successCount++;
            } catch (Exception e) {
                it.remove(); // 發(fā)送失敗,移除失效Session
                e.printStackTrace(); // 實(shí)際項(xiàng)目建議使用日志框架(如Logback/Log4j2)
            }
        }
        // 清理空集合
        if (sessions.isEmpty()) {
            clients.remove(userId);
        }
        return successCount;
    }
}

關(guān)鍵優(yōu)化點(diǎn):

  1. 把原有的Map<String, Session>改為Map<String, Set>,支持同一用戶多端登錄,所有連接都能收到消息;
  2. 增加Session有效性判斷(session.isOpen()),避免向失效連接發(fā)送消息;
  3. 完善的異常捕獲,發(fā)送消息失敗時(shí)自動(dòng)移除失效 Session,防止內(nèi)存泄漏;
  4. 提供精準(zhǔn)移除(單 Session)和批量移除(全 Session)兩種方法,適配不同場(chǎng)景。

四、WebSocket 服務(wù)端端點(diǎn)(核心業(yè)務(wù)處理)

創(chuàng)建WebSocketService類,使用@ServerEndpoint注解定義 WebSocket 服務(wù)端地址,通過(guò)@OnOpen、@OnMessage、@OnClose、@OnError注解處理 WebSocket 的連接打開(kāi)、接收消息、連接關(guān)閉、連接異常四大事件,同時(shí)通過(guò)@Component注解讓 Spring 管理該 Bean。
包路徑:com.itl.framework.web.service

/**
 * All rights reserved.
 */
package com.itl.framework.web.service;

import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import org.springframework.stereotype.Component;
import com.itl.common.utils.WebSocketUtils;

/**
 * 類描述:WebSocket服務(wù)端端點(diǎn),處理客戶端連接和事件回調(diào)
 * 服務(wù)端地址:/connect/{userId}
 * @author itl
 * 修復(fù)onError方法參數(shù)注解問(wèn)題;優(yōu)化連接管理,精準(zhǔn)移除Session
 */
@ServerEndpoint("/connect/{userId}") // WebSocket連接地址,{userId}為用戶唯一標(biāo)識(shí)
@Component // 必須交給Spring管理,否則無(wú)法掃描
public class WebSocketService {

    /**
     * 連接打開(kāi)事件(客戶端首次連接時(shí)調(diào)用)
     * @param userId 路徑參數(shù)中的用戶ID
     * @param session 客戶端會(huì)話
     */
    @OnOpen
    public void onOpen(@PathParam("userId") String userId, Session session) {
        System.out.println("【W(wǎng)ebSocket】連接打開(kāi)成功!");
        WebSocketUtils.add(userId, session);
        System.out.println("【W(wǎng)ebSocket】用戶" + userId + "上線,當(dāng)前在線人數(shù):" + WebSocketUtils.clients.size());
    }

    /**
     * 接收客戶端消息事件
     * @param userId 發(fā)送消息的用戶ID
     * @param message 客戶端發(fā)送的消息
     * @return 服務(wù)端向客戶端的回執(zhí)消息
     */
    @OnMessage
    public String onMessage(@PathParam("userId") String userId, String message) {
        // 心跳檢測(cè)(可選),客戶端發(fā)送&時(shí),服務(wù)端回執(zhí)&,避免連接被斷開(kāi)
        if (message.equals("&")) {
            return "&";
        } else {
            // 調(diào)用工具類處理消息
            WebSocketUtils.receive(userId, message);
            return "【服務(wù)端回執(zhí)】已收到消息:" + message;
        }
    }

    /**
     * 連接異常事件(網(wǎng)絡(luò)中斷、客戶端崩潰等)
     * 注意:@OnError注解不支持@PathParam參數(shù),會(huì)導(dǎo)致參數(shù)解析異常
     * @param session 異常的客戶端會(huì)話
     * @param throwable 異常信息
     */
    @OnError
    public void onError(Session session, Throwable throwable) {
        // 遍歷移除該失效的Session
        WebSocketUtils.clients.forEach((userId, sessions) -> {
            WebSocketUtils.remove(userId, session);
        });
        throwable.printStackTrace();
        System.out.println("【W(wǎng)ebSocket】連接異常,已移除失效會(huì)話");
    }

    /**
     * 連接關(guān)閉事件(客戶端主動(dòng)關(guān)閉連接)
     * @param userId 斷開(kāi)連接的用戶ID
     * @param session 關(guān)閉的客戶端會(huì)話
     */
    @OnClose
    public void onClose(@PathParam("userId") String userId, Session session) {
        System.out.println("【W(wǎng)ebSocket】連接關(guān)閉成功!");
        WebSocketUtils.remove(userId, session);
        System.out.println("【W(wǎng)ebSocket】用戶" + userId + "下線,當(dāng)前在線人數(shù):" + WebSocketUtils.clients.size());
    }
}

核心注意點(diǎn):

  1. @ServerEndpoint(“/connect/{userId}”):定義 WebSocket 的服務(wù)端連接地址,前端通過(guò)ws://ip:port/connect/left連接左屏,ws://ip:port/connect/right連接右屏;
  2. @Component:必須添加,否則 Spring 無(wú)法掃描到該端點(diǎn),配合配置類的ServerEndpointExporter完成注冊(cè);
  3. @OnError方法不支持@PathParam注解:原代碼中該注解會(huì)導(dǎo)致運(yùn)行時(shí)參數(shù)解析異常,直接通過(guò) Session 遍歷移除即可;
  4. 增加心跳檢測(cè):客戶端定時(shí)發(fā)送&,服務(wù)端回執(zhí)&,避免因長(zhǎng)時(shí)間無(wú)交互導(dǎo)致連接被防火墻 / 服務(wù)器斷開(kāi)。

五、測(cè)試接口(HTTP 觸發(fā) WebSocket 消息推送)

創(chuàng)建 Controller,提供 HTTP 接口,用于通過(guò)后端接口觸發(fā) WebSocket 消息推送(比如業(yè)務(wù)系統(tǒng)調(diào)用接口向前端推送消息),實(shí)現(xiàn)左屏 / 右屏雙端消息互推,同時(shí)使用AjaxResult返回統(tǒng)一的響應(yīng)結(jié)果(SpringBoot 項(xiàng)目通用)。

import com.itl.common.utils.WebSocketUtils;
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiImplicitParam;
import io.swagger.annotations.ApiOperation;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

/**
 * WebSocket測(cè)試控制器,雙屏消息互推接口
 * @author itl
 * @date 2026-02-05
 */
@RestController
@RequestMapping("/websocket")
@Api(tags = "WebSocket測(cè)試接口")
public class WebSocketController {

    /**
     * 接收左屏消息并推送至右屏
     * @param message 消息內(nèi)容
     * @return 推送結(jié)果(1=成功,0=失?。?
     */
    @ApiOperation(value = "左屏推右屏", notes = "HTTP接口觸發(fā),向右屏推送消息")
    @ApiImplicitParam(name = "message", value = "推送的消息內(nèi)容", required = true, dataType = "String")
    @GetMapping(value = "/right")
    public AjaxResult right(String message) {
        // toAjax:通用工具類,1=成功,0=失敗
        return toAjax(WebSocketUtils.sendMessage("right", message));
    }

    /**
     * 接收右屏消息并推送至左屏
     * @param message 消息內(nèi)容
     * @return 推送結(jié)果(1=成功,0=失?。?
     */
    @ApiOperation(value = "右屏推左屏", notes = "HTTP接口觸發(fā),向左屏推送消息")
    @ApiImplicitParam(name = "message", value = "推送的消息內(nèi)容", required = true, dataType = "String")
    @GetMapping(value = "/left")
    public AjaxResult left(String message) {
        return toAjax(WebSocketUtils.sendMessage("left", message));
    }

    /**
     * 通用響應(yīng)結(jié)果封裝(項(xiàng)目已實(shí)現(xiàn)可忽略)
     * @param rows 成功數(shù)
     * @return AjaxResult
     */
    private AjaxResult toAjax(int rows) {
        return rows > 0 ? AjaxResult.success() : AjaxResult.error();
    }
}

接口說(shuō)明:

  1. 左屏推右屏:GET http://ip:port/websocket/right?message=測(cè)試消息
  2. 右屏推左屏:GET http://ip:port/websocket/left?message=測(cè)試消息
  3. 響應(yīng)結(jié)果:成功返回{“code”:200,“msg”:“操作成功”,“data”:null},失敗返回{“code”:500,“msg”:“操作失敗”,“data”:null}。

六、前端測(cè)試(兩種方式)

6.1 在線 WebSocket 測(cè)試工具(快速驗(yàn)證)

推薦使用在線工具:WebSocket 在線測(cè)試,無(wú)需編寫前端代碼,直接測(cè)試連接和消息推送。在線測(cè)試網(wǎng)站 https://wstool.js.org/
測(cè)試步驟:

  1. 打開(kāi)兩個(gè)瀏覽器窗口,分別訪問(wèn)在線測(cè)試工具;
  2. 第一個(gè)窗口連接地址填ws://localhost:8080/connect/left,點(diǎn)擊連接,提示 “連接成功”;
  3. 第二個(gè)窗口連接地址填ws://localhost:8080/connect/right,點(diǎn)擊連接,提示 “連接成功”;
  4. 左屏窗口發(fā)送消息Hello 右屏,右屏窗口會(huì)收到左屏推送:Hello 右屏;
  5. 右屏窗口發(fā)送消息Hello 左屏,左屏窗口會(huì)收到右屏推送:Hello 左屏;
  6. 調(diào)用 HTTP 接口http://localhost:8080/websocket/right?message=接口推右屏,右屏窗口會(huì)收到該消息。

6.2 自定義 HTML 測(cè)試頁(yè)面(項(xiàng)目使用)

編寫簡(jiǎn)單的 HTML 頁(yè)面,通過(guò)原生 WebSocket API 實(shí)現(xiàn)連接和消息收發(fā),可直接放入項(xiàng)目的resources/static目錄下:

<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <title>WebSocket雙屏測(cè)試</title>
</head>
<body>
    <h3>WebSocket雙屏聯(lián)動(dòng)測(cè)試(<span id="screenType">左屏</span>)</h3>
    <input type="text" id="msgInput" placeholder="請(qǐng)輸入消息內(nèi)容">
    <button onclick="sendMsg()">發(fā)送消息</button>
    <div id="msgList" style="margin-top: 20px; width: 500px; height: 300px; border: 1px solid #ccc; padding: 10px; overflow-y: auto;"></div>

    <script>
        // 定義用戶ID,left=左屏,right=右屏
        const userId = "left";
        document.getElementById("screenType").innerText = userId === "left" ? "左屏" : "右屏";
        // WebSocket連接地址,替換為自己的服務(wù)端地址
        const ws = new WebSocket("ws://localhost:8080/connect/" + userId);

        // 連接成功回調(diào)
        ws.onopen = function() {
            addMsg("【系統(tǒng)提示】WebSocket連接成功!");
        };

        // 接收消息回調(diào)
        ws.onmessage = function(event) {
            addMsg("【收到消息】" + event.data);
        };

        // 連接關(guān)閉回調(diào)
        ws.onclose = function() {
            addMsg("【系統(tǒng)提示】WebSocket連接關(guān)閉!");
        };

        // 連接異常回調(diào)
        ws.onerror = function() {
            addMsg("【系統(tǒng)提示】WebSocket連接異常!");
        };

        // 發(fā)送消息
        function sendMsg() {
            const msg = document.getElementById("msgInput").value;
            if (!msg) {
                alert("請(qǐng)輸入消息內(nèi)容!");
                return;
            }
            ws.send(msg);
            addMsg("【發(fā)送消息】" + msg);
            document.getElementById("msgInput").value = "";
        }

        // 追加消息到頁(yè)面
        function addMsg(content) {
            const msgList = document.getElementById("msgList");
            const div = document.createElement("div");
            div.style.margin = "5px 0";
            div.innerText = new Date().toLocaleString() + " - " + content;
            msgList.appendChild(div);
            // 滾動(dòng)到底部
            msgList.scrollTop = msgList.scrollHeight;
        }

        // 心跳檢測(cè),每30秒發(fā)送一次&,防止連接斷開(kāi)
        setInterval(() => {
            ws.send("&");
        }, 30000);
    </script>
</body>
</html>

使用說(shuō)明:

復(fù)制兩份頁(yè)面,分別修改userId為left和right,命名為left.html和right.html;
啟動(dòng)項(xiàng)目后,訪問(wèn)http://localhost:8080/left.html和http://localhost:8080/right.html;
兩個(gè)頁(yè)面可互相發(fā)送消息,同時(shí)支持后端接口推送。

七、部署方式說(shuō)明

本文的配置已完美適配Jar 包內(nèi)嵌 Tomcat和War 包外部 Tomcat兩種部署方式,無(wú)需修改任何代碼。

7.1 Jar 包部署(推薦,SpringBoot 默認(rèn))

  1. pom.xml中打包方式為jar:
<packaging>jar</packaging >
  1. 執(zhí)行 Maven 命令打包:mvn clean package -DskipTests;
  2. 運(yùn)行 Jar 包:java -jar xxx.jar;
  3. 核心原理:內(nèi)嵌 Tomcat 環(huán)境,WebSocketAutoWired返回true,創(chuàng)建ServerEndpointExporter,WebSocket 正常注冊(cè)。

7.2 War 包部署(外部 Tomcat)

  1. pom.xml中修改打包方式為war,并排除內(nèi)嵌 Tomcat:
<packaging>war</packaging>

<dependencies>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-web</artifactId>
        <!-- 排除內(nèi)嵌Tomcat -->
        <exclusions>
            <exclusion>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-tomcat</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
    <!-- 引入servlet-api依賴 -->
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>javax.servlet-api</artifactId>
        <version>3.1.0</version>
        <scope>provided</scope>
    </dependency>
</dependencies>
  1. 修改啟動(dòng)類,繼承SpringBootServletInitializer,重寫configure方法:
@SpringBootApplication
public class Application extends SpringBootServletInitializer {
    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
        return application.sources(Application.class);
    }

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
  1. 執(zhí)行 Maven 命令打包:mvn clean package -DskipTests;
  2. 將 war 包放入外部 Tomcat 的webapps目錄,啟動(dòng) Tomcat 即可;
  3. 核心原理:外部 Tomcat 環(huán)境,WebSocketAutoWired返回false,不創(chuàng)建ServerEndpointExporter,由 Tomcat 容器自身初始化 WebSocket,避免沖突。

八、常見(jiàn)問(wèn)題及解決方案

8.1 客戶端連接報(bào) 404 錯(cuò)誤

原因:未創(chuàng)建ServerEndpointExporter Bean,Spring 未掃描到@ServerEndpoint注解;
解決方案:檢查配置類WebSocketConfig和條件判斷類WebSocketAutoWired是否正確,Jar 包部署時(shí)確保matches方法返回true。

8.2 War 包部署到外部 Tomcat 啟動(dòng)報(bào) Bean 沖突

原因:外部 Tomcat 環(huán)境下創(chuàng)建了ServerEndpointExporter Bean,與容器自身的 WebSocket 初始化沖突;
解決方案:確保條件判斷類WebSocketAutoWired在 War 包部署時(shí)返回false,不創(chuàng)建該 Bean。

8.3 發(fā)送消息時(shí)報(bào) IO 異常

原因:向失效的 Session(連接已關(guān)閉 / 網(wǎng)絡(luò)中斷)發(fā)送消息,或未做異常捕獲;
解決方案:在sendMessage方法中增加session.isOpen()判斷,同時(shí)捕獲異常并移除失效 Session(本文工具類已實(shí)現(xiàn))。

8.4 同一用戶多端登錄,只有最后一個(gè)連接能收到消息

原因:原代碼使用Map<String, Session>存儲(chǔ)連接,新連接會(huì)覆蓋舊連接;
解決方案:改為Map<String, Set>存儲(chǔ),同一用戶的所有連接都加入 Set(本文工具類已實(shí)現(xiàn))。

8.5 長(zhǎng)時(shí)間無(wú)交互,連接被斷開(kāi)

原因:防火墻 / 服務(wù)器會(huì)斷開(kāi)長(zhǎng)時(shí)間無(wú)數(shù)據(jù)交互的 TCP 連接;
解決方案:實(shí)現(xiàn)心跳檢測(cè),客戶端定時(shí)發(fā)送心跳包(如&),服務(wù)端回執(zhí),保持連接活躍(本文代碼已實(shí)現(xiàn))。

九、總結(jié)

本文詳細(xì)講解了 SpringBoot 集成 WebSocket 的全流程,從核心依賴引入→配置類編寫(解決 Jar/War 兼容)→工具類封裝(連接管理 + 消息發(fā)送)→服務(wù)端端點(diǎn)實(shí)現(xiàn)(事件處理)→測(cè)試接口開(kāi)發(fā)→前端測(cè)試,一步一步實(shí)現(xiàn)了雙屏實(shí)時(shí)消息互推的功能,同時(shí)解決了項(xiàng)目開(kāi)發(fā)和部署中的常見(jiàn)問(wèn)題。
本文的代碼具有以下特點(diǎn):

  • 高可用性:完善的異常處理、Session 有效性判斷、失效連接清理,避免內(nèi)存泄漏;
  • 高擴(kuò)展性:工具類和服務(wù)端端點(diǎn)解耦,可根據(jù)業(yè)務(wù)需求自定義消息處理邏輯;
  • 高兼容性:支持 Jar 包和 War 包兩種部署方式,無(wú)需手動(dòng)修改代碼;
  • 線程安全:使用 ConcurrentHashMap 和 ConcurrentHashSet 保證多線程下的連接管理安全。

WebSocket 的應(yīng)用場(chǎng)景非常廣泛,除了雙屏聯(lián)動(dòng),還可以用于在線聊天、實(shí)時(shí)監(jiān)控、訂單推送、彈幕等場(chǎng)景,只需在本文代碼的基礎(chǔ)上,根據(jù)業(yè)務(wù)需求修改WebSocketUtils的receive方法和消息發(fā)送邏輯即可。

以上就是SpringBoot集成WebSocket實(shí)現(xiàn)雙屏實(shí)時(shí)消息互推功能的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot WebSocket雙屏消息互推的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Java多態(tài)成員訪問(wèn)的特點(diǎn)是什么?

    Java多態(tài)成員訪問(wèn)的特點(diǎn)是什么?

    在上一篇文章中介紹了方法重載和方法重寫的區(qū)別,但是在多態(tài)情況下發(fā)現(xiàn)程序的執(zhí)行結(jié)果和我們預(yù)期的不太一樣,這篇將繼續(xù)介紹多態(tài)場(chǎng)景下,Java成員訪問(wèn)的特點(diǎn),需要的朋友可以參考下
    2021-06-06
  • Spring 注解編程模型相關(guān)知識(shí)詳解

    Spring 注解編程模型相關(guān)知識(shí)詳解

    這篇文章主要介紹了Spring 注解編程模型相關(guān)知識(shí)詳解,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-09-09
  • Maven提示jdk版本不正確的問(wèn)題

    Maven提示jdk版本不正確的問(wèn)題

    這篇文章主要介紹了Maven提示jdk版本不正確的問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2023-04-04
  • Ribbon負(fù)載均衡服務(wù)調(diào)用的示例詳解

    Ribbon負(fù)載均衡服務(wù)調(diào)用的示例詳解

    Rbbo其實(shí)就是一個(gè)軟負(fù)載均衡的客戶端組件,他可以和其他所需請(qǐng)求的客戶端結(jié)合使用,這篇文章主要介紹了Ribbon負(fù)載均衡服務(wù)調(diào)用案例代碼,需要的朋友可以參考下
    2023-01-01
  • Java基于享元模式實(shí)現(xiàn)五子棋游戲功能實(shí)例詳解

    Java基于享元模式實(shí)現(xiàn)五子棋游戲功能實(shí)例詳解

    這篇文章主要介紹了Java基于享元模式實(shí)現(xiàn)五子棋游戲功能,較為詳細(xì)的分析了享元模式的概念、功能并結(jié)合實(shí)例形式詳細(xì)分析了Java使用享元模式實(shí)現(xiàn)五子棋游戲的具體操作步驟與相關(guān)注意事項(xiàng),需要的朋友可以參考下
    2018-05-05
  • Java實(shí)現(xiàn)PDF轉(zhuǎn)為線性PDF詳解

    Java實(shí)現(xiàn)PDF轉(zhuǎn)為線性PDF詳解

    線性化PDF文件是PDF文件的一種特殊格式,可以通過(guò)Internet更快地進(jìn)行查看。本文將通過(guò)后端Java程序?qū)崿F(xiàn)將PDF文件轉(zhuǎn)為線性化PDF。感興趣的可以了解一下
    2021-12-12
  • mybatis?plus?MetaObjectHandler?不生效的解決

    mybatis?plus?MetaObjectHandler?不生效的解決

    今天使用mybatis-plus自動(dòng)為更新和插入操作插入更新時(shí)間和插入時(shí)間,配置了MetaObjectHandler不生效,本文就來(lái)解決一下,具有一定的 參考價(jià)值,感興趣的可以了解一下
    2023-10-10
  • 深入淺出SpringBoot WebSocket構(gòu)建實(shí)時(shí)應(yīng)用全面指南

    深入淺出SpringBoot WebSocket構(gòu)建實(shí)時(shí)應(yīng)用全面指南

    WebSocket 是一種在單個(gè) TCP 連接上進(jìn)行全雙工通信的協(xié)議,這篇文章主要為大家詳細(xì)介紹了SpringBoot如何集成WebSocket構(gòu)建實(shí)時(shí)應(yīng)用,感興趣的小伙伴可以了解下
    2025-07-07
  • 基于Springboot+Mybatis對(duì)數(shù)據(jù)訪問(wèn)層進(jìn)行單元測(cè)試的方式分享

    基于Springboot+Mybatis對(duì)數(shù)據(jù)訪問(wèn)層進(jìn)行單元測(cè)試的方式分享

    本文將介紹一種快高效、可復(fù)用的解決測(cè)試方案——對(duì)數(shù)據(jù)訪問(wèn)層做單元測(cè)試,文章通過(guò)代碼示例介紹的非常詳細(xì),具有一定的參考價(jià)值,需要的朋友可以參考下
    2023-07-07
  • Mapper報(bào)錯(cuò)@MapKey is required問(wèn)題及解決方式

    Mapper報(bào)錯(cuò)@MapKey is required問(wèn)題及解決方式

    這段描述主要討論了在使用MyBatis或類似框架時(shí)遇到@MapKeyisrequired錯(cuò)誤的常見(jiàn)原因及解決方法,包括MapKey注解的正確使用以及相關(guān)配置檢查,特別強(qiáng)調(diào)了正確配置isMapMapKey和ResultMap注解對(duì)于避免數(shù)據(jù)不一致和運(yùn)行時(shí)異常的重要性
    2026-06-06

最新評(píng)論

荣成市| 乌什县| 井陉县| 孝义市| 浦城县| 龙游县| 潍坊市| 信丰县| 静乐县| 察雅县| 浪卡子县| 丰县| 长海县| 泸西县| 沈丘县| 安国市| 莒南县| 营山县| 齐齐哈尔市| 阳江市| 延庆县| 濮阳县| 隆尧县| 大丰市| 建德市| 辉县市| 汨罗市| 玉屏| 藁城市| 渭南市| 衡水市| 洪泽县| 南漳县| 上林县| 安顺市| 正镶白旗| 原阳县| 平江县| 奉贤区| 淮南市| 略阳县|