欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

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

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

項目開發(fā)中,實時消息推送已成為提升用戶體驗的關(guān)鍵技術(shù)。無論是聊天應(yīng)用、通知系統(tǒng)、實時數(shù)據(jù)展示,還是協(xié)同辦公場景,都需要服務(wù)器能夠主動向客戶端推送消息。本文將詳細介紹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)點

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

缺點

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

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)點

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

缺點

  • 服務(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)點

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

缺點

  • 單向通信,客戶端無法通過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)點

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

缺點

  • 實現(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)點

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

缺點

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

三、方案對比與選擇建議

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

選擇建議

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

四、總結(jié)

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

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

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

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

相關(guān)文章

  • Java實現(xiàn)支付寶之第三方支付寶即時到賬支付功能

    Java實現(xiàn)支付寶之第三方支付寶即時到賬支付功能

    這篇文章主要介紹了Java實現(xiàn)支付寶之第三方支付寶即時到賬支付功能的相關(guān)資料,非常不錯,具有參考借鑒價值,需要的朋友可以參考下
    2016-07-07
  • Java Lambda表達式超詳細介紹

    Java Lambda表達式超詳細介紹

    這篇文章主要介紹了Java Lambda表達式,Lambda表達式是Java SE 8中一個重要的新特性, Lambda 表達式(Lambda expression)可以看作是一個匿名函數(shù),基于數(shù)學(xué)中的λ演算得名,也可稱為閉包(Closure),下面來看看文章具體的詳細介紹吧
    2022-02-02
  • Java獲取兩個字符串中最大相同子串的方法

    Java獲取兩個字符串中最大相同子串的方法

    今天小編就為大家分享一篇Java獲取兩個字符串中最大相同子串的方法,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2018-07-07
  • java中關(guān)于getProperties方法的使用

    java中關(guān)于getProperties方法的使用

    這篇文章主要介紹了java中關(guān)于getProperties方法的使用,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-12-12
  • SpringBoot集成Spring Security的方法

    SpringBoot集成Spring Security的方法

    Spring security,是一個強大的和高度可定制的身份驗證和訪問控制框架。這篇文章主要介紹了SpringBoot集成Spring Security的操作方法,本文給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-07-07
  • SpringBoot快速集成jxls-poi(自定義模板,支持本地文件導(dǎo)出,在線文件導(dǎo)出)

    SpringBoot快速集成jxls-poi(自定義模板,支持本地文件導(dǎo)出,在線文件導(dǎo)出)

    這篇文章主要介紹了SpringBoot快速集成jxls-poi(自定義模板,支持本地文件導(dǎo)出,在線文件導(dǎo)出),文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-09-09
  • 詳解java一維數(shù)組及練習(xí)題實例

    詳解java一維數(shù)組及練習(xí)題實例

    在本篇文章里小編給大家整理了關(guān)于java一維數(shù)組及練習(xí)題的相關(guān)知識點和實例代碼,有需要的朋友們跟著學(xué)習(xí)下。
    2019-07-07
  • Spring中的事務(wù)管理及實現(xiàn)方式解析

    Spring中的事務(wù)管理及實現(xiàn)方式解析

    這篇文章主要介紹了Spring中的事務(wù)管理及實現(xiàn)方式解析,Spring事務(wù)管理基于底層數(shù)據(jù)庫本身的事務(wù)處理機制,數(shù)據(jù)庫事務(wù)的基礎(chǔ),是掌握Spring事務(wù)管理的基礎(chǔ),這篇總結(jié)下Spring事務(wù),需要的朋友可以參考下
    2024-01-01
  • Java編程實現(xiàn)基于TCP協(xié)議的Socket聊天室示例

    Java編程實現(xiàn)基于TCP協(xié)議的Socket聊天室示例

    這篇文章主要介紹了Java編程實現(xiàn)基于TCP協(xié)議的Socket聊天室,結(jié)合實例形式詳細分析了java基于TCP協(xié)議的Socket聊天室客戶端與服務(wù)器端相關(guān)實現(xiàn)與使用技巧,需要的朋友可以參考下
    2018-01-01
  • Java中數(shù)組的定義和使用教程(二)

    Java中數(shù)組的定義和使用教程(二)

    這篇文章主要給大家介紹了關(guān)于Java中數(shù)組的定義和使用的相關(guān)資料,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-01-01

最新評論