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

WebSocket(java版)服務(wù)核心實(shí)例代碼

 更新時(shí)間:2025年12月21日 10:57:53   作者:深海藍(lán)山的博客  
WebSocket是一種協(xié)議,用于在Web應(yīng)用程序和服務(wù)器之間建立實(shí)時(shí)、雙向的通信連接,這篇文章主要介紹了WebSocket(java版)服務(wù)核心代碼的相關(guān)資料,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下

說明:

這是一個(gè)使用 Java JDK 8 和 Spring Boot 實(shí)現(xiàn)的WebSocket演示項(xiàng)目。目的是為解決多端消息通訊的問題。

WebSocket 是一種基于 TCP 的全雙工通信協(xié)議,核心作用是解決傳統(tǒng) HTTP 協(xié)議 “請(qǐng)求 - 響應(yīng)” 模式的局限性,實(shí)現(xiàn) 客戶端與服務(wù)器之間的實(shí)時(shí)、雙向、低延遲數(shù)據(jù)傳輸。

源碼地址:https://gitee.com/lqh4188/web-socket

一、功能介紹

功能特性:

  • 基于 Maven 的 Spring Boot 項(xiàng)目骨架。
  • 純 WebSocket 端點(diǎn) /ws ,支持用戶隔離,http:使用ws,https:使用wss。
  • 支持分片設(shè)置和緩沖區(qū)大小設(shè)置,解決傳輸內(nèi)容限制
  • 提供靜態(tài)測(cè)試頁(yè)面 index.html ,用于連接、發(fā)送消息、查看消息。

項(xiàng)目結(jié)構(gòu):

  • pom.xml :Spring Boot 3.3,依賴 spring-boot-starter-web 和 spring-boot-starter-websocket 。
  • src/main/java/com/example/websocket/WebSocketApplication.java :應(yīng)用入口。
  • src/main/java/com/example/websocket/WebSocketConfig.java :注冊(cè) WebSocket 處理器,端點(diǎn)為 /ws 。
  • src/main/java/com/example/websocket/ChatWebSocketHandler.java :文本消息處理,廣播到所有會(huì)話。
  • src/main/resources/static/index.html :頁(yè)面內(nèi)置 JS,連接 ws://{host}/ws ,可發(fā)送、顯示消息。

關(guān)鍵代碼位置

  • 啟動(dòng)類: src/main/java/com/example/websocket/WebSocketApplication.java:1
  • WebSocket 配置: src/main/java/com/example/websocket/WebSocketConfig.java:1
  • 文本消息處理器: src/main/java/com/example/websocket/ChatWebSocketHandler.java:1
  • 靜態(tài)頁(yè)面: src/main/resources/static/index.html:1

測(cè)試連接

  • 打開 http://localhost:8800 ,使用頁(yè)面上的“連接/發(fā)送”測(cè)試
  • WebSocket 地址: ws://localhost:8080/ws

二、運(yùn)行測(cè)試

可通過UserId來創(chuàng)建獨(dú)立的聯(lián)接,進(jìn)行用戶隔離

三、核心代碼說明

由于websocket對(duì)傳輸?shù)膬?nèi)容有限制,若內(nèi)容較大可進(jìn)行緩沖區(qū)大小設(shè)置,并對(duì)不同文本進(jìn)行分片處理

ChatWebSocketHandler.java代碼:

package com.example.websocket;
import java.io.ByteArrayOutputStream;
import java.net.URI;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;

import org.springframework.web.socket.BinaryMessage;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.AbstractWebSocketHandler;
import com.fasterxml.jackson.databind.ObjectMapper;

