1. 什么是WebSocket?
WebSocket是一種協(xié)議,用于在Web應用程序和服務器之間建立實時、雙向的通信連接。
它通過一個單一的TCP連接提供了持久化連接,這使得Web應用程序可以更加實時地傳遞數(shù)據(jù)。
WebSocket協(xié)議最初由W3C開發(fā),并于2011年成為標準。
2. WebSocket的優(yōu)勢和劣勢
WebSocket的優(yōu)勢包括:
實時性: 由于WebSocket的持久化連接,它可以實現(xiàn)實時的數(shù)據(jù)傳輸,避免了Web應用程序需要不斷地發(fā)送請求以獲取最新數(shù)據(jù)的情況。
雙向通信: WebSocket協(xié)議支持雙向通信,這意味著服務器可以主動向客戶端發(fā)送數(shù)據(jù),而不需要客戶端發(fā)送請求。
減少網(wǎng)絡負載: 由于WebSocket的持久化連接,它可以減少HTTP請求的數(shù)量,從而減少了網(wǎng)絡負載。
WebSocket的劣勢包括:
需要瀏覽器和服務器都支持: WebSocket是一種相對新的技術,需要瀏覽器和服務器都支持。一些舊的瀏覽器和服務器可能不支持WebSocket。
需要額外的開銷: WebSocket需要在服務器上維護長時間的連接,這需要額外的開銷,包括內(nèi)存和CPU。
安全問題: 由于WebSocket允許服務器主動向客戶端發(fā)送數(shù)據(jù),可能會存在安全問題。服務器必須保證只向合法的客戶端發(fā)送數(shù)據(jù)。
3. WebSocket的協(xié)議
WebSocket 協(xié)議是一種基于TCP的協(xié)議,用于在客戶端和服務器之間建立持久連接,并且可以在這個連接上實時地交換數(shù)據(jù)。WebSocket協(xié)議有自己的握手協(xié)議,用于建立連接,也有自己的數(shù)據(jù)傳輸格式。
當客戶端發(fā)送一個 WebSocket 請求時,服務器將發(fā)送一個協(xié)議響應以確認請求。在握手期間,客戶端和服務器將協(xié)商使用的協(xié)議版本、支持的子協(xié)議、支持的擴展選項等。一旦握手完成,連接將保持打開狀態(tài),客戶端和服務器就可以在連接上實時地傳遞數(shù)據(jù)。
WebSocket 協(xié)議使用的是雙向數(shù)據(jù)傳輸,即客戶端和服務器都可以在任意時間向?qū)Ψ桨l(fā)送數(shù)據(jù),而不需要等待對方的請求。它支持二進制數(shù)據(jù)和文本數(shù)據(jù),可以自由地在它們之間進行轉換。
總之,WebSocket協(xié)議是一種可靠的、高效的、雙向的、持久的通信協(xié)議,它適用于需要實時通信的Web應用程序,如在線游戲、實時聊天、儀表盤、股票行情等等。
4. WebSocket的生命周期
WebSocket 生命周期描述了 WebSocket 連接從創(chuàng)建到關閉的過程。一個 WebSocket 連接包含以下四個主要階段:
連接建立階段(Connection Establishment): 在這個階段,客戶端和服務器之間的 WebSocket 連接被建立。客戶端發(fā)送一個 WebSocket 握手請求,服務器響應一個握手響應,然后連接就被建立了。
連接開放階段(Connection Open): 在這個階段,WebSocket 連接已經(jīng)建立并開放,客戶端和服務器可以在連接上互相發(fā)送數(shù)據(jù)。
連接關閉階段(Connection Closing): 在這個階段,一個 WebSocket 連接即將被關閉。它可以被客戶端或服務器發(fā)起,通過發(fā)送一個關閉幀來關閉連接。
連接關閉完成階段(Connection Closed): 在這個階段,WebSocket 連接已經(jīng)完全關閉。客戶端和服務器之間的任何交互都將無效。
“需要注意的是,WebSocket 連接在任何時候都可能關閉,例如網(wǎng)絡故障、服務器崩潰等情況都可能導致連接關閉。因此,需要及時處理 WebSocket 連接關閉的事件,以確保應用程序的可靠性和穩(wěn)定性。
5.WebSocket的性能
1 與傳統(tǒng)的HTTP請求/響應模型比較
- 雙向通信性能更好: WebSocket協(xié)議使用單一的TCP連接,允許客戶端和服務器在同一個連接上進行雙向通信。這種實時的雙向通信可以更快地傳輸數(shù)據(jù),而不需要建立多個HTTP請求/響應連接。
- 更小的網(wǎng)絡流量: 與HTTP相比,WebSocket協(xié)議需要更少的網(wǎng)絡流量來維護連接,因為它不需要在每個請求/響應交換中發(fā)送頭部信息。
- 更低的延遲: WebSocket協(xié)議允許服務器主動向客戶端推送消息,而不需要客戶端先發(fā)送請求。這種實時通信可以減少響應延遲,并提高應用程序的性能。
- 更好的服務器資源管理: 由于WebSocket連接可以保持活動狀態(tài),服務器可以更好地管理客戶端連接,減少服務器開銷和處理時間。
WebSocket協(xié)議的性能比傳統(tǒng)的HTTP請求/響應模型更好,特別是在實時通信和低延遲方面。WebSocket協(xié)議適用于需要實時通信和實時數(shù)據(jù)更新的應用程序,如在線聊天、多人游戲、實時監(jiān)控等。
2 優(yōu)化WebSocket的性能
- 減少消息大小: WebSocket 傳輸?shù)臄?shù)據(jù)大小對性能有很大影響。盡量減少消息的大小,可以降低網(wǎng)絡帶寬和服務器負載。例如,可以使用二進制傳輸協(xié)議來代替文本傳輸,或使用壓縮算法對消息進行壓縮。
- 使用CDN加速: 使用 CDN可以將靜態(tài)資源緩存到離用戶更近的節(jié)點上,提高傳輸速度和性能。CDN 可以緩存 Websocket 的初始握手請求,避免不必要的網(wǎng)絡延遲。
- 使用[負載均衡]** WebSocket 服務可以使用負載均衡來分配并平衡多個服務器的負載。負載均衡可以避免單個服務器被過載,并提高整個服務的可伸縮性。
- 優(yōu)化服務端代碼: WebSocket 服務端代碼的性能也是關鍵因素。使用高效的框架和算法,避免使用過多的內(nèi)存和 CPU 資源,可以提高服務端的性能和響應速度。
- 避免網(wǎng)絡阻塞: WebSocket 的性能也會受到網(wǎng)絡阻塞的影響。當有太多的連接同時請求數(shù)據(jù)時,服務器的性能會下降。使用合適的線程池和異步 IO 操作可以避免網(wǎng)絡阻塞,提高 WebSocket 服務的并發(fā)性能。
6. WebSocket的擴展應用和未來發(fā)展方向
- 更加完善的標準規(guī)范: WebSocket 標準規(guī)范還有很多可以優(yōu)化的地方,未來可能會繼續(xù)完善 WebSocket 的標準規(guī)范,以適應更加復雜的應用場景。
- 更加安全的通信方式: 由于 WebSocket 的開放性,使得它可能會受到一些安全威脅,未來可能會通過加密、[身份驗證] 等方式來增強 WebSocket 的安全性。
- 更好的兼容性: WebSocket 協(xié)議需要在 HTTP 協(xié)議的基礎上建立連接,因此可能會遇到兼容性問題,未來可能會通過技術手段來解決這些問題。
- 更好的性能和可伸縮性: WebSocket 協(xié)議的性能和可伸縮性對于復雜的應用場景非常關鍵,未來可能會通過技術手段來進一步提高 WebSocket 的性能和可伸縮性。
使用
<global-web-socket :uri="`/websocket/url1`" @getData="getData" />
// socket傳回數(shù)據(jù)
getData(data) {
try {
console.log(data,'success');
} catch (err) {
console.log(err, 'socket');
}
},
組件
<template>
</template>
<script>
import store from '@/store';
export default {
props: {
uri: {
type: String
}
},
data() {
return {
// webSocket實例
webSocket: null,
// 重連鎖,避免多次重連
lockReconnect: false,
// 最大重連次數(shù), -1 標識無限重連
maxReconnect: 8,
// 重連嘗試次數(shù)
reconnectTime: 0,
heartbeat: {
// 心跳間隔時間
interval: 30 * 1000,
// 響應超時時間
timeout: 10 * 1000,
// 延時發(fā)送心跳的定時器
pingTimeoutObj: null,
// 接收心跳響應的定時器
pongTimeoutObj: null,
// 心跳請求信息
pingMessage: JSON.stringify({ type: 'ping' })
}
};
},
computed: {},
created() {
this.initWebSocket();
},
destroyed: function() {
this.webSocket.close();
this.clearTimeoutObj(this.heartbeat);
},
methods: {
/**
* 初始化 weoSocket
*/
initWebSocket() {
// ws地址
const host = window.location.host;
const wsUri = `ws://${host}${this.uri}?access_token=token`;
// 建立連接
this.webSocket = new WebSocket(wsUri);
// 連接成功
this.webSocket.onopen = this.onOpen;
// 連接錯誤
this.webSocket.onerror = this.onError;
// 接收信息
this.webSocket.onmessage = this.onMessage;
// 連接關閉
this.webSocket.onclose = this.onClose;
},
/**
* 重新連接
*/
reconnect() {
if (!this.token) {
return;
}
if (this.lockReconnect || (this.maxReconnect !== -1 && this.reconnectTime > this.maxReconnect)) {
return;
}
this.lockReconnect = true;
setTimeout(() => {
this.reconnectTime++;
// 建立新連接
this.initWebSocket();
this.lockReconnect = false;
}, 5000);
},
/**
* 清空定時器
*/
clearTimeoutObj: function(heartbeat) {
heartbeat.pingTimeoutObj && clearTimeout(heartbeat.pingTimeoutObj);
heartbeat.pongTimeoutObj && clearTimeout(heartbeat.pongTimeoutObj);
},
/**
* 開啟心跳
*/
startHeartbeat() {
const webSocket = this.webSocket;
const heartbeat = this.heartbeat;
// 清空定時器
this.clearTimeoutObj(heartbeat);
// 延時發(fā)送下一次心跳
heartbeat.pingTimeoutObj = setTimeout(() => {
// 如果連接正常
if (webSocket.readyState === 1) {
// 這里發(fā)送一個心跳,后端收到后,返回一個心跳消息,
webSocket.send(heartbeat.pingMessage);
// 心跳發(fā)送后,如果服務器超時未響應則斷開,如果響應了會被重置心跳定時器
heartbeat.pongTimeoutObj = setTimeout(() => {
webSocket.close();
}, heartbeat.timeout);
} else {
// 否則重連
this.reconnect();
}
}, heartbeat.interval);
},
/**
* 連接成功事件
*/
onOpen() {
console.log('WebSocket connection success');
// 開啟心跳
this.startHeartbeat();
this.reconnectTime = 0;
},
/**
* 連接失敗事件
* @param e
*/
onError(e) {
// 錯誤
console.log(`WebSocket connection error:${e.code} ${e.reason} ${e.wasClean}`);
// 重連
this.reconnect();
},
/**
* 連接關閉事件
* @param e
*/
onClose(e) {
// 關閉
console.log(`WebSocket connection closed:${e.code} ${e.reason} ${e.wasClean}`);
// 重連
this.reconnect();
},
/**
* 接收服務器推送的信息
* @param msgEvent
*/
onMessage(msgEvent) {
// 收到服務器信息,心跳重置并發(fā)送
this.startHeartbeat();
const text = msgEvent.data;
if (text.indexOf('pong') > 0) {
return;
}
this.$notify.warning({
title: '消息通知',
dangerouslyUseHTMLString: true,
message: text + '消息通知',
offset: 60
});
},
/**
* 數(shù)據(jù)發(fā)送
* @param msg
*/
send(msg) {
// 數(shù)據(jù)發(fā)送
this.webSocket.send(msg);
}
}
};
</script>