點選下方「前端開發愛好者」,選擇「設為星標」
第一時間關註技術幹貨!
前言
因新部門需求有一個後台管理需要一個右上角的即時的訊息提醒功能,第一時間想到的就是使用WebSocket建立即時通訊了,之前沒整過,於是只能學習了。和原部門相比現在太忙了,快樂的日子一去不復返了。經典的加量不加薪啊!!!
一.WebSocket 基本概念
1.WebSocket是什麽?
WebSocket 是基於 TCP 的一種新的套用層網路協定。它提供了一個全雙工的通道,允許伺服器和客戶端之間即時雙向通訊。因此,在 WebSocket 中,瀏覽器和伺服器只需要完成一次握手,兩者之間就直接可以建立永續性的連線,並進行雙向數據傳輸,客戶端和伺服器之間的數據交換變得更加簡單。WebSocket
2.與 HTTP 協定的區別
與 HTTP 協定相比,WebSocket 具有以下優點:
更高的即時效能:WebSocket 允許伺服器和客戶端之間即時雙向通訊,從而提高了即時通訊場景中的效能。
更少的網路開銷:HTTP 請求和響應之間需要額外的數據傳輸,而 WebSocket 透過在同一個連線上雙向通訊,減少了網路開銷。
更靈活的通訊方式:HTTP 請求和響應通常是一一對應的,而 WebSocket 允許伺服器和客戶端之間以多種方式進行通訊,例如訊息 Push、事件推播等。
更簡潔的 API:WebSocket 提供了簡潔的 API,使得客戶端開發人員可以更輕松地進行即時通訊。
當然肯定有缺點的:
不支持無連線: WebSocket 是一種持久化的協定,這意味著連線不會在一次請求之後立即斷開。這是有利的,因為它消除了建立連線的開銷,但是也可能導致一些資源泄漏的問題。
不支持廣泛: WebSocket 是 HTML5 中的一種標準協定,雖然現代瀏覽器都支持,但是一些舊的瀏覽器可能不支持 WebSocket。
需要特殊的伺服器支持: WebSocket 需要伺服端支持,只有特定的伺服器才能夠實作 WebSocket 協定。這可能會增加系統的復雜性和部署的難度。
數據流不相容: WebSocket 的數據流格式與 HTTP 不同,這意味著在不同的網路環境下,WebSocket 的表現可能會有所不同。
3.WebSocket工作原理
1. 握手階段
WebSocket在建立連線時需要進行握手階段。握手階段包括以下幾個步驟:
客戶端向伺服端發送請求,請求建立WebSocket連線。請求中包含一個Sec-WebSocket-Key參數,用於生成WebSocket的隨機金鑰。
伺服端接收到請求後,生成一個隨機金鑰,並使用隨機金鑰生成一個新的Sec-WebSocket-Accept參數。
客戶端接收到伺服端發送的新的Sec-WebSocket-Accept參數後,使用原來的隨機金鑰和新的Sec-WebSocket-Accept參數共同生成一個新的Sec-WebSocket-Key參數,用於加密數據傳輸。
客戶端將新的Sec-WebSocket-Key參數發送給伺服端,伺服端接收到後,使用該參數加密數據傳輸。
2. 數據傳輸階段
建立連線後,客戶端和伺服端就可以透過WebSocket進行即時雙向通訊。數據傳輸階段包括以下幾個步驟:
客戶端向伺服端發送數據,伺服端收到數據後將其轉發給其他客戶端。
伺服端向客戶端發送數據,客戶端收到數據後進行處理。
雙方如何進行相互傳輸數據的 具體的數據格式是怎麽樣的呢?WebSocket 的每條訊息可能會被切分成多個數據幀(最小單位)。發送端會將訊息切割成多個幀發送給接收端,接收端接收訊息幀,並將關聯的幀重新組裝成完整的訊息。
發送方 -> 接收方:ping。
接收方 -> 發送方:pong。
ping 、pong 的操作,對應的是 WebSocket 的兩個控制幀
3. 關閉階段
當不再需要WebSocket連線時,需要進行關閉階段。關閉階段包括以下幾個步驟:
客戶端向伺服端發送關閉請求,請求中包含一個WebSocket的隨機金鑰。
伺服端接收到關閉請求後,向客戶端發送關閉響應,關閉響應中包含伺服端生成的隨機金鑰。
客戶端收到關閉響應後,關閉WebSocket連線。
總的來說,WebSocket透過握手階段、數據傳輸階段和關閉階段實作了伺服器和客戶端之間的即時雙向通訊。
二.WebSocket 數據幀結構和控制幀結構。
1. 數據幀結構
WebSocket 數據幀主要包括兩個部份:幀頭和有效載荷。以下是 WebSocket 數據幀結構的簡要介紹:
幀頭:幀頭包括四個部份:fin、rsv1、rsv2、rsv3、opcode、masked 和 payload_length。其中,fin 表示數據幀的結束標誌,rsv1、rsv2、rsv3 表示保留欄位,opcode 表示數據幀的型別,masked 表示是否進行掩碼處理,payload_length 表示有效載荷的長度。
有效載荷:有效載荷是數據幀中實際的數據部份,它由客戶端和伺服端進行數據傳輸。
2. 控制幀結構
除了數據幀之外,WebSocket 協定還包括一些控制幀,主要包括 Ping、Pong 和 Close 幀。以下是 WebSocket 控制幀結構的簡要介紹:
Ping 幀:Ping 幀用於測試客戶端和伺服端之間的連線狀態,客戶端向伺服端發送 Ping 幀,伺服端收到後需要向客戶端發送 Pong 幀進行響應。
Pong 幀:Pong 幀用於響應客戶端的 Ping 幀,它用於測試客戶端和伺服端之間的連線狀態。
Close 幀:Close 幀用於關閉客戶端和伺服端之間的連線,它包括四個部份:fin、rsv1、rsv2、rsv3、opcode、masked 和 payload_length。其中,opcode 的值為 8,表示 Close 幀。
三. JavaScript 中 WebSocket 物件的內容和方法,以及如何建立和連線 WebSocket。
WebSocket 物件的內容和方法:
WebSocket
物件:WebSocket 物件表示一個新的 WebSocket 連線。WebSocket.onopen
事件處理常式:當 WebSocket 連線開啟時觸發。WebSocket.onmessage
事件處理常式:當接收到來自 WebSocket 的訊息時觸發。WebSocket.onerror
事件處理常式:當 WebSocket 發生錯誤時觸發。WebSocket.onclose
事件處理常式:當 WebSocket 連線關閉時觸發。WebSocket.send
方法:向 WebSocket 發送數據。WebSocket.close
方法:關閉 WebSocket 連線。
建立和連線 WebSocket:
建立 WebSocket 物件:
var socket = new WebSocket('ws://example.com');
其中,
ws://example.com
是 WebSocket 的 URL,表示要連線的伺服器。
連線 WebSocket:
使用
WebSocket.onopen
事件處理常式檢查 WebSocket 是否成功連線。
socket.onopen = function() {
console.log('WebSocket connected');
};
接收來自 WebSocket 的訊息:
使用
WebSocket.onmessage
事件處理常式接收來自 WebSocket 的訊息。
socket.onmessage = function(event) {
console.log('WebSocket message:', event.data);
};
向 WebSocket 發送訊息:
使用
WebSocket.send
方法向 WebSocket 發送訊息。
socket.send('Hello, WebSocket!');
關閉 WebSocket:
當需要關閉 WebSocket 時,使用
WebSocket.close
方法。
socket.close();
註意:在 WebSocket 連線成功開啟和關閉時,會分別觸發
WebSocket.onopen
和
WebSocket.onclose
事件。在接收到來自 WebSocket 的訊息時,會觸發
WebSocket.onmessage
事件。當 WebSocket 發生錯誤時,會觸發
WebSocket.onerror
事件。
四.webSocket簡單範例
以下是一個簡單的 WebSocket 編程範例,透過 WebSocket 向伺服器發送數據,並接收伺服器返回的數據:
首先,建立一個 HTML 檔,添加一個按鈕和一個用於顯示訊息的文字域:
<!DOCTYPE html>
<html>
<head>
<metacharset="UTF-8">
<title>WebSocket 範例</title>
</head>
<body>
<buttonid="sendBtn">發送訊息</button>
<textareaid="messageBox"readonly></textarea>
<scriptsrc="main.js"></script>
</body>
</html>
2. 接下來,建立一個 JavaScript 檔(例如 main.js),並在其中編寫以下程式碼:
// 獲取按鈕和文字域元素
const sendBtn = document.getElementById('sendBtn');
const messageBox = document.getElementById('messageBox');
// 建立 WebSocket 物件
const socket = new WebSocket('ws://echo.websocket.org'); // 使用一個 WebSocket 伺服器進行測試
// 設定 WebSocket 連線開啟時的回呼函式
socket.onopen = function() {
console.log('WebSocket 連線已開啟');
};
// 設定 WebSocket 接收到訊息時的回呼函式
socket.onmessage = function(event) {
console.log('WebSocket 接收到訊息:', event.data);
messageBox.value += event.data + '\n';
};
// 設定 WebSocket 發生錯誤時的回呼函式
socket.onerror = function() {
console.log('WebSocket 發生錯誤');
};
// 設定 WebSocket 連線關閉時的回呼函式
socket.onclose = function() {
console.log('WebSocket 連線已關閉');
};
// 點選按鈕時發送訊息
sendBtn.onclick = function() {
const message = 'Hello, WebSocket!';
socket.send(message);
messageBox.value += '發送訊息: ' + message + '\n';
};
五.webSocket套用場景
即時通訊:WebSocket 非常適合即時通訊場景,例如聊天室、線上遊戲、即時數據傳輸等。透過 WebSocket,客戶端和伺服器之間可以即時通訊,無需依賴輪詢,從而提高通訊效率和減少網路延遲。
監控數據傳輸:WebSocket 可以在監控系統中實作即時數據傳輸,例如透過 WebSocket,客戶端可以即時接收和處理監控數據,而無需等待輪詢數據。
自動化控制:WebSocket 可以在自動化系統中實作遠端控制,例如透過 WebSocket,客戶端可以遠端控制裝置或系統,而無需直接操作。
數據分析:WebSocket 可以在數據分析場景中實作即時數據傳輸和處理,例如透過 WebSocket,客戶端可以即時接收和處理數據,而無需等待數據儲存和分析。
人工智慧:WebSocket 可以在人工智慧場景中實作即時數據傳輸和處理,例如透過 WebSocket,客戶端可以即時接收和處理數據,而無需等待數據處理和分析。
六.WebSocket 錯誤處理
WebSocket 的錯誤處理
WebSocket is not supported
:當瀏覽器不支持 WebSocket 時,會出現此錯誤。解決方法是在瀏覽器相容性列表中檢查是否支持 WebSocket。WebSocket connection closed
:當 WebSocket 連線被關閉時,會出現此錯誤。解決方法是在WebSocket.onclose
事件處理常式中進行錯誤處理。WebSocket error
:當 WebSocket 發生錯誤時,會出現此錯誤。解決方法是在WebSocket.onerror
事件處理常式中進行錯誤處理。WebSocket timeout
:當 WebSocket 連線超時時,會出現此錯誤。解決方法是在WebSocket.ontimeout
事件處理常式中進行錯誤處理。WebSocket handshake error
:當 WebSocket 握手失敗時,會出現此錯誤。解決方法是在WebSocket.onerror
事件處理常式中進行錯誤處理。WebSocket closed by server
:當 WebSocket 連線被伺服器關閉時,會出現此錯誤。解決方法是在WebSocket.onclose
事件處理常式中進行錯誤處理。WebSocket closed by protocol
:當 WebSocket 連線被協定錯誤關閉時,會出現此錯誤。解決方法是在WebSocket.onclose
事件處理常式中進行錯誤處理。WebSocket closed by network
:當 WebSocket 連線被網路錯誤關閉時,會出現此錯誤。解決方法是在WebSocket.onclose
事件處理常式中進行錯誤處理。WebSocket closed by server
:當 WebSocket 連線被伺服器錯誤關閉時,會出現此錯誤。解決方法是在WebSocket.onclose
事件處理常式中進行錯誤處理。
透過為
WebSocket
物件的
onclose
、
onerror
和
ontimeout
事件添加處理常式,可以及時捕獲和處理 WebSocket 錯誤,從而確保程式的穩定性和可靠性。
七.利用單例模式建立完整的wesocket連線
classwebSocket class{
constructor(thatVue) {
this.lockReconnect = false;
this.localUrl = process.env.NODE_ENV === 'production' ? 你的websocket生產地址' : '測試地址';
this.globalCallback = null;
this.userClose = false;
this.createWebSocket();
this.webSocketState = false
this.thatVue = thatVue
}
createWebSocket() {
let that = this;
// console.log('開始建立websocket新的例項', new Date().toLocaleString())
if( typeof(WebSocket) != "function" ) {
alert("您的瀏覽器不支持Websocket通訊協定,請更換瀏覽器為Chrome或者Firefox再次使用!")
}
try {
that.ws = new WebSocket(that.localUrl);
that.initEventHandle();
that.startHeartBeat()
} catch (e) {
that.reconnect();
}
}
//初始化
initEventHandle() {
let that = this;
// //連線成功建立後響應
that.ws.onopen = function() {
console.log("連線成功");
};
//連線關閉後響應
that.ws.onclose = function() {
// console.log('websocket連線斷開', new Date().toLocaleString())
if (!that.userClose) {
that.reconnect(); //重連
}
};
that.ws.onerror = function() {
// console.log('websocket連線發生錯誤', new Date().toLocaleString())
if (!that.userClose) {
that.reconnect(); //重連
}
};
that.ws.onmessage = function(event) {
that.getWebSocketMsg(that.globalCallback);
// console.log('socket server return'+ event.data);
};
}
startHeartBeat () {
// console.log('心跳開始建立', new Date().toLocaleString())
setTimeout(() => {
let params = {
request: 'ping',
}
this.webSocketSendMsg(JSON.stringify(params))
this.waitingServer()
}, 30000)
}
//延時等待伺服端響應,透過webSocketState判斷是否連線成功
waitingServer () {
this.webSocketState = false//線上狀態
setTimeout(() => {
if(this.webSocketState) {
this.startHeartBeat()
return
}
// console.log('心跳無響應,已斷線', new Date().toLocaleString())
try {
this.closeSocket()
} catch(e) {
console.log('連線已關閉,無需關閉', new Date().toLocaleString())
}
this.reconnect()
//重連操作
}, 5000)
}
reconnect() {
let that = this;
if (that.lockReconnect) return;
that.lockReconnect = true; //沒連線上會一直重連,設定延遲避免請求過多
setTimeout(function() {
that.createWebSocket();
that.thatVue.openSuccess(that) //重連之後做一些事情
that.thatVue.getSocketMsg(that)
that.lockReconnect = false;
}, 15000);
}
webSocketSendMsg(msg) {
this.ws.send(msg);
}
getWebSocketMsg(callback) {
this.ws.onmessage = ev => {
callback && callback(ev);
};
}
onopenSuccess(callback) {
this.ws.onopen = () => {
// console.log("連線成功", new Date().toLocaleString())
callback && callback()
}
}
closeSocket() {
let that = this;
if (that.ws) {
that.userClose = true;
that.ws.close();
}
}
}
export default webSocket class;
作者:耀耀切克鬧灬 https://juejin.cn/post/7309687967063818292
結語
我是林三心
一個待過 小型toG型外包公司、大型外包公司、小公司、潛力型創業公司、大公司 的作死型前端選手;
一個偏前端的全幹工程師;
一個不正經的金塊作者;
逗比的B站up主;
不帥的小紅書博主;
喜歡打鐵的籃球菜鳥;
喜歡歷史的乏味少年;
喜歡rap的五音不全弱雞如果你想一起學習前端,一起摸魚,一起研究簡歷最佳化,一起研究面試進步,一起交流歷史音樂籃球rap,可以來俺的摸魚學習群哈哈,點這個,有7000多名前端小夥伴在等著一起學習哦 --> 摸魚沸點
廣州的兄弟可以約飯哦,或者約球~我負責打鐵,你負責進球,謝謝~