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

SpringBoot實現(xiàn)網(wǎng)頁消息推送的5種方法小結(jié)

 更新時間:2025年03月27日 11:02:10   作者:風(fēng)象南  
項目開發(fā)中,實時消息推送已成為提升用戶體驗的關(guān)鍵技術(shù),本文將詳細(xì)介紹SpringBoot中實現(xiàn)網(wǎng)頁消息推送的幾種主流方案,希望對大家有所幫助

項目開發(fā)中,實時消息推送已成為提升用戶體驗的關(guān)鍵技術(shù)。無論是聊天應(yīng)用、通知系統(tǒng)、實時數(shù)據(jù)展示,還是協(xié)同辦公場景,都需要服務(wù)器能夠主動向客戶端推送消息。本文將詳細(xì)介紹SpringBoot中實現(xiàn)網(wǎng)頁消息推送的幾種主流方案,幫助開發(fā)者根據(jù)實際需求選擇最合適的技術(shù)。

一、為什么需要消息推送

傳統(tǒng)的HTTP請求是客戶端主動請求,服務(wù)端被動響應(yīng)的模式。但在很多場景下,我們需要服務(wù)器能夠主動將消息推送給瀏覽器,例如:

  • Web版即時通訊
  • 股票、基金等金融數(shù)據(jù)實時更新
  • 系統(tǒng)通知和提醒
  • 協(xié)同編輯文檔時的實時更新
  • ......

二、消息推送實現(xiàn)方案

1. 短輪詢 (Short Polling)

原理:客戶端以固定的時間間隔頻繁發(fā)送請求,詢問服務(wù)器是否有新消息。

實現(xiàn)方式

@RestController
@RequestMapping("/api/messages")
public class MessageController {
    
    private final Map<String, List<String>> userMessages = new ConcurrentHashMap<>();
    
    @GetMapping("/{userId}")
    public List<String> getMessages(@PathVariable String userId) {
        List<String> messages = userMessages.getOrDefault(userId, new ArrayList<>());
        List<String> result = new ArrayList<>(messages);
        messages.clear();  // 清空已讀消息
        return result;
    }
    
    @PostMapping("/{userId}")
    public void sendMessage(@PathVariable String userId, @RequestBody String message) {
        userMessages.computeIfAbsent(userId, k -> new ArrayList<>()).add(message);
    }
}

前端實現(xiàn)

function startPolling() {
    setInterval(() => {
        fetch('/api/messages/user123')
            .then(response => response.json())
            .then(messages => {
                if (messages.length > 0) {
                    messages.forEach(msg => console.log(msg));
                }
            });
    }, 3000); // 每3秒查詢一次
}

優(yōu)點(diǎn)

  • 實現(xiàn)簡單,不需要特殊的服務(wù)器配置
  • 兼容性好,支持幾乎所有瀏覽器和服務(wù)器

缺點(diǎn)

  • 資源消耗大,大量無效請求
  • 實時性較差,受輪詢間隔影響
  • 服務(wù)器負(fù)載高,尤其是在用戶量大的情況下

2. 長輪詢 (Long Polling)

原理:客戶端發(fā)送請求后,如果服務(wù)器沒有新消息,則保持連接打開直到有新消息或超時,然后客戶端立即發(fā)起新的請求。

實現(xiàn)方式

@RestController
@RequestMapping("/api/long-polling")
public class LongPollingController {
    
    private final Map<String, DeferredResult<List<String>>> waitingRequests = new ConcurrentHashMap<>();
    private final Map<String, List<String>> pendingMessages = new ConcurrentHashMap<>();
    
    @GetMapping("/{userId}")
    public DeferredResult<List<String>> waitForMessages(@PathVariable String userId) {
        DeferredResult<List<String>> result = new DeferredResult<>(60000L, new ArrayList<>());
        
        // 檢查是否有待處理的消息
        List<String> messages = pendingMessages.get(userId);
        if (messages != null && !messages.isEmpty()) {
            List<String> messagesToSend = new ArrayList<>(messages);
            messages.clear();
            result.setResult(messagesToSend);
        } else {
            // 沒有消息,等待
            waitingRequests.put(userId, result);
            
            result.onCompletion(() -> waitingRequests.remove(userId));
            result.onTimeout(() -> waitingRequests.remove(userId));
        }
        
        return result;
    }
    
