WebSockets與伺服器傳送事件SSE比較

banq發表於2024-04-07

客戶端和伺服器之間的實時通訊對於建立動態和互動式 Web 應用程式至關重要。用於實現此目的的兩種流行技術是伺服器傳送事件 (SSE) 和 Web 套接字。兩者都允許雙向通訊,但它們有不同的用例和實現。

本文旨在探討 SSE 和 WebSocket 之間的差異,並提供一些使用 Java 的程式碼示例。

 伺服器傳送事件(SSE):單向更新
伺服器傳送事件 (SSE) 是一種簡單而高效的技術,用於透過 HTTP 將資料從伺服器推送到客戶端。伺服器傳送的事件建立長期執行的 HTTP 連線,允許伺服器將資料推送到客戶端。它非常適合更新主要來自伺服器的場景,例如股票行情、實時新聞源或只有伺服器廣播訊息的聊天應用程式。

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.sse.Sse;
import jakarta.ws.rs.sse.SseEventSink;

@Path("/events")
public class SSEServer {

    @GET
    @Produces(MediaType.SERVER_SENT_EVENTS)
    public void getServerSentEvents(@jakarta.ws.rs.core.Context SseEventSink eventSink, @jakarta.ws.rs.core.Context  Sse sse) {
        // Set up a new thread to generate events
        new Thread(() -> {
            try {
                for (int i = 0; i < 20; i++) {
                    // Send events to the client
                    eventSink.send(sse.newEventBuilder().name("sseevent").data(" " + i).build());
                    Thread.sleep(2000); // Simulate delay
                }
            } catch (InterruptedException e) {
            } finally {
                // Close the event sink when done
                eventSink.close();
            }
        }).start();
    }
}

上面的程式碼演示了一個定期傳送模擬資料更新的 SSE 伺服器。

下面演示了訂閱 SSE 端點並顯示接收到的資料:

<h1>Server-Sent Events (SSE) Client</h1>

        <div style="background-color: #64a4bd; width: 200px; padding: 10px">

            <h4 id="eventId"></h4>

        </div>

        <script>
            // 處理傳入事件的函式
            function handleEvent(event) {
                var eventsDiv = document.getElementById("eventId");
                eventsDiv.innerHTML = 'Received event: ' + event.data + '<br>';
            }

            // 建立指向 SSE 端點的新事件源
            var eventSource = new EventSource("rest/events");

            // 附加事件監聽器以處理接收到的事件
            eventSource.addEventListener('sseevent', handleEvent);
            //eventSource.onmessage = handleEvent;

            //處理 SSE 錯誤的事件監聽器
            eventSource.onerror = function (event) {
                eventSource.close();
                console.error('SSE error:', event);

            };
        </script>

上面程式碼建立一個EventSource物件,訂閱伺服器的 SSE 端點。

好處:

  • 簡單性: 伺服器和客戶端的實現都相對簡單。
  • 輕量級: 單向通訊使協議保持高效。
  • 廣泛的瀏覽器支援: 大多數現代瀏覽器都提供對 SSE 的內建支援。

缺點:

  • 單向通訊: 資料只能從伺服器傳輸到客戶端。客戶端不能直接發回訊息。
  • 基於文字的資料:  SSE 僅限於文字資料(UTF-8 格式)。它無法傳輸影像或影片等二進位制資料。


WebSockets:雙向通訊
WebSockets 透過客戶端和伺服器之間的單個長期連線提供全雙工通訊通道。與 SSE 不同,WebSocket 允許客戶端和伺服器隨時相互傳送訊息。這種多功能性使它們非常適合聊天或協作編輯等互動式應用程式。

下面程式碼定義了用於雙向通訊的 JAX-RS 註釋的 WebSocket 端點。

import jakarta.websocket.OnClose;
import jakarta.websocket.OnError;
import jakarta.websocket.OnMessage;
import jakarta.websocket.OnOpen;
import jakarta.websocket.Session;
import jakarta.websocket.server.ServerEndpoint;
import java.io.IOException;

@ServerEndpoint("/websocket")
public class WebSocketServer {

    @OnOpen
    public void onOpen(Session session) {
        System.out.println("WebSocket opened: " + session.getId());
    }

    @OnMessage
    public void onMessage(String message, Session session) {
        System.out.println("Message received: " + message);
        try {
            session.getBasicRemote().sendText("Echo: " + message); // Echo back the message
        } catch (IOException e) {
        }
    }

    @OnClose
    public void onClose(Session session) {
        System.out.println("WebSocket closed: " + session.getId());
    }

