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

SpringBoot項目中使用WebSocket實現(xiàn)實時通信功能

 更新時間:2026年03月25日 09:31:53   作者:彳余日青  
在傳統(tǒng)的 HTTP 通信中,客戶端發(fā)起請求,服務(wù)器給出響應(yīng),一次通信就此結(jié)束,這種模式對于靜態(tài)頁面展示完全夠用,但對于需要實時推送的場景,就力不從心了,因此本文介紹了在SpringBoot項目中使用WebSocket實現(xiàn)實時通信功能,需要的朋友可以參考下

一、前言

在傳統(tǒng)的 HTTP 通信中,客戶端發(fā)起請求,服務(wù)器給出響應(yīng),一次通信就此結(jié)束。這種模式對于靜態(tài)頁面展示完全夠用,但對于需要實時推送的場景——如在線聊天、實時通知、股票行情、設(shè)備狀態(tài)監(jiān)控——就力不從心了。

早期的解決方案是輪詢:客戶端每隔幾秒就發(fā)一次請求問服務(wù)器"有新消息嗎",效率低下且浪費資源。WebSocket 的出現(xiàn)徹底改變了這一局面。

二、WebSocket 是什么

WebSocket 是一種在單個 TCP 連接上進行全雙工通信的網(wǎng)絡(luò)協(xié)議,由 HTML5 規(guī)范引入,RFC 6455 正式定義。

2.1 與 HTTP 的核心區(qū)別

對比項HTTPWebSocket
通信方向單向(客戶端請求,服務(wù)器響應(yīng))雙向(任意一方均可主動發(fā)送)
連接狀態(tài)無狀態(tài),一問一答后斷開有狀態(tài),連接建立后持續(xù)保持
協(xié)議頭開銷每次請求都攜帶完整 Header握手一次后,后續(xù)幀頭極?。?~10字節(jié))
適用場景普通頁面請求實時推送、聊天、游戲

2.2 握手過程

WebSocket 復(fù)用了 HTTP 的握手機制,通過一次 HTTP 請求完成協(xié)議升級:

客戶端發(fā)送升級請求:
GET /ws/chat HTTP/1.1
Host: localhost:8080
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Sec-WebSocket-Version: 13
服務(wù)器返回:
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=

握手成功后,協(xié)議從 HTTP 升級為 WebSocket,后續(xù)通信不再經(jīng)過 HTTP 層,直接在 TCP 連接上傳輸 WebSocket 幀。

三、Spring Boot 集成 WebSocket

Spring Boot 提供了兩種方式使用 WebSocket:

  • 方式一:基于 Java EE 標(biāo)準的 @ServerEndpoint(本文重點介紹)
  • 方式二:基于 Spring 的 WebSocketHandler

3.1 添加依賴

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

3.2 注冊配置類

使用內(nèi)置 Tomcat 時,需要手動注冊 ServerEndpointExporter,讓 Spring 能掃描到 @ServerEndpoint 注解:

