第一次看到
ws://
和wss://
時候,感覺好高階啊,還有這種協議。
Websocket 歷史
WebSocket是在2008年6月誕生的1。經由IEFT標準化後,2009年chrome 4第一個提供了該標準支援,並預設啟用。於2011年由IEFT標準化為RFC 6455。
現在的瀏覽器均已支援該標準。
Websocket 出現的背景
思考一下我們經常遇到的一種需求場景,要求在某個網頁下,網頁的內容可以實時更新。
這種情況下,最大眾化的方式就是輪詢介面了,即通過定時器,定時請求介面,獲取到最新的資訊後,將內容更新到頁面中,如下:
setInterval(() => {
queryAPI().then(() => update());
}, 1000);
但是我們知道,這種定時器的延時並不是很精確,而且加上介面的請求時延,實際時間可能不止程式碼中所預先設定的時間長度,所以這種實時更新是偽實時更新。
除此之外,還有一點可能會經常遇到,即,我們更新資訊並總是要更新整個頁面上所有可以看到的資訊,我們更關注一些經常變化的資訊,比如狀態,狀態的資訊可能大小隻有幾個位元組,但是我們輪詢介面拿到的資訊卻是這個頁面的所有資訊,大小自然不只幾個位元組,但是除狀態以外的資訊都可以視作是冗餘的。
我們實際只需要一個欄位,而且即使後端提供只返回狀態的介面,但實際在一個請求中還要計算ip報文頭的大小,依舊是很佔用頻寬的。
輪詢這種解決方案目前依舊是非常流行,最新的輪詢技術是Comet,這種技術雖然可以實現雙向通訊,但仍然需要反覆發出請求。而且在Comet中普遍採用的HTTP長連線也會消耗伺服器資源2。
Websocket通訊模式
瞭解網路的都知道,資料傳輸分為單工、半雙工、全雙工三種工作模式。
Websocket是基於TCP的,使用全雙工通訊模式的協議,他使得客戶端和服務端之間的資料交換變得更簡單。而且,作為一個工作在全雙工模式下的協議,服務端可以在建立連線後隨時向客戶端推送訊息。
由於協議是基於TCP的,所以websocket也是需要建連和關閉連線的,但要注意的是,一般在websocket的握手通常指的是:客戶端傳送一個http請求到服務端,服務端響應後標誌這個連結建立起來。而不是指tcp的三次握手。
另外在RFC 6455 1.1節「Background」中介紹:WebSocket通過HTTP埠的80和443進行工作,並支援HTTP代理和中介。
原文:it is designed to work over HTTP ports 80 and 443 as well as to support HTTP proxies and intermediaries
為了實現和HTTP的相容性,WebSocket握手使用HTTP的Upgrade頭將HTTP協議轉換成Websocket協議。
作為一種協議,websocket自然也是有其用於協議控制的頭部資訊的,但是相對於HTTP請求每次都要帶上完整的頭部資訊,傳輸資料時,websocket資料包的頭部資訊就相對較小,從而降低了控制開銷。
相對於前文所提到的輪詢介面,websocket可以做到服務端直接向客戶端傳輸資料,省去了客戶端發起請求的步驟,同時沒有間隔時間,只要服務端內容變化,就可以告知客戶端,實時性上有了很大的提高。
Websocket 使用
WebSocket使用十分簡單,只需要關注以下API:
WebSocket(url[, protocol])
建構函式
使用 new WebSocket(xxx)
建立物件時,會同時建立與伺服器的連線
WebSocket.onopen
,WebSocket.onclose
分別對應連線成功、失敗時的回撥,這裡可以做一些初始化、銷燬的工作
WebSocket.onmessage
實際處理資料是用的該函式
在資料處理完成後,需要移除回撥函式,不然可能會影響到其他地方的處理
const ws = new WebSocket('ws://sdf.com');
function handleData(evt) {
// handle server data.
}
ws.onmessage = handleData;
ws.addEventListener('message', handleData);
WebSocket.send
主動向服務端傳送訊息,可以通過send和onmessage進行資料互動,如:
ws.send('list');
ws.onmessage = evt => {
const data = evt.data;
if (data === 'hello') {
console.log('world');
return ;
}
try {
const obj = JSON.parse(data);
switch (obj.type) {
case 'list':
// do something.
}
} catch (ex) {}
}
WebSocket.close
關閉連線
Node服務端的實現,這個就參考相關的庫吧,比較複雜。
衍生知識
http協議至今,主要經歷了三個版本。
-
http1.0 短連線,單工通訊
- http/1.0預設的模型是短連線,每個HTTP請求都由他自己獨立完成,下圖左1,可以看到每一個http請求都對應了一個建立連線關閉連線的階段,每一個請求都有TCP握手和揮手的階段。
- 在這個模型下,想要做到實時更新頁面資料,只能考慮輪詢。
-
http1.1 支援長連結,半雙工通訊
- 1.0之後的版本,1.1會讓某個連線保持一定的時間,在這段時間裡重複傳送一系列請求(下圖左2),就是保活。
- http2.0 支援多路複用,全雙工通訊
參考文獻
[1] [whatwg] TCPConnection feedback
[2] wiki
[3] RFC 6455
[4] Websocket教程
[5] HTTP1.x連線管理