    @OnError
    public void onError(Throwable error) {
        System.out.println("" + error);
    }
}

下面演示了連線到 WebSocket 端點以及傳送/接收訊息:

<h1>WebSocket Client</h1>
Messages<br/>
<textarea id="messages" rows="6" cols="50" style="background-color: E6E6FF; border-radius: 15px;margin-bottom: 10px;width: 40%;padding: 20px"></textarea><br/>

<form id="messageForm" style="margin-bottom: 10px">
    <input type="text" id="messageInput" placeholder="Enter message" name="name">
    <input type="button" onclick="join()" value="Join"/>
    <input type="button" onclick="sendmessage()" value="Send"/><br/>
    <input type="button" onclick="disconnect()" value="Disconnect"/>
</form><br/>

  <script language="javascript" type="text/javascript">
            // Create WebSocket connection
            const socket = new WebSocket('ws://localhost:8080/my-faces-app/websocket');

            var messageInput = document.getElementById('messageInput');
            var users = document.getElementById('users');
            var messages = document.getElementById('messages');

            var username;

            socket.onopen = function (evt) {
                onOpen(evt);
            };
            socket.onclose = function (evt) {
                onClose(evt);
            };
            socket.onerror = function (evt) {
                onError(evt);
            };
            socket.onmessage = function (evt) {
                onMessage(evt);
            };

            var output = document.getElementById('output');

            function join() {
                username = messageInput.value;
                socket.send(username + " joined");
                // Clear input field
                messageInput.value = '';
            }

            function sendmessage() {
                socket.send(username + ": " + messageInput.value);
                // Clear input field
                messageInput.value = '';
            }

            function onOpen() {
                writeToScreen("CONNECTED");
            }
            function onClose() {
                writeToScreen("DISCONNECTED");
            }

            function onMessage(evt) {
                writeToScreen("Message Received: " + evt.data);
                if (evt.data.indexOf("joined") !== -1) {
                    users.innerHTML += evt.data.substring(0, evt.data.indexOf(" joined")) + "\n";
                } else {
                    messages.innerHTML += evt.data + "\n";
                }
            }

            function onError(evt) {
                wrtieToScreen("Error : " + evt.data);
            }

            function disconnect() {
                socket.close();
            }

            function writeToScreen(message) {
                var pre = document.createElement("p");
                pre.style.wordWrap = "break-word";
                pre.innerHTML = message;

                output.appendChild(pre);
            }
        </script>


WebSocket 具有以下幾個優點:

  • 全雙工通訊: 資料可以在兩個方向上無縫流動,從而促進真正的互動性。
  • 低延遲: 通訊通道是持久的,最大限度地減少資料交換的延遲。
  • 豐富的通訊:  WebSocket不僅支援文字,還支援二進位制資料格式,允許更通用的資料傳輸。

WebSocket 雖然功能強大,但也有一些注意事項:

  • 複雜性: 實現 WebSockets 涉及握手過程和處理雙向訊息流,使其比 SSE 稍微複雜一些。
  • 握手開銷: 建立 WebSocket 連線需要初始握手,與 SSE 相比增加了少量開銷。

WebSockets 與 SSE:比較分析

通訊方向    

  • 全雙工:客戶端和伺服器都可以傳送資料    
  • 單向:伺服器向客戶端推送資料

協議    
  • WebSocket協議    
  • HTTP/HTTPS 協議

連線建立    

  • WebSocket需要握手    
  • SSE使用標準 HTTP 連線

持續連線    
  • WebSocket保持持久連線    
  • SSE使用長期 HTTP 連線

訊息型別    
  • WebSocket支援二進位制和文字訊息  
  • SSE 僅支援簡訊

伺服器開銷    

  • WebSocket低的  
  • SSE 由於 HTTP 標頭和開銷而較高

可擴充套件性    

  • WebSocket連線數更少,可擴充套件性更好  
  •  SSE 由於多個連線,可擴充套件性可能較差

用法    

  • WebSocket實時應用程式、遊戲和聊天應用程式    
  • SSE推送通知、實時更新、股票行情等。

結論
本文探討了使用 WebSocket 與 SSE。透過了解它們的優點和缺點,我們可以做出明智的決定,將實時通訊構建到我們的 Web 應用程式中。總之,伺服器傳送事件 (SSE) 和 Web 套接字是支援 Web 應用程式中客戶端和伺服器之間實時通訊的強大技術。 SSE實現起來比較簡單,非常適合伺服器需要向客戶端推送更新的場景。另一方面,WebSocket 提供全雙工通訊,適合需要雙向通訊的互動性更強的應用程式。
 

相關文章