@Configuration
public class WebSocketConfig {
    /**
     * 使用內(nèi)置 Tomcat 時必須注入此 Bean
     * 若使用外部 Tomcat 部署,則不需要,容器會自行管理
     */
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

3.3 編寫 WebSocket 端點

@Component
@ServerEndpoint("/ws/chat/{userId}")
public class ChatWebSocket {
    private static final Logger log = LoggerFactory.getLogger(ChatWebSocket.class);
    // 存儲所有在線連接,key=userId,value=WebSocket會話
    private static final ConcurrentHashMap<String, Session> SESSIONS = new ConcurrentHashMap<>();
    /**
     * 連接建立時觸發(fā)
     */
    @OnOpen
    public void onOpen(Session session, @PathParam("userId") String userId) {
        SESSIONS.put(userId, session);
        log.info("用戶 {} 上線,當(dāng)前在線人數(shù):{}", userId, SESSIONS.size());
    }
    /**
     * 收到客戶端消息時觸發(fā)
     */
    @OnMessage
    public void onMessage(String message, @PathParam("userId") String userId) {
        log.info("收到用戶 {} 的消息:{}", userId, message);
        // 解析消息,轉(zhuǎn)發(fā)給目標(biāo)用戶
        handleMessage(userId, message);
    }
    /**
     * 連接關(guān)閉時觸發(fā)
     */
    @OnClose
    public void onClose(@PathParam("userId") String userId) {
        SESSIONS.remove(userId);
        log.info("用戶 {} 下線,當(dāng)前在線人數(shù):{}", userId, SESSIONS.size());
    }
    /**
     * 發(fā)生異常時觸發(fā)
     */
    @OnError
    public void onError(Session session, Throwable error) {
        log.error("WebSocket 發(fā)生異常:{}", error.getMessage());
    }
    /**
     * 向指定用戶發(fā)送消息
     */
    public static void sendToUser(String userId, String message) {
        Session session = SESSIONS.get(userId);
        if (session != null && session.isOpen()) {
            try {
                session.getBasicRemote().sendText(message);
            } catch (IOException e) {
                log.error("向用戶 {} 發(fā)送消息失敗:{}", userId, e.getMessage());
            }
        }
    }
    /**
     * 廣播消息給所有在線用戶
     */
    public static void broadcast(String message) {
        SESSIONS.forEach((userId, session) -> {
            if (session.isOpen()) {
                try {
                    session.getBasicRemote().sendText(message);
                } catch (IOException e) {
                    log.error("廣播消息失敗,userId={}:{}", userId, e.getMessage());
                }
            }
        });
    }
    /**
     * 處理消息邏輯(示例:解析JSON轉(zhuǎn)發(fā))
     */
    private void handleMessage(String fromUserId, String message) {
        try {
            // 假設(shè)消息格式為 JSON:{"toUserId":"xxx","content":"hello"}
            JSONObject json = JSONObject.parseObject(message);
            String toUserId = json.getString("toUserId");
            String content = json.getString("content");
            JSONObject response = new JSONObject();
            response.put("fromUserId", fromUserId);
            response.put("content", content);
            response.put("time", System.currentTimeMillis());
            sendToUser(toUserId, response.toJSONString());
        } catch (Exception e) {
            log.error("消息處理失敗:{}", e.getMessage());
        }
    }
}

3.4 在業(yè)務(wù)代碼中主動推送

WebSocket 不只能被動接收消息,也可以在任意業(yè)務(wù)邏輯中主動向客戶端推送:

@Service
public class OrderService {
    public void completeOrder(Long orderId, Long userId) {
        // 處理訂單完成邏輯...
        // 訂單完成后,主動推送通知給用戶
        JSONObject notify = new JSONObject();
        notify.put("type", "ORDER_COMPLETE");
        notify.put("orderId", orderId);
        notify.put("message", "您的訂單已完成,請及時查看");
        ChatWebSocket.sendToUser(String.valueOf(userId), notify.toJSONString());
    }
}

四、前端對接

<!DOCTYPE html>
<html>
<body>
<script>
    const userId = "user_001";
    const ws = new WebSocket(`ws://localhost:8080/ws/chat/${userId}`);
    // 連接建立
    ws.onopen = function () {
        console.log("WebSocket 連接成功");
        ws.send(JSON.stringify({
            toUserId: "user_002",
            content: "你好!"
        }));
    };
    // 收到消息
    ws.onmessage = function (event) {
        const data = JSON.parse(event.data);
        console.log(`收到來自 ${data.fromUserId} 的消息:${data.content}`);
    };
    // 連接關(guān)閉
    ws.onclose = function () {
        console.log("WebSocket 連接已關(guān)閉");
    };
    // 發(fā)生錯誤
    ws.onerror = function (error) {
        console.error("WebSocket 錯誤:", error);
    };
</script>
</body>
</html>

五、生產(chǎn)環(huán)境注意事項

5.1 @ServerEndpoint 無法注入 Spring Bean 的問題

@ServerEndpoint 的實例由 Tomcat 管理,不是 Spring Bean,所以直接用 @Autowired 注入會失?。?/p>

@ServerEndpoint("/ws/chat/{userId}")
public class ChatWebSocket {
    @Autowired
    private UserService userService;  // ? 注入失敗,值為 null
}

解決方案:通過靜態(tài)變量 + ApplicationContext 獲取

@ServerEndpoint("/ws/chat/{userId}")
public class ChatWebSocket implements ApplicationContextAware {
    private static UserService userService;
    @Override
    public void setApplicationContext(ApplicationContext context) {
        userService = context.getBean(UserService.class);
    }
}

或者更簡潔地,在配置類里提前拿到:

@Component
public class WebSocketBeanFactory implements ApplicationContextAware {
    private static ApplicationContext context;
    @Override
    public void setApplicationContext(ApplicationContext applicationContext) {
        context = applicationContext;
    }
    public static <T> T getBean(Class<T> clazz) {
        return context.getBean(clazz);
    }
}
// 在 WebSocket 端點里使用
UserService userService = WebSocketBeanFactory.getBean(UserService.class);

5.2 集群部署下的消息廣播問題

單機部署時,所有連接都在同一個 JVM 里,ConcurrentHashMap 存儲會話沒問題。但集群部署時,用戶 A 連接在節(jié)點1,用戶 B 連接在節(jié)點2,節(jié)點1無法直接找到用戶 B 的 Session。

解決方案:引入消息中間件(如 RabbitMQ / Redis Pub-Sub)

節(jié)點1收到消息
    ↓
發(fā)布到 MQ / Redis Channel
    ↓
所有節(jié)點訂閱并消費消息
    ↓
各節(jié)點檢查自己管理的 Session 里有沒有目標(biāo)用戶
    ↓
有則發(fā)送,無則忽略

5.3 連接心跳?;?/h3>

長時間無數(shù)據(jù)交換時,防火墻或負載均衡器可能會斷開連接,需要定期發(fā)送心跳:

// 前端每 30 秒發(fā)送一次心跳
setInterval(() => {
    if (ws.readyState === WebSocket.OPEN) {
        ws.send(JSON.stringify({ type: "PING" }));
    }
}, 30000);
// 后端收到心跳回復(fù)
@OnMessage
public void onMessage(String message, Session session) {
    JSONObject json = JSONObject.parseObject(message);
    if ("PING".equals(json.getString("type"))) {
        try {
            session.getBasicRemote().sendText("{\"type\":\"PONG\"}");
        } catch (IOException e) {
            log.error("心跳回復(fù)失敗");
        }
    }
}

5.4 連接斷開重連

網(wǎng)絡(luò)抖動時前端需要自動重連:

function createWebSocket(userId) {
    const ws = new WebSocket(`ws://localhost:8080/ws/chat/${userId}`);
    ws.onclose = function () {
        console.log("連接斷開,3秒后重連...");
        setTimeout(() => createWebSocket(userId), 3000);
    };
    return ws;
}

六、完整流程總結(jié)

1. 引入 spring-boot-starter-websocket 依賴
         ↓
2. 注冊 ServerEndpointExporter Bean(內(nèi)置Tomcat必須)
         ↓
3. 編寫 @ServerEndpoint 端點類
   實現(xiàn) @OnOpen / @OnMessage / @OnClose / @OnError
         ↓
4. 用 ConcurrentHashMap 管理所有在線 Session
         ↓
5. 業(yè)務(wù)代碼調(diào)用靜態(tài)方法主動推送消息
         ↓
6. 前端用 new WebSocket(url) 建立連接
   通過 onmessage 接收,ws.send() 發(fā)送

七、適用場景總結(jié)

場景說明
在線聊天用戶之間實時發(fā)送消息
實時通知訂單完成、充值到賬、審批結(jié)果
設(shè)備監(jiān)控硬件設(shè)備實時上報狀態(tài)數(shù)據(jù)
協(xié)同編輯多人同時編輯同一文檔
行情推送股票、加密貨幣實時價格
在線游戲多人游戲?qū)崟r同步狀態(tài)