    @PostMapping("/{userId}")
    public void sendMessage(@PathVariable String userId, @RequestBody String message) {
        // 查看是否有等待的請求
        DeferredResult<List<String>> deferredResult = waitingRequests.get(userId);
        
        if (deferredResult != null) {
            List<String> messages = new ArrayList<>();
            messages.add(message);
            deferredResult.setResult(messages);
            waitingRequests.remove(userId);
        } else {
            // 存儲消息,等待下一次輪詢
            pendingMessages.computeIfAbsent(userId, k -> new ArrayList<>()).add(message);
        }
    }
}

前端實現(xiàn)

function longPolling() {
    fetch('/api/long-polling/user123')
        .then(response => response.json())
        .then(messages => {
            if (messages.length > 0) {
                messages.forEach(msg => console.log(msg));
            }
            // 立即發(fā)起下一次長輪詢
            longPolling();
        })
        .catch(() => {
            // 出錯后延遲一下再重試
            setTimeout(longPolling, 5000);
        });
}

優(yōu)點(diǎn)

  • 減少無效請求,相比短輪詢更高效
  • 近實時體驗,有消息時立即推送
  • 兼容性好,幾乎所有瀏覽器都支持

缺點(diǎn)

  • 服務(wù)器資源消耗,大量連接會占用服務(wù)器資源
  • 可能受超時限制
  • 難以處理服務(wù)器主動推送的場景

3. Server-Sent Events (SSE)

原理:服務(wù)器與客戶端建立單向連接,服務(wù)器可以持續(xù)向客戶端推送數(shù)據(jù),而不需要客戶端重復(fù)請求。

SpringBoot實現(xiàn)

@RestController
@RequestMapping("/api/sse")
public class SSEController {
    
    private final Map<String, SseEmitter> emitters = new ConcurrentHashMap<>();
    
    @GetMapping("/subscribe/{userId}")
    public SseEmitter subscribe(@PathVariable String userId) {
        SseEmitter emitter = new SseEmitter(Long.MAX_VALUE);
        
        emitter.onCompletion(() -> emitters.remove(userId));
        emitter.onTimeout(() -> emitters.remove(userId));
        emitter.onError(e -> emitters.remove(userId));
        
        // 發(fā)送一個初始事件保持連接
        try {
            emitter.send(SseEmitter.event().name("INIT").data("連接已建立"));
        } catch (IOException e) {
            emitter.completeWithError(e);
        }
        
        emitters.put(userId, emitter);
        return emitter;
    }
    
    @PostMapping("/publish/{userId}")
    public ResponseEntity<String> publish(@PathVariable String userId, @RequestBody String message) {
        SseEmitter emitter = emitters.get(userId);
        if (emitter != null) {
            try {
                emitter.send(SseEmitter.event()
                    .name("MESSAGE")
                    .data(message));
                return ResponseEntity.ok("消息已發(fā)送");
            } catch (IOException e) {
                emitters.remove(userId);
                return ResponseEntity.internalServerError().body("發(fā)送失敗");
            }
        } else {
            return ResponseEntity.notFound().build();
        }
    }
    
    @PostMapping("/broadcast")
    public ResponseEntity<String> broadcast(@RequestBody String message) {
        List<String> deadEmitters = new ArrayList<>();
        
        emitters.forEach((userId, emitter) -> {
            try {
                emitter.send(SseEmitter.event()
                    .name("BROADCAST")
                    .data(message));
            } catch (IOException e) {
                deadEmitters.add(userId);
            }
        });
        
        deadEmitters.forEach(emitters::remove);
        return ResponseEntity.ok("廣播消息已發(fā)送");
    }
}

前端實現(xiàn)

function connectSSE() {
    const eventSource = new EventSource('/api/sse/subscribe/user123');
    
    eventSource.addEventListener('INIT', function(event) {
        console.log(event.data);
    });
    
    eventSource.addEventListener('MESSAGE', function(event) {
        console.log('收到消息: ' + event.data);
    });
    
    eventSource.addEventListener('BROADCAST', function(event) {
        console.log('收到廣播: ' + event.data);
    });
    
    eventSource.onerror = function() {
        eventSource.close();
        // 可以在這里實現(xiàn)重連邏輯
        setTimeout(connectSSE, 5000);
    };
}

優(yōu)點(diǎn)

  • 真正的服務(wù)器推送,節(jié)省資源
  • 自動重連機(jī)制
  • 支持事件類型區(qū)分
  • 相比WebSocket更輕量

缺點(diǎn)

  • 單向通信,客戶端無法通過SSE向服務(wù)器發(fā)送數(shù)據(jù)
  • 連接數(shù)限制,瀏覽器對同一域名的SSE連接數(shù)有限制
  • IE瀏覽器不支持

4. WebSocket

原理:WebSocket是一種雙向通信協(xié)議,在單個TCP連接上提供全雙工通信通道。

