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