H5數據實時方案「WebSocket」

數據實時:即數據庫中的數據得到更新,頁面立刻就想得到更新并展示最新的數據狀態。通常使用在大數據可視化分析,運營數據監控等場景。

# 數據實時方案

Web想要更新頁面,通常都是客戶端發起Http異步請求,主動向服務端索取數據,方案有:
(1)Ajax輪詢,又稱 Ajax短連接:即啟動一個定時器隔一定時間(如1s)發送一個請求,服務端收到請求無論如何都直接返回當前數據庫狀態數據。缺點是實時性不夠,產生很多不必要的請求。可用于刷新頻率不是很高的場景。
(2)Ajax長連接:客戶端發起Http請求,并設置一個長超時時間,服務端收到請求后,檢查數據庫如果沒有更新則阻塞請求,直到有更新或超時為止。客戶端每次收到響應后,立即再發一個請求,Comet就是這種方式。缺點是服務器的處理線程長時間掛起,極大浪費資源,且網絡鏈路可能被網關關閉,需要如ping數據來維持鏈接。

? 以上兩種機制都治標不治本,是否能有一種機制,由服務端自己檢測數據狀態,有更新主動告知客戶端。好在,HTML5推出了 WebSocket 協議,解決了這個問題
?

# WebSocket是什么

? WebSocket(以下簡稱 ws)是HTML5提供的一種在單個 TCP 連接上進行全雙工通訊的網絡技術,目的是在瀏覽器和服務器之間建立一個不受限的雙向通信的通道,讓雙方都可以主動給對方發消息
? 雖說ws是H5下新的協議,但其實也不是全新的。它屬于應用層協議,復用了HTTP的握手通道。ws協議與HTTP協議都是基于TCP的,因此都是可靠的協議。ws客戶端和服務器只需要做一個握手的動作,兩者之間就形成了一條快速通道。在建立握手連接時,數據是通過http進行傳輸的,但建立之后,真正的數據傳輸階段就不需要http參與了

圖片來自菜鳥教程

?

# WebSocket的優點

? ws協議相比于HTTP協議,它具有以下優勢:

  • 全雙工通信能力:支持客戶端和服務端主動給對方發送消息
  • 高實時性:Ajax輪詢只是不斷的請求,而服務端檢測到更新主動推送才是真正意義上的實時。
  • 高效節能:HTTP協議請求一般都會有較長的頭部,而需要實時更新的數據可能就一點點,這就造成了帶寬很多不必要的消耗。而ws協議控制數據包的頭部比較小,一般只有十個字節左右。
  • 支持擴展: ws協議定義了擴展,用戶可以擴展協議,或實現自定義子協議。
  • 沒有跨域限制:不是xhr請求,沒有同源策略的限制
    ?

# WebSocket的第一次握手

? 雖說ws支持雙向通訊能力,但請求必須是由客戶發起。由于發起時是一個http握手,因此格式如下

GET ws://localhost:3000/ws/chat HTTP/1.1
Host: localhost
Upgrade: websocket
Connection: Upgrade
Origin: http://localhost:3000
Sec-WebSocket-Key: w4v7O6xFTi36lq3RNcgctw== // 客戶端隨機串
Sec-WebSocket-Version: 13

值得注意的是:
(1)其只能發GET請求,且不再是 http://... 而是換成了 ws://... 開頭的地址
(2)請求頭Upgrade: websocketConnection: Upgrade表示該連接將要被升級為WebSocket連接;
(3)Sec-WebSocket-Key 標識連接的Key串(下方有更多解釋)
(4)Sec-WebSocket-Version 指定了WebSocket的協議版本。

如果服務器識別key正確,會接收這個請求,就會響應如下:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: Oy4NRAQ13jhfONC7bP8dTKb4PTU=  // 服務端隨機串

服務端Accept串是根據客戶端隨機串計算出來的,計算規則為:(1)與固定串拼接,(2)執行sha1算法,(3)轉為base64字符串。這對Key/Accept需ws客戶端和服務端提前約定,目的是為了避免非法ws請求等一些常見的意外情況。并不能確保數據安全性,畢竟算法公開且簡單。公式如下:

toBase64( sha1( Sec-WebSocket-Key + 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 ) )