SpringBoot配置

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

public class MessageWebSocketHandler extends TextWebSocketHandler {
    
    private static final Map<String, WebSocketSession> sessions = new ConcurrentHashMap<>();
    
    @Override
    public void afterConnectionEstablished(WebSocketSession session) throws Exception {
        String userId = extractUserId(session);
        sessions.put(userId, session);
    }
    
    @Override
    protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
        // 處理從客戶端接收的消息
        String payload = message.getPayload();
        // 處理邏輯...
    }
    
    @Override
    public void afterConnectionClosed(WebSocketSession session, CloseStatus status) throws Exception {
        String userId = extractUserId(session);
        sessions.remove(userId);
    }
    
    private String extractUserId(WebSocketSession session) {
        // 從session中提取用戶ID
        return session.getUri().getQuery().replace("userId=", "");
    }
    
    // 發(fā)送消息給指定用戶
    public static void sendToUser(String userId, String message) {
        WebSocketSession session = sessions.get(userId);
        if (session != null && session.isOpen()) {
            try {
                session.sendMessage(new TextMessage(message));
            } catch (IOException e) {
                sessions.remove(userId);
            }
        }
    }
    
    // 廣播消息
    public static void broadcast(String message) {
        sessions.forEach((userId, session) -> {
            if (session.isOpen()) {
                try {
                    session.sendMessage(new TextMessage(message));
                } catch (IOException e) {
                    sessions.remove(userId);
                }
            }
        });
    }
}

前端實現(xiàn)

function connectWebSocket() {
    const socket = new WebSocket('ws://localhost:8080/ws/messages?userId=user123');
    
    socket.onopen = function() {
        console.log('WebSocket連接已建立');
        // 可以發(fā)送一條消息
        socket.send(JSON.stringify({type: 'JOIN', content: '用戶已連接'}));
    };
    
    socket.onmessage = function(event) {
        const message = JSON.parse(event.data);
        console.log('收到消息:', message);
    };
    
    socket.onclose = function() {
        console.log('WebSocket連接已關(guān)閉');
        // 可以在這里實現(xiàn)重連邏輯
        setTimeout(connectWebSocket, 5000);
    };
    
    socket.onerror = function(error) {
        console.error('WebSocket錯誤:', error);
        socket.close();
    };
}

優(yōu)點(diǎn)

  • 全雙工通信,服務(wù)器和客戶端可以隨時相互發(fā)送數(shù)據(jù)
  • 實時性最好,延遲最低
  • 效率高,建立連接后無需HTTP頭,數(shù)據(jù)傳輸量小
  • 支持二進(jìn)制數(shù)據(jù)

缺點(diǎn)

  • 實現(xiàn)相對復(fù)雜
  • 對服務(wù)器要求高,需要處理大量并發(fā)連接
  • 可能受到防火墻限制

5. STOMP (基于WebSocket)

原理:STOMP (Simple Text Oriented Messaging Protocol) 是一個基于WebSocket的簡單消息傳遞協(xié)議,提供了更高級的消息傳遞模式。

SpringBoot配置

@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {

    @Override
    public void configureMessageBroker(MessageBrokerRegistry registry) {
        // 啟用簡單的基于內(nèi)存的消息代理
        registry.enableSimpleBroker("/topic", "/queue");
        // 設(shè)置應(yīng)用的前綴
        registry.setApplicationDestinationPrefixes("/app");
        // 設(shè)置用戶目的地前綴
        registry.setUserDestinationPrefix("/user");
    }

    @Override
    public void registerStompEndpoints(StompEndpointRegistry registry) {
        registry.addEndpoint("/ws")
                .setAllowedOrigins("*")
                .withSockJS(); // 添加SockJS支持
    }
}

@Controller
public class MessageController {
    
    private final SimpMessagingTemplate messagingTemplate;
    
    public MessageController(SimpMessagingTemplate messagingTemplate) {
        this.messagingTemplate = messagingTemplate;
    }
    
    // 處理客戶端發(fā)送到/app/sendMessage的消息
    @MessageMapping("/sendMessage")
    public void processMessage(String message) {
        // 處理消息...
    }
    
    // 處理客戶端發(fā)送到/app/chat/{roomId}的消息,并廣播到相應(yīng)的聊天室
    @MessageMapping("/chat/{roomId}")
    @SendTo("/topic/chat/{roomId}")
    public ChatMessage chat(@DestinationVariable String roomId, ChatMessage message) {
        // 處理聊天消息...
        return message;
    }
    