WebSocket 適合高頻、雙向、實時的通信場景。如果只是服務(wù)器單向推送(如消息通知),也可以考慮更輕量的 SSE(Server-Sent Events);如果實時性要求不高,簡單的短輪詢也能滿足需求。選擇合適的技術(shù),比盲目使用 WebSocket 更重要。

以上就是SpringBoot項目中使用WebSocket實現(xiàn)實時通信功能的詳細內(nèi)容,更多關(guān)于SpringBoot WebSocket實時通信的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • SpringBoot返回統(tǒng)一的JSON標(biāo)準格式實現(xiàn)步驟

    SpringBoot返回統(tǒng)一的JSON標(biāo)準格式實現(xiàn)步驟

    這篇文章主要介紹了SpringBoot返回統(tǒng)一的JSON標(biāo)準格式,本文通過實例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2021-08-08
  • Springboot的spring-boot-maven-plugin導(dǎo)入失敗的解決方案

    Springboot的spring-boot-maven-plugin導(dǎo)入失敗的解決方案

    這篇文章主要介紹了Springboot的spring-boot-maven-plugin導(dǎo)入失敗的解決方案,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • Java多線程中的wait與notify方法詳解

    Java多線程中的wait與notify方法詳解

    這篇文章主要介紹了Java多線程中的wait與notify方法詳解,線程的調(diào)度是無序的,但有些情況要求線程的執(zhí)行是有序的,因此,我們可以使用 wait() 方法來使線程執(zhí)行有序,需要的朋友可以參考下
    2023-08-08
  • 一文帶你了解RabbitMQ消息轉(zhuǎn)換器

    一文帶你了解RabbitMQ消息轉(zhuǎn)換器

    這篇文章主要為大家詳細介紹了RabbitMQ中消息轉(zhuǎn)換器的相關(guān)知識,文中的示例代碼講解詳細,具有一定的借鑒價值,感興趣的小伙伴可以了解一下
    2023-04-04
  • Mybatis如何配置連接池

    Mybatis如何配置連接池

    本文通過實例代碼給大家詳細介紹了mybatis配置連接池的方法,非常不錯,具有參考借鑒價值,感興趣的朋友參考下吧
    2016-12-12
  • Java獲得一個數(shù)組的指定長度排列組合算法示例

    Java獲得一個數(shù)組的指定長度排列組合算法示例

    這篇文章主要介紹了Java獲得一個數(shù)組的指定長度排列組合算法,結(jié)合實例形式分析了java排列組合相關(guān)數(shù)組遍歷、運算操作技巧,需要的朋友可以參考下
    2019-06-06
  • SpringMVC核心原理與前后端數(shù)據(jù)交互機制詳解(推薦)

    SpringMVC核心原理與前后端數(shù)據(jù)交互機制詳解(推薦)

    文章詳細介紹了SpringMVC的核心原理及前后端數(shù)據(jù)交互機制,包括SpringMVC的概述、架構(gòu)、工作流程、請求參數(shù)綁定、JSON數(shù)據(jù)處理、數(shù)據(jù)驗證、異常處理以及最佳實踐,感興趣的朋友跟隨小編一起看看吧
    2025-11-11
  • Java中控制多線程順序執(zhí)行的六種實現(xiàn)方案

    Java中控制多線程順序執(zhí)行的六種實現(xiàn)方案

    在多線程編程中,線程的執(zhí)行順序本質(zhì)上是不確定的,由操作系統(tǒng)調(diào)度器決定,但在某些業(yè)務(wù)場景中,我們需要確保線程按照特定順序執(zhí)行,所以本文介紹了Java中控制多線程順序執(zhí)行的六種實現(xiàn)方案,需要的朋友可以參考下
    2025-09-09
  • 一起來學(xué)習(xí)Java IO的轉(zhuǎn)化流

    一起來學(xué)習(xí)Java IO的轉(zhuǎn)化流

    這篇文章主要為大家詳細介紹了Java IO的轉(zhuǎn)化流,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下,希望能夠給你帶來幫助
    2022-03-03
  • 從@CrossOrigin到Gateway詳解Spring Boot跨域處理的10種姿勢

    從@CrossOrigin到Gateway詳解Spring Boot跨域處理的10種姿勢

    在前后端分離架構(gòu)成為主流的今天,跨域問題已成為每個Web開發(fā)者必須面對的挑戰(zhàn),本文將系統(tǒng)性地介紹10種Spring Boot跨域處理方案,下面小編就和大家簡單介紹一下吧
    2025-11-11

最新評論

西城区| 湖北省| 上杭县| 陈巴尔虎旗| 正安县| 肇东市| 高要市| 清水河县| 贵阳市| 辛集市| 肇源县| 平乡县| 邵阳市| 娱乐| 汉中市| 吴川市| 丰宁| 皋兰县| 浏阳市| 安溪县| 安平县| 汉寿县| 林西县| 抚宁县| 繁昌县| 正蓝旗| 偃师市| 昌平区| 鄂尔多斯市| 高雄市| 河西区| 武山县| 铜山县| 普洱| 肇庆市| 富阳市| 来安县| 剑河县| 宝丰县| 东阳市| 综艺|