WebSocket
1. WebSocket 理解
1.1 WebSocket出現(xiàn)的原因
我們已經(jīng)有了 HTTP 協(xié)議世落,為什么還需要另一個(gè)協(xié)議?
因?yàn)?HTTP 協(xié)議有一個(gè)缺陷:通信只能由客戶端發(fā)起。
這種單向請求的特點(diǎn)曾棕,注定了如果服務(wù)器有連續(xù)的狀態(tài)變化陋守,客戶端要獲知就非常麻煩橘原。因此我們只能使用"輪詢", 也就是每隔一段時(shí)候,就發(fā)出一個(gè)詢問郁妈,了解服務(wù)器有沒有新的信息浑玛。最典型的場景就是聊天室。
<body>
<script>
//http是單向的噩咪,要東西才會(huì)給
setInterval(() => {
$.ajax({ //每隔10s向后臺(tái)請求數(shù)據(jù)顾彰,這就是輪詢
...
})
}, 10000)
</script>
</body>
然而輪詢的效率比較低, 非常 浪費(fèi)資源。輪詢的缺陷--后臺(tái)數(shù)據(jù)沒變胃碾,間隔響應(yīng)時(shí)間還會(huì)請求數(shù)據(jù)
因此為了更好地處理這種問題.WebSocket就 這樣被發(fā)明了
1.2 WebSocket 簡介
WebSocket 協(xié)議在2008年誕生涨享,2011年成為國際標(biāo)準(zhǔn)。所有瀏覽器都已經(jīng)支持了仆百。
它的最大特點(diǎn)就是厕隧,服務(wù)器可以主動(dòng)向客戶端推送信息,客戶端也可以主動(dòng)向服務(wù)器發(fā)送信息俄周,是真正的雙向平等對(duì)話吁讨,屬于服務(wù)器推送技術(shù)的一種。
特點(diǎn) :
建立在 TCP 協(xié)議之上峦朗,服務(wù)器端的實(shí)現(xiàn)比較容易建丧。
與 HTTP 協(xié)議有著良好的兼容性。默認(rèn)端口也是80和443波势,并且握手階段采用 HTTP 協(xié)議翎朱,因此握手時(shí)不容易屏蔽,能通過各種 HTTP 代理服務(wù)器
數(shù)據(jù)格式比較輕量艰亮,性能開銷小闭翩,通信高效。
可以發(fā)送文本迄埃,也可以發(fā)送二進(jìn)制數(shù)據(jù)疗韵。
沒有同源(跨域)限制,客戶端可以與任意服務(wù)器通信侄非。
協(xié)議標(biāo)識(shí)符是
ws
(如果加密蕉汪,則為wss
),服務(wù)器網(wǎng)址就是 URL逞怨。
ws://example.com:80/some/path
2. 客戶端API
2.1 WebSocket 構(gòu)造函數(shù)
WebSocket
對(duì)象提供了用于創(chuàng)建和管理 WebSocket 連接者疤,以及可以通過該連接發(fā)送和接收數(shù)據(jù)的 API。
對(duì)于我們來時(shí)通過WebSocket
構(gòu)造函數(shù)實(shí)例化WebSocket
對(duì)象, 在實(shí)例化過程中連接服務(wù)器
語法;
var aWebSocket = new WebSocket(url);
參數(shù):
- url: 要連接的URL叠赦;這應(yīng)該是WebSocket服務(wù)器將響應(yīng)的URL
例子:
let ws = new WebSocket('ws://www.localhost:8080')在關(guān)閉
4. CLOSED: 值為3, 表示連
2.2 webSocket.readyState
實(shí)例的readyState
屬性返回實(shí)例對(duì)象的當(dāng)前狀態(tài), 共有四種狀態(tài)
- CONNECTING: 值為0, 表示正在連接
- OPEN: 值為1, 表示連接成功, 可以通信了
- CLOSING: 值為2, 表示連接正接已經(jīng)關(guān)閉, 或者 打開連接失敗
switch (ws.readyState) {
case WebSocket.CONNECTING:
break;
case WebSocket.OPEN:
break;
case WebSocket.CLOSING:
break;
case WebSocket.CLOSED:
break;
default:
break;
}
3 webSocket.onopen
實(shí)例對(duì)象的onopen
方法, 用于執(zhí)行連接成功后的回調(diào)函數(shù)
// socket 連接成功后觸發(fā)事件
ws.onopen = function(){
// 像后端發(fā)送數(shù)據(jù)
ws.send("Hello Word")
}
如果需要指定多個(gè)回調(diào)函數(shù), 可以使用addEventListener
事件綁定
ws.addEventListener('open', function (ev) {
ws.send('Hello Server!');
});
ws.addEventListener('open', function (ev) {
ws.send('你好');
});
2.4 webSocket.send()
實(shí)例對(duì)象的send()
方法用于向服務(wù)器發(fā)送數(shù)據(jù)驹马。
發(fā)送文本數(shù)據(jù)
ws.send('hello word');
發(fā)送JSON數(shù)據(jù)
ws.send(JSON.stringify({name:'張三'}));
2.5 webSocket.onmessage
實(shí)例對(duì)象的onmessage
屬性,用于指定收到服務(wù)器數(shù)據(jù)后的回調(diào)函數(shù)。
ws.onmessage = function(ev) {
var data = ev.data;
// 處理數(shù)據(jù)
};
ws.addEventListener("message", function(ev) {
var data = ev.data;
// 處理數(shù)據(jù)
});
2.6 webSocket.onclose
實(shí)例對(duì)象的onclose
屬性, 指定連接關(guān)閉后的回調(diào)函數(shù)
注意: socket連接失敗也會(huì)觸發(fā)onclose
事件
ws.onclose = function(ev) {
console.log('關(guān)閉socket連接', ev)
};
ws.addEventListener("close", function(event) {
console.log('關(guān)閉socket連接', ev)
});
2.7 webSocket.onerror
實(shí)例對(duì)象的onerror
屬性糯累,用于指定報(bào)錯(cuò)時(shí)的回調(diào)函數(shù)算利。
socket.onerror = function(ev) {
// 處理錯(cuò)誤
};
socket.addEventListener("error", function(ev) {
// 處理錯(cuò)誤
});
3. WebSocket 封裝
class JsSocket {
/**
* 構(gòu)造方法, 進(jìn)行初始化
* @param {String} url socket 連接地址
* */
constructor(url) {
this.url = url;
this.lockReconnect = false;
this.reconnectCount = 0
}
/**
* 開始連接
* @params { Function } callback
* */
connect(callback) {
this.callback = callback;
this.disconnect()
try {
console.log("JsSocket開始連接...", this.url)
this.ws = new WebSocket(this.url)
this.ws.onopen = function(ev) {
console.log("JsSocket onpen...", ev)
this.reconnectTimes = 0
}
this.ws.onmessage = function(ev) {
console.log("JsSocket onmessage...", ev)
let data = ev.data;
if (!data) {
data = JSON.parse(data)
}
callback(data)
}
this.ws.onclose = (ev) => {
console.log("JsSocket onclose...", ev)
this.reconnect()
}
this.ws.onerror = (ev) => {
console.log("JsSocket onerror...", ev)
this.reconnect()
}
} catch (error) {
console.log("JsSocket", error)
}
}
/**
* 斷開連接
* */
disconnect() {
if (this.ws) {
this.ws.onclose = null;
this.ws.close()
this.ws = null
}
}
/**
* 發(fā)送消息
* @params { Object } data 消息內(nèi)容
* */
send(data) {
if (this.ws) {
this.ws.send(data)
}
}
/**
* 重新連接
* */
reconnect() {
if (this.lockReconnect || this.reconnectCount > 5) {
console.log('JsSocket 重連鎖定 或者 超過重連次數(shù)')
return;
}
this.lockReconnect = true;
setTimeout(() => {
this.reconnectCount++;
console.log('JsSocket 重連...')
this.connect(this.callback)
this.lockReconnect = false
}, 3000)
}
}