    // 發(fā)送私人消息
    @MessageMapping("/private-message")
    public void privateMessage(PrivateMessage message) {
        messagingTemplate.convertAndSendToUser(
            message.getRecipient(),  // 接收者的用戶名
            "/queue/messages",      // 目的地
            message                 // 消息內(nèi)容
        );
    }
    
    // REST API發(fā)送廣播消息
    @PostMapping("/api/broadcast")
    public ResponseEntity<String> broadcast(@RequestBody String message) {
        messagingTemplate.convertAndSend("/topic/broadcast", message);
        return ResponseEntity.ok("消息已廣播");
    }
    
    // REST API發(fā)送私人消息
    @PostMapping("/api/private-message/{userId}")
    public ResponseEntity<String> sendPrivateMessage(
            @PathVariable String userId,
            @RequestBody String message) {
        messagingTemplate.convertAndSendToUser(userId, "/queue/messages", message);
        return ResponseEntity.ok("私人消息已發(fā)送");
    }
}

前端實現(xiàn)

const stompClient = new StompJs.Client({
    brokerURL: 'ws://localhost:8080/ws',
    connectHeaders: {
        login: 'user',
        passcode: 'password'
    },
    debug: function (str) {
        console.log(str);
    },
    reconnectDelay: 5000,
    heartbeatIncoming: 4000,
    heartbeatOutgoing: 4000
});

stompClient.onConnect = function (frame) {
    console.log('Connected: ' + frame);
    
    // 訂閱廣播消息
    stompClient.subscribe('/topic/broadcast', function (message) {
        console.log('收到廣播: ' + message.body);
    });
    
    // 訂閱特定聊天室
    stompClient.subscribe('/topic/chat/room1', function (message) {
        const chatMessage = JSON.parse(message.body);
        console.log('聊天消息: ' + chatMessage.content);
    });
    
    // 訂閱私人消息
    stompClient.subscribe('/user/queue/messages', function (message) {
        console.log('收到私人消息: ' + message.body);
    });
    
    // 發(fā)送消息到聊天室
    stompClient.publish({
        destination: '/app/chat/room1',
        body: JSON.stringify({
            sender: 'user123',
            content: '大家好!',
            timestamp: new Date()
        })
    });
    
    // 發(fā)送私人消息
    stompClient.publish({
        destination: '/app/private-message',
        body: JSON.stringify({
            sender: 'user123',
            recipient: 'user456',
            content: '你好,這是一條私信',
            timestamp: new Date()
        })
    });
};

stompClient.onStompError = function (frame) {
    console.error('STOMP錯誤: ' + frame.headers['message']);
    console.error('Additional details: ' + frame.body);
};

stompClient.activate();

優(yōu)點(diǎn)

  • 高級消息模式:主題訂閱、點(diǎn)對點(diǎn)消息傳遞
  • 內(nèi)置消息代理,簡化消息路由
  • 支持消息確認(rèn)和事務(wù)
  • 框架支持完善,SpringBoot集成度高
  • 支持認(rèn)證和授權(quán)

缺點(diǎn)

  • 學(xué)習(xí)曲線較陡
  • 資源消耗較高
  • 配置相對復(fù)雜

三、方案對比與選擇建議

方案實時性雙向通信資源消耗實現(xiàn)復(fù)雜度瀏覽器兼容性
短輪詢極好
長輪詢
SSE否(單向)IE不支持
WebSocket極高良好(需考慮兼容)
STOMP極高良好(需考慮兼容)

選擇建議

  • 簡單通知場景:對實時性要求不高,可以選擇短輪詢長輪詢
  • 服務(wù)器單向推送數(shù)據(jù):如實時數(shù)據(jù)展示、通知提醒等,推薦使用SSE
  • 實時性要求高且需雙向通信:如聊天應(yīng)用、在線游戲等,應(yīng)選擇WebSocket
  • 復(fù)雜消息傳遞需求:如需要主題訂閱、點(diǎn)對點(diǎn)消息、消息確認(rèn)等功能,推薦使用STOMP
  • 需要考慮老舊瀏覽器:應(yīng)避免使用SSE和WebSocket,或提供降級方案

四、總結(jié)

在SpringBoot中實現(xiàn)網(wǎng)頁消息推送,有多種技術(shù)方案可選,每種方案都有其適用場景:

  • 短輪詢:最簡單但效率最低,適合非實時性要求的場景
  • 長輪詢:改進(jìn)版的輪詢,降低了服務(wù)器負(fù)載,提高了實時性
  • SSE:輕量級的服務(wù)器推送技術(shù),適合單向通信場景
  • WebSocket:功能最強(qiáng)大的雙向通信方案,適合高實時性要求場景
  • STOMP:基于WebSocket的消息協(xié)議,提供了更高級的消息傳遞功能