public class ChatWebSocketHandler extends AbstractWebSocketHandler {
    private final ConcurrentHashMap<String, Set<WebSocketSession>> userSessions = new ConcurrentHashMap<>();
    private static final ObjectMapper MAPPER = new ObjectMapper();
    private final ConcurrentHashMap<String, StringBuilder> textFragments = new ConcurrentHashMap<>();
    private final ConcurrentHashMap<String, ByteArrayOutputStream> binaryFragments = new ConcurrentHashMap<>();

    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        // 驗(yàn)證用戶ID的有效性
        String uid = resolveUserId(session);
        if (uid == null || uid.isEmpty()) {
            session.close(CloseStatus.BAD_DATA);
            return;
        }
        session.getAttributes().put("userId", uid);
        //多會(huì)話管理
        userSessions.computeIfAbsent(uid, k -> ConcurrentHashMap.newKeySet()).add(session);
    }

    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        // 分片處理
        String id = session.getId();
        if (!message.isLast()) {
            textFragments.computeIfAbsent(id, k -> new StringBuilder()).append(message.getPayload());
            return;
        }
        StringBuilder sb = textFragments.remove(id);
        String payload = sb != null ? sb.append(message.getPayload()).toString() : message.getPayload();
        routePayload(session, payload);
    }

    @Override
    protected void handleBinaryMessage(WebSocketSession session, BinaryMessage message) throws Exception {
        //二進(jìn)制消息處理
        String id = session.getId();
        ByteBuffer buf = message.getPayload();
        byte[] chunk = new byte[buf.remaining()];
        buf.get(chunk);
        ByteArrayOutputStream acc = binaryFragments.computeIfAbsent(id, k -> new ByteArrayOutputStream());
        acc.write(chunk);
        if (message.isLast()) {
            byte[] all = acc.toByteArray();
            binaryFragments.remove(id);
            String payload = new String(all, StandardCharsets.UTF_8);
            routePayload(session, payload);
        }
    }

    @Override
    public boolean supportsPartialMessages() {
        return true;
    }

    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        //  WebSocket 連接關(guān)閉時(shí)的清理邏輯
        Object v = session.getAttributes().get("userId");
        if (v == null) return;
        String uid = String.valueOf(v);
        Set<WebSocketSession> set = userSessions.get(uid);
        if (set != null) {
            set.remove(session);
            if (set.isEmpty()) userSessions.remove(uid);
        }
    }

    /** 從 WebSocket 連接的 URL 查詢參數(shù)中提取用戶ID */
    private String resolveUserId(WebSocketSession session) {
        URI uri = session.getUri();
        if (uri == null) return null;
        String q = uri.getQuery();
        if (q == null || q.isEmpty()) return null;
        String[] parts = q.split("&");
        for (String p : parts) {
            int i = p.indexOf('=');
            if (i > 0) {
                String k = p.substring(0, i);
                String val = p.substring(i + 1);
                if ("userId".equals(k)) return val;
            }
        }
        return null;
    }

    private void routePayload(WebSocketSession session, String payload) throws Exception {
        Object v = session.getAttributes().get("userId");
        if (v == null) return;
        String fromUid = String.valueOf(v);

        // 解析消息
        Message message = new Message();
        message.setFromUserId(fromUid);
        
        try {
            // 嘗試將payload解析為Message對(duì)象
            Message receivedMsg = MAPPER.readValue(payload, Message.class);
            message.setToUserId(receivedMsg.getToUserId());
            message.setContent(receivedMsg.getContent());
            message.setType(receivedMsg.getType());
        } catch (Exception e) {
            // 如果解析失敗,將整個(gè)payload作為content
            message.setContent(payload);
        }

        String toUid = message.getToUserId();
        boolean isP2P = toUid != null && !toUid.isEmpty();
        
        Set<WebSocketSession> targets;
        if (isP2P) {
            targets = userSessions.get(toUid);
        } else {
            targets = userSessions.get(fromUid);
        }
        
        // 序列化消息對(duì)象
        String outStr = MAPPER.writeValueAsString(message);
        TextMessage msg = new TextMessage(outStr);
        
        if (targets == null || targets.isEmpty()) {
            if (session.isOpen()) {
                session.sendMessage(msg);
            }
            return;
        }

        for (WebSocketSession s : targets) {
            if (s.isOpen()) {
                s.sendMessage(msg);
            }
        }
        if (isP2P && session.isOpen()) {
            session.sendMessage(msg);
        }
    }
}

配置類WebSocketConfig.java

package com.example.websocket;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import org.springframework.web.socket.server.standard.ServletServerContainerFactoryBean;