響應碼101表示將切換協議,更改后的協議就是Upgrade: websocket指定的WebSocket協議。當連接建立成功后,雙方就可以自由通訊消息了。消息一般分兩種:(1)文本,(2)二進制數據。開發中會使用JSON文本數據比較直觀。
?

# ws為什么能實現全雙工通訊

? 前文多次遇到 全雙工通信 字眼,意思就是客戶端和服務端能隨時給對方發送消息。好像理解了但又朦朦朧朧。這里解釋一下:

  • 單工: 數據傳輸只支持在一個方向上的傳輸,同時只能有一方發送或接收消息。
  • 半雙工:數據允許在兩個方向上傳輸,但任一時刻,只允許有一方在傳輸,是一種切換方向的單工通信
  • 全雙工:任何時刻都允許兩個方向進行數據傳輸,不受對方限制。

? HTTP 和WebSocket 都是基于TCP傳輸協議的,其實TCP本身是支持全雙工通訊的,而HTTP協議的請求,因為其應答機制限制了全雙工通信。當第一次握手完成后,協議由HTTP切換成了WebSocket,ws連接建立,其實只是簡單規定了下:后續通訊不再使用http協議,雙發可以互相發送數據了。

http、WebSocket及TCP的關系(圖片摘自網絡)

?

# 安全的WebSocket通訊

? 與 HTTPS 類似,安全的ws連接使用的是wss://...開頭的請求,它首先會通過https創建安全的連接,升級協議后,底層通信依然走的 SSL/TLS 協議
?

# 連接保持 - 心跳

? WebSocket為了保持客戶端與服務端的實時雙向通訊,需保持TCP通道鏈接沒有斷開。然而長時間沒有數據往來的連接,會浪費一些連接資源,網絡鏈路同樣可能被網關關閉,畢竟網關不是我們能控制的。因此鏈路鏈接就需要提示說明還在使用周期內,這個提示就是心跳來實現的。

  • 發送方 --> 接收方: ping
  • 接收方 --> 發送方: pong
    舉例,ws服務端向客戶端發送ping,代碼如下
ws.ping('', false, true)

?

$ WebSocket API

? 理解了WebSocket的概念及相應的特征后,來看看怎么上手編寫

# 創建WebSocket實例

? ws提供了WebSocket(url[, protocals])構造函數來返回實例化ws對象。參數一表示要連接的URL,參數二表示可接受的子協議。

let socket= new WebSocket('http://localhost:8080')

? 執行以上代碼,瀏覽器就開始嘗試創建連接,與 xhr 的readystatechange 類似的是,ws連接也有一個表示當前狀態的屬性readyState

# 連接狀態-readyState 只讀

? 用于返回當前WebSocket連接的狀態,其值即含義如下

狀態含義
0 WebSocket.CONNECTING
1 WebSocket.OPEN
2 WebSocket.CLOSING
3 WebSocket.CLOSED

一個ws連接各個狀態的執行時刻如下

let socket = new WebSocket('http://localhost:8080')
// 正在創建連接
console.log('[readyState]:', socket.readyState) // 0

// 連接建立成功后觸發onopen回調
socket.onopen = function() {
  console.log('connected,[readyState]:', socket.readyState) // 1
  // 發送消息
  socket.send('from client: Hello')
}

// 從服務端收到信息觸發onmessage回調
socket.onmessage = function() {
  console.log('received,[readyState]:', socket.readyState) // 1
  // 發送消息
  socket.send('from client: Hello')
}

// 連接失敗觸發onerror回調
socket.onerror = function() {
  console.log('connect error, [readyState]:', socket.readyState)  // 3
}

// 調用關閉連接,狀態立刻變成2(正在關閉)。關閉成功觸發onclose變成3
socket.close()

// 連接關閉觸發onclose回調,有回調參數
socket.onclose = function(event) {
  const { code, reason, wasClean } = event
  console.log('connect closed, [readyState]:', socket.readyState) // 3
  console.log(code, reason, wasClean) // wasClean表示連接是否已經關閉。boolean
}

? 當readyState的值從 0 變成 1 后,客戶端和服務端就可以通訊了。
?

# 方法

- 發送數據 send()

? 發送數據一定是伴隨在連接已經打開的情況下

socket.addEventListener('open', function(event) {
  sokcet.send('hello server')
})
- 關閉連接 close()