選擇合適的推送技術(shù)需要根據(jù)業(yè)務(wù)需求性能要求瀏覽器兼容性等因素綜合考慮。在實際應(yīng)用中,也可以結(jié)合多種技術(shù),提供優(yōu)雅降級方案,確保在各種環(huán)境下都能提供良好的用戶體驗。

以上就是SpringBoot實現(xiàn)網(wǎng)頁消息推送的5種方法小結(jié)的詳細(xì)內(nèi)容,更多關(guān)于SpringBoot網(wǎng)頁消息推送的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • MyBatis傳入多個參數(shù)時parameterType的寫法

    MyBatis傳入多個參數(shù)時parameterType的寫法

    這篇文章主要介紹了MyBatis傳入多個參數(shù)時parameterType的寫法,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-12-12
  • MybatisPlus分頁失效不起作用的解決

    MybatisPlus分頁失效不起作用的解決

    在使用MybatisPlus的selectPage時發(fā)現(xiàn)分頁不起作用,每次返回的都是全部的數(shù)據(jù),本文就來介紹一下MybatisPlus分頁失效不起作用的解決,感興趣的可以了解一下
    2024-03-03
  • java實現(xiàn)線程調(diào)度器和時間分片

    java實現(xiàn)線程調(diào)度器和時間分片

    線程調(diào)度器和時間分片是多線程編程和操作系統(tǒng)設(shè)計中的核心概念,本文主要介紹了java實現(xiàn)線程調(diào)度器和時間分片,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2024-10-10
  • spring security中Authority、Role的區(qū)別及說明

    spring security中Authority、Role的區(qū)別及說明

    這篇文章主要介紹了spring security中Authority、Role的區(qū)別及說明,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-09-09
  • Python__雙劃線參數(shù)代碼實例解析

    Python__雙劃線參數(shù)代碼實例解析

    這篇文章主要介紹了python__雙劃線參數(shù)代碼實例解析,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下
    2020-02-02
  • Java AQS的實現(xiàn)原理詳解

    Java AQS的實現(xiàn)原理詳解

    這篇文章主要借助了ReentrantLock來帶大家搞清楚AQS的實現(xiàn)原理,文中的示例代碼講解詳細(xì),具有一定的學(xué)習(xí)價值,感興趣的小伙伴可以了解一下
    2023-04-04
  • Kafka消費(fèi)客戶端協(xié)調(diào)器GroupCoordinator詳解

    Kafka消費(fèi)客戶端協(xié)調(diào)器GroupCoordinator詳解

    這篇文章主要為大家介紹了Kafka消費(fèi)客戶端協(xié)調(diào)器GroupCoordinator使用示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-10-10
  • java中優(yōu)化大量if...else...方法總結(jié)

    java中優(yōu)化大量if...else...方法總結(jié)

    在我們平時的開發(fā)過程中,經(jīng)??赡軙霈F(xiàn)大量If else的場景,代碼顯的很臃腫,非常不優(yōu)雅,下面這篇文章主要給大家介紹了關(guān)于java中優(yōu)化大量if...else...方法的相關(guān)資料,需要的朋友可以參考下
    2023-03-03
  • mybatis-generator-gui 工具使用(圖形化工具)

    mybatis-generator-gui 工具使用(圖形化工具)

    基于 mybatis generator 開發(fā)一款界面工具, 本工具可以使你非常容易及快速生成 Mybatis 的 Java POJO 文件及數(shù)據(jù)庫 Mapping 文件。本文重點(diǎn)給大家介紹mybatis-generator-gui 工具使用,感興趣的朋友一起看看吧
    2022-03-03
  • Java讀寫.properties文件解決中文亂碼問題

    Java讀寫.properties文件解決中文亂碼問題

    這篇文章主要介紹了Java讀寫.properties文件解決中文亂碼問題,非常具有實用價值,需要的朋友可以參考下
    2017-11-11

最新評論

齐齐哈尔市| 精河县| 琼结县| 读书| 凤冈县| 安陆市| 赤水市| 正宁县| 泰州市| 天水市| 岳池县| 禄丰县| 城步| 游戏| 扶绥县| 定远县| 桐柏县| 且末县| 客服| 济阳县| 江山市| 长春市| 清涧县| 扬州市| 邵武市| 平乡县| 无极县| 望都县| 讷河市| 阜平县| 九江县| 汝南县| 上饶市| 察隅县| 于田县| 毕节市| 苏尼特右旗| 浏阳市| 台中市| 万荣县| 安多县|