在 Java 中使用 WebSocket
作者:青年老年程序員
原文:https://blog.csdn.net/bairo007/article/details/131779053
一、 簡介
1.1 什麼是 WebSocket
WebSocket 是一種協議,用於在 Web 應用程序和服務器之間建立實時、雙向的通信連接。它通過一個單一的 TCP 連接提供了持久化連接,這使得 Web 應用程序可以更加實時地傳遞數據。WebSocket 協議最初由 W3C 開發,並於 2011 年成爲標準。
1.2 WebSocket 的優勢和劣勢
WebSocket 的優勢包括:
-
實時性: 由於 WebSocket 的持久化連接,它可以實現實時的數據傳輸,避免了 Web 應用程序需要不斷地發送請求以獲取最新數據的情況。
-
雙向通信: WebSocket 協議支持雙向通信,這意味着服務器可以主動向客戶端發送數據,而不需要客戶端發送請求。
-
減少網絡負載: 由於 WebSocket 的持久化連接,它可以減少 HTTP 請求的數量,從而減少了網絡負載。
WebSocket 的劣勢包括:
-
需要瀏覽器和服務器都支持: WebSocket 是一種相對新的技術,需要瀏覽器和服務器都支持。一些舊的瀏覽器和服務器可能不支持 WebSocket。
-
需要額外的開銷: WebSocket 需要在服務器上維護長時間的連接,這需要額外的開銷,包括內存和 CPU。
-
安全問題: 由於 WebSocket 允許服務器主動向客戶端發送數據,可能會存在安全問題。服務器必須保證只向合法的客戶端發送數據。
二、 WebSocket 的基本概念
2.1 WebSocket 的協議
WebSocket 協議是一種基於 TCP 的協議,用於在客戶端和服務器之間建立持久連接,並且可以在這個連接上實時地交換數據。WebSocket 協議有自己的握手協議,用於建立連接,也有自己的數據傳輸格式。
當客戶端發送一個 WebSocket 請求時,服務器將發送一個協議響應以確認請求。在握手期間,客戶端和服務器將協商使用的協議版本、支持的子協議、支持的擴展選項等。一旦握手完成,連接將保持打開狀態,客戶端和服務器就可以在連接上實時地傳遞數據。
WebSocket 協議使用的是雙向數據傳輸,即客戶端和服務器都可以在任意時間向對方發送數據,而不需要等待對方的請求。它支持二進制數據和文本數據,可以自由地在它們之間進行轉換。
總之,WebSocket 協議是一種可靠的、高效的、雙向的、持久的通信協議,它適用於需要實時通信的 Web 應用程序,如在線遊戲、實時聊天等。
2.2 WebSocket 的生命週期
WebSocket 生命週期描述了 WebSocket 連接從創建到關閉的過程。一個 WebSocket 連接包含以下四個主要階段:
-
連接建立階段(Connection Establishment): 在這個階段,客戶端和服務器之間的 WebSocket 連接被建立。客戶端發送一個 WebSocket 握手請求,服務器響應一個握手響應,然後連接就被建立了。
-
連接開放階段(Connection Open): 在這個階段,WebSocket 連接已經建立並開放,客戶端和服務器可以在連接上互相發送數據。
-
連接關閉階段(Connection Closing): 在這個階段,一個 WebSocket 連接即將被關閉。它可以被客戶端或服務器發起,通過發送一個關閉幀來關閉連接。
-
連接關閉完成階段(Connection Closed): 在這個階段,WebSocket 連接已經完全關閉。客戶端和服務器之間的任何交互都將無效。
需要注意的是,WebSocket 連接在任何時候都可能關閉,例如網絡故障、服務器崩潰等情況都可能導致連接關閉。因此,需要及時處理 WebSocket 連接關閉的事件,以確保應用程序的可靠性和穩定性。
下面是一個簡單的 WebSocket 生命週期示意圖:
在這個示意圖中,客戶端向服務器發送一個 WebSocket 握手請求,服務器響應一個握手響應,連接就被建立了。一旦連接建立,客戶端和服務器就可以在連接上互相發送數據,直到其中一方發送一個關閉幀來關閉連接。在關閉幀被接收後,連接就會被關閉,WebSocket 連接關閉完成。
2.3 WebSocket 的消息格式
WebSocket 的消息格式與 HTTP 請求和響應的消息格式有所不同。WebSocket 的消息格式可以是文本或二進制數據,並且 WebSocket 消息的傳輸是在一個已經建立的連接上進行的,因此不需要再進行 HTTP 請求和響應的握手操作。
WebSocket 消息格式由兩個部分組成:消息頭和消息體。
消息頭包含以下信息:
-
FIN: 表示這是一條完整的消息,一般情況下都是 1。
-
RSV1、RSV2、RSV3: 暫時沒有使用,一般都是 0。
-
Opcode: 表示消息的類型,包括文本消息、二進制消息等。
-
Mask: 表示消息是否加密。
-
Payload length: 表示消息體的長度。
-
Masking key: 僅在消息需要加密時出現,用於對消息進行解密。
消息體就是實際傳輸的數據,可以是文本或二進制數據。
2.4 WebSocket 的 API
WebSocket API 是用於在 Web 應用程序中創建和管理 WebSocket 連接的接口集合。WebSocket API 由瀏覽器原生支持,無需使用額外的 JavaScript 庫或框架,可以直接在 JavaScript 中使用。
下面是一些常用的 WebSocket API:
WebSocket 構造函數: WebSocket 構造函數用於創建 WebSocket 對象。它接受一個 URL 作爲參數,表示要連接的 WebSocket 服務器的地址。例如:
let ws = new WebSocket('ws://example.com/ws');
WebSocket.send() 方法: WebSocket.send()
方法用於向服務器發送數據。它接受一個參數,表示要發送的數據。數據可以是字符串、Blob 對象或 ArrayBuffer 對象。例如:
ws.send('Hello, server!');
WebSocket.onopen 事件: WebSocket.onopen
事件在 WebSocket 連接成功建立時觸發。例如:
ws.onopen = function() {
console.log('WebSocket 連接已經建立。');
};
WebSocket.onmessage 事件: WebSocket.onmessage
事件在接收到服務器發送的消息時觸發。它的 event 對象包含一個 data 屬性,表示接收到的數據。例如:
ws.onmessage = function(event) {
console.log('收到服務器消息:', event.data);
};
WebSocket.onerror 事件: WebSocket.onerror
事件在 WebSocket 連接出現錯誤時觸發。例如:
ws.onerror = function(event) {
console.error('WebSocket 連接出現錯誤:', event);
};
WebSocket.onclose 事件: WebSocket.onclose
事件在 WebSocket 連接被關閉時觸發。例如:
ws.onclose = function() {
console.log('WebSocket 連接已經關閉。');
};
以上是一些常用的 WebSocket API。
三、 在 Java 中使用 WebSocket
依賴:
<dependency>
<groupId>javax.websocket</groupId>
<artifactId>javax.websocket-api</artifactId>
<version>1.1</version>
</dependency>
3.1 使用 Java WebSocket API 編寫 WebSocket 服務端
下面是一個使用 Java WebSocket API 編寫 WebSocket 服務端的示例代碼:
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
@ServerEndpoint("/echo")
public class EchoServer {
@OnOpen
public void onOpen(Session session) {
System.out.println("WebSocket 連接已經建立。");
}
@OnMessage
public void onMessage(String message, Session session) throws IOException {
System.out.println("收到客戶端消息:" + message);
session.getBasicRemote().sendText("服務器收到消息:" + message);
}
@OnClose
public void onClose() {
System.out.println("WebSocket 連接已經關閉。");
}
@OnError
public void onError(Throwable t) {
System.out.println("WebSocket 連接出現錯誤:" + t.getMessage());
}
}
這個示例代碼定義了一個名爲 "echo" 的 WebSocket 端點,它會監聽客戶端發來的消息,並將收到的消息返回給客戶端。具體來說,它使用了 @ServerEndpoint
註解來指定 WebSocket 端點的 URL,使用了 @OnOpen
、@OnMessage
、@OnClose
和 @OnError
註解來定義 WebSocket 事件處理器。
要使用這個 WebSocket 服務端,我們需要部署它到一個支持 WebSocket 的 Web 容器中。例如,我們可以使用 Tomcat 8 或以上版本來運行它。在部署完成後,我們可以使用任何支持 WebSocket 的客戶端來連接這個服務端,發送消息並接收服務器的響應。
例如,下面是一個簡單的 HTML/JavaScript 客戶端代碼:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>WebSocket Demo</title>
<script>
var ws = new WebSocket('ws://localhost:8080/echo');
ws.onopen = function() {
console.log('WebSocket 連接已經建立。');
ws.send('Hello, server!');
};
ws.onmessage = function(event) {
console.log('收到服務器消息:', event.data);
};
ws.onerror = function(event) {
console.error('WebSocket 連接出現錯誤:', event);
};
ws.onclose = function() {
console.log('WebSocket 連接已經關閉。');
};
</script>
</head>
<body>
<h1>WebSocket Demo</h1>
</body>
</html>
這個客戶端使用了 WebSocket 構造函數來創建一個 WebSocket 對象,並指定連接的 URL 爲我們之前部署的服務端的 URL。它使用了 WebSocket 的事件處理器來處理 WebSocket 事件,例如當 WebSocket 連接成功建立時,它會向服務器發送一條消息,並在收到服務器的響應時打印出消息內容。
3.2 使用 Java WebSocket API 編寫 WebSocket 客戶端
下面是一個使用 Java WebSocket API 編寫 WebSocket 客戶端的示例代碼:
import javax.websocket.*;
import java.io.IOException;
import java.net.URI;
@ClientEndpoint
public class EchoClient {
private Session session;
@OnOpen
public void onOpen(Session session) {
System.out.println("WebSocket 連接已經建立。");
this.session = session;
}
@OnMessage
public void onMessage(String message, Session session) {
System.out.println("收到服務器消息:" + message);
}
@OnClose
public void onClose() {
System.out.println("WebSocket 連接已經關閉。");
}
@OnError
public void onError(Throwable t) {
System.out.println("WebSocket 連接出現錯誤:" + t.getMessage());
}
public void connect(String url) throws Exception {
WebSocketContainer container = ContainerProvider.getWebSocketContainer();
container.connectToServer(this, new URI(url));
}
public void send(String message) throws IOException {
session.getBasicRemote().sendText(message);
}
public void close() throws IOException {
session.close();
}
}
3.3 使用 Spring Boot 編寫 WebSocket 服務端
創建 Spring Boot 項目
首先,您需要創建一個新的 Spring Boot 項目。可以使用 Spring Initializr 創建一個新項目,添加依賴項。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
配置 WebSocket
應用程序中,需要配置 WebSocket。創建一個新的 Java 類,並添加註釋@ServerEndpoint("/websocket")
。這將指定 WebSocket 服務端的端點。
在此類中,需要實現幾個方法:
import javax.websocket.OnClose;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.ServerEndpoint;
@ServerEndpoint("/websocket")
public class WebSocketServer {
@OnOpen
public void onOpen(Session session) {
System.out.println("Connection opened: " + session.getId());
sessions.add(session);
}
@OnMessage
public void onMessage(Session session, String message) throws IOException {
System.out.println("Received message: " + message);
session.getBasicRemote().sendText("Server received: " + message);
}
@OnClose
public void onClose(Session session) {
System.out.println("Connection closed: " + session.getId());
sessions.remove(session);
}
private static final Set<Session> sessions = Collections.synchronizedSet(new HashSet<Session>());
}
處理 WebSocket 消息
在@OnMessage
方法中,可以處理 WebSocket 客戶端發送的消息,並向客戶端發送響應。下面是一個簡單的示例代碼:
@OnMessage
public void onMessage(Session session, String message) throws IOException {
System.out.println("Received message: " + message);
session.getBasicRemote().sendText("Server received: " + message);
}
在此代碼中,我們簡單地打印出收到的消息,並向客戶端發送響應。
關閉 WebSocket 連接
在@OnClose
方法中,可以刪除連接並做一些清理工作。下面是一個示例代碼:
@OnClose
public void onClose(Session session) {
System.out.println("Connection closed: " + session.getId());
sessions.remove(session);
}
在此代碼中,我們從連接池中刪除連接,並打印出連接已關閉的消息。
配置 WebSocket 支持
最後,需要配置 Spring Boot 以支持 WebSocket。創建一個新的 Java 類,並添加註釋@Configuration
和@EnableWebSocket
。然後,需要覆蓋方法registerWebSocketHandlers()
,並指定 WebSocket 處理程序。下面是一個示例代碼:
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(new WebSocketServer(), "/websocket").setAllowedOrigins("*");
}
}
在此代碼中,我們創建了一個新的WebSocketServer
對象,並將其添加到 WebSocket 處理程序中。我們還指定了 WebSocket 端點(/websocket
)和允許的來源(*
)。
四、 WebSocket 的消息格式
4.1 文本消息和二進制消息
文本消息是普通的 Unicode 文本字符串。當 WebSocket 連接建立時,客戶端和服務器可以通過發送文本消息來互相交換信息。服務器可以使用 Session 對象的getBasicRemote()
方法來向客戶端發送文本消息,客戶端可以使用 WebSocket 的send()
方法來向服務器發送文本消息。
下面是向客戶端發送文本消息的示例代碼:
session.getBasicRemote().sendText("Hello, client!");
二進制消息可以是任意類型的數據,包括圖像、音頻、視頻等。要向客戶端發送二進制消息,服務器可以使用 Session 對象的getBasicRemote()
方法,將消息作爲 ByteBuffer 對象發送。客戶端可以使用 WebSocket 的 send() 方法來向服務器發送二進制消息。
下面是向客戶端發送二進制消息的示例代碼:
byte[] data = // binary data
ByteBuffer buffer = ByteBuffer.wrap(data);
session.getBasicRemote().sendBinary(buffer);
請注意,儘管文本消息和二進制消息在格式上有所不同,但它們都是通過 WebSocket 發送的消息類型,因此客戶端和服務器都需要能夠處理這兩種類型的消息。
4.2 Ping 和 Pong 消息
WebSocket 還支持 Ping 和 Pong 消息類型,用於檢測 WebSocket 連接是否仍然處於活動狀態。Ping 消息由客戶端發送到服務器,Pong 消息由服務器發送回客戶端作爲響應。如果客戶端在一段時間內沒有收到 Pong 消息,則它可以假定 WebSocket 連接已斷開,並關閉連接。
要發送 Ping 消息,請使用 Session 對象的getBasicRemote()
方法,並將 Ping 消息作爲 ByteBuffer 對象發送。客戶端可以使用 WebSocket 的sendPing()
方法來向服務器發送 Ping 消息。
下面是向客戶端發送 Ping 消息的示例代碼:
ByteBuffer pingMessage = ByteBuffer.wrap(new byte[] { 8, 9, 10 });
session.getBasicRemote().sendPing(pingMessage);
要接收 Pong 消息,請在您的 WebSocket 處理程序中實現onPong()
方法。當您的 WebSocket 服務器接收到 Pong 消息時,它將自動調用此方法,並將接收到的 Pong 消息作爲 ByteBuffer 對象傳遞給它。
下面是實現 onPong() 方法的示例代碼:
@OnMessage
public void onPong(Session session, ByteBuffer pongMessage) {
System.out.println("Received Pong message: " + pongMessage);
}
請注意,Ping 和 Pong 消息通常用於 WebSocket 連接的健康檢查。如果您希望在 WebSocket 連接中使用此功能,則應定期發送 Ping 消息並等待 Pong 消息的響應。
4.3 關閉消息
WebSocket 還支持關閉消息類型,用於關閉 WebSocket 連接。關閉消息可以由客戶端或服務器發起,並且可以攜帶一個可選的狀態碼和關閉原因。當 WebSocket 連接關閉時,客戶端和服務器都應該發送一個關閉消息以結束連接。
要發送關閉消息,請使用 Session 對象的getBasicRemote()
方法,並調用它的sendClose()
方法。關閉消息可以攜帶一個可選的狀態碼和關閉原因。如果您不希望發送狀態碼或關閉原因,則可以將它們設置爲 0 和 null。
下面是向客戶端發送關閉消息的示例代碼:
session.close(new CloseReason(CloseReason.CloseCodes.NORMAL_CLOSURE, "Closing from client."));
要處理接收到的關閉消息,請在您的 WebSocket 處理程序中實現onClose()
方法。當您的 WebSocket 服務器接收到關閉消息時,它將自動調用此方法,並將接收到的狀態碼和關閉原因傳遞給它。
下面是實現onClose()
方法的示例代碼:
@OnClose
public void onClose(Session session, CloseReason closeReason) {
System.out.println("Connection closed: " + closeReason.getCloseCode() + " - " + closeReason.getReasonPhrase());
}
請注意,客戶端和服務器都應該發送關閉消息以結束 WebSocket 連接。如果只有一方發送了關閉消息,則另一方可能無法正確地關閉連接,並且可能需要等待超時才能釋放資源。建議客戶端和服務器在關閉連接時都發送關閉消息,以確保連接正確地關閉。
五、 WebSocket 的性能
5.1 與傳統的 HTTP 請求 / 響應模型比較
-
雙向通信性能更好: WebSocket 協議使用單一的 TCP 連接,允許客戶端和服務器在同一個連接上進行雙向通信。這種實時的雙向通信可以更快地傳輸數據,而不需要建立多個 HTTP 請求 / 響應連接。
-
更小的網絡流量: 與 HTTP 相比,WebSocket 協議需要更少的網絡流量來維護連接,因爲它不需要在每個請求 / 響應交換中發送頭部信息。
-
更低的延遲: WebSocket 協議允許服務器主動向客戶端推送消息,而不需要客戶端先發送請求。這種實時通信可以減少響應延遲,並提高應用程序的性能。
-
更好的服務器資源管理: 由於 WebSocket 連接可以保持活動狀態,服務器可以更好地管理客戶端連接,減少服務器開銷和處理時間。
WebSocket 協議的性能比傳統的 HTTP 請求 / 響應模型更好,特別是在實時通信和低延遲方面。WebSocket 協議適用於需要實時通信和實時數據更新的應用程序,如在線聊天、多人遊戲、實時監控等。
5.2 優化 WebSocket 的性能
-
減少消息大小: WebSocket 傳輸的數據大小對性能有很大影響。儘量減少消息的大小,可以降低網絡帶寬和服務器負載。例如,可以使用二進制傳輸協議來代替文本傳輸,或使用壓縮算法對消息進行壓縮。
-
使用 CDN 加速: 使用 CDN(內容分發網絡)可以將靜態資源緩存到離用戶更近的節點上,提高傳輸速度和性能。CDN 可以緩存 Websocket 的初始握手請求,避免不必要的網絡延遲。
-
使用負載均衡: WebSocket 服務可以使用負載均衡來分配並平衡多個服務器的負載。負載均衡可以避免單個服務器被過載,並提高整個服務的可伸縮性。
-
優化服務端代碼: WebSocket 服務端代碼的性能也是關鍵因素。使用高效的框架和算法,避免使用過多的內存和 CPU 資源,可以提高服務端的性能和響應速度。
-
避免網絡阻塞: WebSocket 的性能也會受到網絡阻塞的影響。當有太多的連接同時請求數據時,服務器的性能會下降。使用合適的線程池和異步 IO 操作可以避免網絡阻塞,提高 WebSocket 服務的併發性能。
六、 WebSocket 的擴展應用和未來發展方向
-
更加完善的標準規範: WebSocket 標準規範還有很多可以優化的地方,未來可能會繼續完善 WebSocket 的標準規範,以適應更加複雜的應用場景。
-
更加安全的通信方式: 由於 WebSocket 的開放性,使得它可能會受到一些安全威脅,未來可能會通過加密、身份驗證等方式來增強 WebSocket 的安全性。
-
更好的兼容性: WebSocket 協議需要在 HTTP 協議的基礎上建立連接,因此可能會遇到兼容性問題,未來可能會通過技術手段來解決這些問題。
-
更好的性能和可伸縮性: WebSocket 協議的性能和可伸縮性對於複雜的應用場景非常關鍵,未來可能會通過技術手段來進一步提高 WebSocket 的性能和可伸縮性。
本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源:https://mp.weixin.qq.com/s/oyHLVzlkFsxokUVTwGJ_QQ