? 關閉當前連接。可以傳 0/1/2 個參數。code解釋關閉原因的狀態碼。reason解釋關閉原因的描述(限制123個字節)。

sokcet.close([code[, reason]])

如果未傳參數,會默認code1005,意為:無參數,未提供關閉原因狀態碼。查看 狀態碼詳情。如果提供一個無效的狀態碼,會拋出異常INVALID_ACCESS_ERR
?

# 事件

- 連接已建立 onopen
socket.addEventListener('open', function(event)  {
  // TODO: send message
});
- 接收服務端消息回調 onmessage

? 當服務器向客戶端發來消息時,WebSocket對象會觸發message事件。這個message事件與其他傳遞消息的協議類似,也是把返回的數據保存在event.data屬性中

socket.addEventListener('message', function(event)  {
  var data = event.data;
  // TODO:
});
- 關閉連接的回調 onclose
socket.addEventListener('close', function(event)  {
  const { code, reason, wasClean } = event
  // TODO:
});
- 連接失敗的回調 onerror
socket.addEventListener('error', function(event)  {
  console.error("WebSocket error observed:", event)
});

?

# 屬性

- 當前剩余未發送數據 bufferedAmount 只讀

? 用于返回已經被send()方法放入隊列但還沒有被發送到網絡中的數據的字節數,只有發送完成它才會被重置為0。如果發送過程中連接被關閉不會重置,不斷的調用send()該值會不斷增長。

if (ws.bufferedAmount === 0){
    console.log("發送已完成");
} else {
    console.log("還有", ws.bufferedAmount, "數據沒有發送");
}
- 連接二進制類型 binaryType 只讀

? 返回websocket連接所傳輸二進制數據的類型

const binaryType = socket.binaryType
- 已選擇的擴展值 extensions 只讀

? 返回服務器已選擇的擴展值

const extensions = socket.extensions 
- 子協議 protocol 只讀

? 返回服務器端選中的子協議的名字;也就是在實例化WebSocket對象時,在參數protocols中指定的字符串

const protocol = socket.protocol  
- 子協議 url 只讀

? 返回值為當構造函數創建WebSocket實例對象時URL的絕對路徑。

const url = socket.url 

?

$ 一個服務端實例

這里提供一個簡單的例子,引入了ws庫實現。也可以使用socket.io

var app = require('express')();
var server = require('http').Server(app);
var WebSocket = require('ws');

var wss = new WebSocket.Server({ port: 8080 });

wss.on('connection', function connection(ws) {
    console.log('server: receive connection.');
    
    ws.on('message', function incoming(message) {
        console.log('server: received: %s', message);
    });

    ws.send('world');
});

app.get('/', function (req, res) {
  res.sendfile(__dirname + '/index.html');
});

app.listen(3000);

?

結束語

參考文獻
WebSocket-菜鳥教程
WebSocket-MDN
WebSocket-廖雪峰的官方網站

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,117評論 6 537
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,860評論 3 423
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,128評論 0 381
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,291評論 1 315
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,025評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,421評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,477評論 3 444
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,642評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,177評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,970評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,157評論 1 371
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,717評論 5 362
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,410評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,821評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,053評論 1 289
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,896評論 3 395
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,157評論 2 375

推薦閱讀更多精彩內容

  • 原文地址:http://www.ibm.com/developerworks/cn/java/j-lo-WebSo...
    敢夢敢當閱讀 8,929評論 0 50
  • WebSocket 機制 WebSocket 是 HTML5 一種新的協議。它實現了瀏覽器與服務器全雙工通信,能更...
    勇敢的_心_閱讀 2,286評論 0 4
  • Socket并非是一個協議,而是為了方便使用TCP而抽象出來的一層,是位于應用層和傳輸控制層之間的一組接口。換句話...
    JunChow520閱讀 3,355評論 0 4
  • 1 述 WebSocket是一種網絡通信協議WebSocket 協議在2008年誕生,2011年成為國際標準。HT...
    凱玲之戀閱讀 694評論 0 0
  • 此教程無需R語言 Step 1 確定需要檢索的芯片 例如 GSE33006 Step 2 利用GEO2R分析差異基...
    Fobbite0579閱讀 12,406評論 0 5