@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
    @Override
    public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
        registry.addHandler(chatHandler(), "/ws").setAllowedOriginPatterns("*");
    }

    @Bean
    public WebSocketHandler chatHandler() {
        return new ChatWebSocketHandler();
    }

    // 配置 WebSocket 容器參數(shù)(解決消息過大、超時(shí)等問題)
    @Bean
    public ServletServerContainerFactoryBean createWebSocketContainer() {
        ServletServerContainerFactoryBean container = new ServletServerContainerFactoryBean();
        // 文本消息緩沖區(qū):2MB(解決解碼后消息過大的核心配置)
        container.setMaxTextMessageBufferSize(2 * 1024 * 1024);
        // 二進(jìn)制消息緩沖區(qū):4MB(按需配置)
        container.setMaxBinaryMessageBufferSize(4 * 1024 * 1024);
        // 會(huì)話空閑超時(shí):60秒(無交互則關(guān)閉連接)
        container.setMaxSessionIdleTimeout(60_000L);
        return container;
    }
}

總結(jié) 

到此這篇關(guān)于WebSocket(java版)服務(wù)核心代碼的文章就介紹到這了,更多相關(guān)java WebSocket服務(wù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 詳解SpringBoot如何開啟異步編程

    詳解SpringBoot如何開啟異步編程

    本文主要介紹了詳解SpringBoot如何開啟異步編程,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2023-04-04
  • Java實(shí)現(xiàn)鼠標(biāo)模擬與鍵盤映射

    Java實(shí)現(xiàn)鼠標(biāo)模擬與鍵盤映射

    這篇文章主要為大家詳細(xì)介紹了Java實(shí)現(xiàn)鼠標(biāo)模擬與鍵盤映射,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-08-08
  • 一文教你Java如何快速構(gòu)建項(xiàng)目骨架

    一文教你Java如何快速構(gòu)建項(xiàng)目骨架

    在?Java?項(xiàng)目開發(fā)過程中,構(gòu)建項(xiàng)目骨架是一項(xiàng)繁瑣但又基礎(chǔ)重要的工作,Java?領(lǐng)域有許多代碼生成工具可以幫助我們快速完成這一任務(wù),下面就跟隨小編一起來了解下吧
    2025-05-05
  • SpringBoot啟動(dòng)原理深入解析

    SpringBoot啟動(dòng)原理深入解析

    我們開發(fā)任何一個(gè)Spring Boot項(xiàng)目都會(huì)用到啟動(dòng)類,下面這篇文章主要給大家介紹了關(guān)于SpringBoot啟動(dòng)原理解析的相關(guān)資料,文中通過圖文以及實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2023-04-04
  • JAVA異常分類和處理解析

    JAVA異常分類和處理解析

    這篇文章主要介紹了JAVA異常分類和處理解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2019-08-08
  • 詳解Springboot2.3集成Spring security 框架(原生集成)

    詳解Springboot2.3集成Spring security 框架(原生集成)

    這篇文章主要介紹了詳解Springboot2.3集成Spring security 框架(原生集成),文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-08-08
  • 詳談Java幾種線程池類型介紹及使用方法

    詳談Java幾種線程池類型介紹及使用方法

    下面小編就為大家?guī)硪黄斦凧ava幾種線程池類型介紹及使用方法。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2017-03-03
  • SpringMVC如何訪問WEB-INF jsp過程解析

    SpringMVC如何訪問WEB-INF jsp過程解析

    這篇文章主要介紹了SpringMVC如何訪問WEB-INF jsp過程解析,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-09-09
  • Elasticsearch?Recovery索引分片分配詳解

    Elasticsearch?Recovery索引分片分配詳解

    這篇文章主要為大家介紹了關(guān)于Elasticsearch的Recovery索引分片分配詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪<BR>
    2022-04-04
  • Java中難理解的四個(gè)概念

    Java中難理解的四個(gè)概念

    這篇文章主要介紹了匿名內(nèi)部類、多線程、如何實(shí)現(xiàn)同步、序列化,這四個(gè)難理解的概念,同學(xué)們一定要仔細(xì)看看
    2021-04-04

最新評(píng)論

玉林市| 铁岭县| 淮阳县| 呈贡县| 普安县| 缙云县| 九龙城区| 舒城县| 三河市| 八宿县| 上高县| 青浦区| 冕宁县| 阿城市| 三门峡市| 扎赉特旗| 龙游县| 华安县| 浦县| 桐庐县| 诏安县| 鲁山县| 南涧| 逊克县| 丽水市| 灵山县| 河南省| 阿拉尔市| 安庆市| 石楼县| 锡林郭勒盟| 富蕴县| 南充市| 长岭县| 金门县| 南皮县| 沈丘县| 平湖市| 祁连县| 宁远县| 寿宁县|