WebSockets与服务器发送事件SSE比较

客户端和服务器之间的实时通信对于创建动态和交互式 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 提供全双工通信,适合需要双向通信的交互性更强的应用程序。