文章目錄
- WebSocket 的由來
- WebSocket 是什么
- WebSocket 優缺點
- 優點
- 缺點
- WebSocket 適用場景
- 主流瀏覽器對 WebSocket 的兼容性
- WebSocket 通信過程以及原理
- 建立連接
- 具體過程
- 示例
- Sec-WebSocket-Key
- Sec-WebSocket-Extensions
- 數據通信
- 數據幀
- 幀頭(Frame Header)
- 掩碼(Masking)
- 負載數據(Payload Data)
- 來自 MDN 的一個小例子
- 維持連接
- 關閉連接
- 使用 WebSocket 實現一個簡易聊天室
- 前端源碼
- 后端源碼 Java
- 總結
- 參考
- 個人簡介
WebSocket 的由來
- 在 WebSocket 出現之前,我們想實現實時通信、變更推送、服務端消息推送功能,我們一般的方案是使用 Ajax 短輪詢、長輪詢兩種方式:
- 比如我們想實現一個服務端數據變更時,立即通知客戶端功能,沒有 WebSocket 之前我們可能會采用以下兩種方案:短輪詢或長輪詢
- 上面兩種方案都有比較明顯的缺點:
1、HTTP 協議包含的較長的請求頭,有效數據只占很少一部分,浪費帶寬
2、短輪詢頻繁輪詢對服務器壓力較大,即使使用長輪詢方案,客戶端較多時仍會對客戶端造成不小壓力
- 在這種情況下,HTML5 定義了 WebSocket 協議,能更好的節省服務器資源和帶寬,并且能夠更實時地進行通訊。
WebSocket 是什么
- WebSocket 是一種網絡傳輸協議,可在單個 TCP 連接上進行全雙工通信,位于 OSI 模型的應用層。
- WebSocket 使得客戶端和服務器之間的數據交換變得更加簡單,允許服務端主動向客戶端推送數據。客戶端和服務器只需要完成一次握手,兩者之間就可以創建持久性的連接,并進行雙向數據傳輸。
WebSocket 優缺點
優點
- 實時性: WebSocket 提供了雙向通信,服務器可以主動向客戶端推送數據,實現實時性非常高,適用于實時聊天、在線協作等應用。
- 減少網絡延遲: 與輪詢和長輪詢相比,WebSocket 可以顯著減少網絡延遲,因為不需要在每個請求之間建立和關閉連接。
- 較小的數據傳輸開銷: WebSocket 的數據幀相比于 HTTP 請求報文較小,減少了在每個請求中傳輸的開銷,特別適用于需要頻繁通信的應用。
- 較低的服務器資源占用: 由于 WebSocket 的長連接特性,服務器可以處理更多的并發連接,相較于短連接有更低的資源占用。
- 跨域通信: 與一些其他跨域通信方法相比,WebSocket 更容易實現跨域通信。
缺點
- 連接狀態保持: 長時間保持連接可能會導致服務器和客戶端都需要維護連接狀態,可能增加一些負擔。
- 不適用于所有場景: 對于一些請求-響應模式較為簡單的場景,WebSocket 的實時特性可能并不是必要的,使用 HTTP 請求可能更為合適。
- 復雜性: 與傳統的 HTTP 請求相比,WebSocket 的實現和管理可能稍顯復雜,尤其是在處理連接狀態、異常等方面。
WebSocket 適用場景
- 實時聊天應用: WebSocket 是實現實時聊天室、即時通訊應用的理想選擇,因為它能夠提供低延遲和高實時性。
- 在線協作和協同編輯: 對于需要多用戶協同工作的應用,如協同編輯文檔或繪圖,WebSocket 的實時性使得用戶能夠看到其他用戶的操作。
- 實時數據展示: 對于需要實時展示數據變化的應用,例如股票行情、實時監控系統等,WebSocket 提供了一種高效的通信方式。
- 在線游戲: 在線游戲通常需要快速、實時的通信,WebSocket 能夠提供低延遲和高并發的通信能力。
- 推送服務: 用于實現消息推送服務,向客戶端主動推送更新或通知。
主流瀏覽器對 WebSocket 的兼容性
- 由上圖可知:目前主流的 Web 瀏覽器都支持 WebSocket,因此我們可以在大多數項目中放心地使用它。
WebSocket 通信過程以及原理
建立連接
- WebSocket 協議屬于應用層協議,依賴傳輸層的 TCP 協議。它通過 HTTP/1.1 協議的 101 狀態碼進行握手建立連接。
具體過程
- 客戶端發送一個 HTTP GET 請求到服務器,請求的路徑是 WebSocket 的路徑(類似 ws://example.com/socket)。請求中包含一些特殊的頭字段,如 Upgrade: websocket 和 Connection: Upgrade,以表明客戶端希望升級連接為 WebSocket。
- 服務器收到這個請求后,會返回一個 HTTP 101 狀態碼(協議切換協議)。同樣在響應頭中包含 Upgrade: websocket 和 Connection: Upgrade,以及一些其他的 WebSocket 特定的頭字段,例如 Sec-WebSocket-Accept,用于驗證握手的合法性。
- 客戶端和服務器之間的連接從普通的 HTTP 連接升級為 WebSocket 連接。之后,客戶端和服務器之間的通信就變成了 WebSocket 幀的傳輸,而不再是普通的 HTTP 請求和響應。
示例
// 客戶端請求
GET ws://localhost:8888/ HTTP/1.1
Host: localhost:8888
Connection: Upgrade
Upgrade: websocket
Origin: http://localhost:63342
Sec-WebSocket-Version: 13
Accept-Encoding: gzip, deflate, br
Accept-Language: zh-CN,zh;q=0.9,ja;q=0.8,en;q=0.7
Sec-WebSocket-Key: b7wpWuB9MCzOeQZg2O/yPg==
Sec-WebSocket-Extensions: permessage-deflate; client_max_window_bits// 服務端響應
HTTP/1.1 101 Web Socket Protocol Handshake
Connection: Upgrade
Date: Wed, 22 Nov 2023 08:15:00 GMT
Sec-WebSocket-Accept: Q4TEk+qOgJsKy7gedijA5AuUVIw=
Server: TooTallNate Java-WebSocket
Upgrade: websocket
Sec-WebSocket-Key
- 與服務端響應頭部的 Sec-WebSocket-Accept 是配套的,提供基本的防護,比如惡意的連接,或者無意的連接;這里的“配套”指的是:Sec-WebSocket-Accept 是根據請求頭部的 Sec-WebSocket-Key 計算而來,計算過程大致為基于 SHA1 算法得到摘要并轉成 base64 字符串。
Sec-WebSocket-Extensions
- 用于協商本次連接要使用的 WebSocket 擴展。
數據通信
- WebSocket 的每條消息可能會被切分成多個數據幀(最小單位)。發送端會將消息切割成多個幀發送給接收端,接收端接收消息幀并將關聯的幀重新組裝成完整的消息。
數據幀
0 1 2 30 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1+-+-+-+-+-------+-+-------------+-------------------------------+|F|R|R|R| opcode|M| Payload len | Extended payload length ||I|S|S|S| (4) |A| (7) | (16/64) ||N|V|V|V| |S| | (if payload len==126/127) || |1|2|3| |K| | |+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +| Extended payload length continued, if payload len == 127 |+ - - - - - - - - - - - - - - - +-------------------------------+| |Masking-key, if MASK set to 1 |+-------------------------------+-------------------------------+| Masking-key (continued) | Payload Data |+-------------------------------- - - - - - - - - - - - - - - - +: Payload Data continued ... :+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +| Payload Data continued ... |+---------------------------------------------------------------+
幀頭(Frame Header)
- FIN(1比特): 表示這是消息的最后一個幀。如果消息分成多個幀,FIN 位在最后一個幀上設置為 1。
- RSV1、RSV2、RSV3(各1比特): 保留位,用于將來的擴展。
- Opcode(4比特): 指定幀的類型,如文本幀、二進制幀、連接關閉等。
WebSocket 定義了幾種幀類型,其中最常見的是文本幀(Opcode 為 0x1)和二進制幀(Opcode 為 0x2)。其他幀類型包括連接關閉幀、Ping 幀、Pong 幀等。
- Mask(1比特): 指示是否使用掩碼對負載進行掩碼操作。
- Payload Length: 指定數據的長度。如果小于 126 字節,直接表示數據的長度。如果等于 126 字節,后面跟著 16 比特的無符號整數表示數據的長度。如果等于 127 字節,后面跟著 64 比特的無符號整數表示數據的長度。
掩碼(Masking)
- 如果 Mask 位被設置為 1,則幀頭后面的 4 字節即為掩碼,用于對負載數據進行簡單的異或操作,以提高安全性。
負載數據(Payload Data)
- 實際要傳輸的數據,可以是文本、二進制數據等
來自 MDN 的一個小例子
Client: FIN=1, opcode=0x1, msg="hello"
Server: (process complete message immediately) Hi.Client: FIN=0, opcode=0x1, msg="and a"
Server: (listening, newmessage containing text started)Client: FIN=0, opcode=0x0, msg="happy new"
Server: (listening, payload concatenated to previous message)Client: FIN=1, opcode=0x0, msg="year!"
Server: (process complete message) Happy new year to you too!
維持連接
- 當建立連接后,連接可能因為網絡等原因斷開,我們可以使用心跳的方式定時檢測連接狀態。若連接斷開,我們可以告警或者重新建立連接。
關閉連接
- WebSocket 是全雙工通信,當客戶端發送關閉請求時,服務端不一定立即響應,而是等服務端也同意關閉時再進行異步響應。
- 下面是一個客戶端關閉的例子:
Client: FIN=1, opcode=0x8, msg="1000"
Server: FIN=1, opcode=0x8, msg="1000"
使用 WebSocket 實現一個簡易聊天室
- 下面是一個簡易聊天室小案例,任何人打開下面的網頁都可以加入我們聊天室進行聊天,然后小紅和小明加入了聊天:
前端源碼
<!DOCTYPE html>
<html lang="en">
<head><meta charset="UTF-8"><title>WebSocket Chat</title>
</head>
<body>
<div id="chat"></div>
<input type="text" id="messageInput" placeholder="Type your message">
<button onclick="sendMessage()">Send</button><script>const socket = new WebSocket('ws://localhost:8888');socket.onopen = (event) => {console.log('WebSocket connection opened:', event);};socket.onmessage = (event) => {const messageDiv = document.getElementById('chat');const messageParagraph = document.createElement('p');messageParagraph.textContent = event.data;messageDiv.appendChild(messageParagraph);};socket.onclose = (event) => {console.log('WebSocket connection closed:', event);};function sendMessage() {const messageInput = document.getElementById('messageInput');const message = messageInput.value;socket.send(message);messageInput.value = '';}
</script>
</body>
</html>
后端源碼 Java
package chat;import org.java_websocket.WebSocket;
import org.java_websocket.handshake.ClientHandshake;
import org.java_websocket.server.WebSocketServer;import java.net.InetSocketAddress;public class ChatServer extends WebSocketServer {public ChatServer(int port) {super(new InetSocketAddress(port));}@Overridepublic void onOpen(WebSocket conn, ClientHandshake handshake) {System.out.println("New connection from: " + conn.getRemoteSocketAddress().getAddress().getHostAddress());}@Overridepublic void onClose(WebSocket conn, int code, String reason, boolean remote) {System.out.println("Closed connection to: " + conn.getRemoteSocketAddress().getAddress().getHostAddress());}@Overridepublic void onMessage(WebSocket conn, String message) {System.out.println("Received message: " + message);// Broadcast the message to all connected clientsbroadcast(message);}@Overridepublic void onError(WebSocket conn, Exception ex) {System.err.println("Error on connection: " + ex.getMessage());}@Overridepublic void onStart() {}public static void main(String[] args) {int port = 8888;ChatServer server = new ChatServer(port);server.start();System.out.println("WebSocket Server started on port: " + port);}
}
總結
- WebSocket 是一種在客戶端和服務器之間建立實時雙向通信的協議。具備全雙工、低延遲等優點,適用于實時聊天、多人協助、實時數據展示等場景。
參考
- WebSocket:概念、原理
個人簡介
👋 你好,我是 Lorin 洛林,一位 Java 后端技術開發者!座右銘:Technology has the power to make the world a better place.
🚀 我對技術的熱情是我不斷學習和分享的動力。我的博客是一個關于Java生態系統、后端開發和最新技術趨勢的地方。
🧠 作為一個 Java 后端技術愛好者,我不僅熱衷于探索語言的新特性和技術的深度,還熱衷于分享我的見解和最佳實踐。我相信知識的分享和社區合作可以幫助我們共同成長。
💡 在我的博客上,你將找到關于Java核心概念、JVM 底層技術、常用框架如Spring和Mybatis 、MySQL等數據庫管理、RabbitMQ、Rocketmq等消息中間件、性能優化等內容的深入文章。我也將分享一些編程技巧和解決問題的方法,以幫助你更好地掌握Java編程。
🌐 我鼓勵互動和建立社區,因此請留下你的問題、建議或主題請求,讓我知道你感興趣的內容。此外,我將分享最新的互聯網和技術資訊,以確保你與技術世界的最新發展保持聯系。我期待與你一起在技術之路上前進,一起探討技術世界的無限可能性。
📖 保持關注我的博客,讓我們共同追求技術卓越。