大致瞭解下websocket

cherryPick發表於2018-12-13

背景

當websocket並不存在自己的知識範圍內,前端如何保證獲取的資料和後端保持同步?傳統做法一般有兩種:http輪詢,Commet推送。那麼,這兩種方式有何不妥呢?

HTTP輪詢

使用輪詢的方法,在設定的時間間隔,客戶端向服務端傳送http請求,獲取服務端最新的資料。

Comet推送

Comet推送技術,伺服器實時地將資訊傳遞給客戶端,Comet是通過長輪詢或者iframe流實現的。

長輪詢:在開啟了一條連線後,在服務端推送了資料過來後才關閉。

iframe流:將一隱藏的iframe插入頁面,利用iframe的src屬性,在客戶端和服務端建立一條長連線,服務端向iframe傳送資料(通常HTML,內有負責插入資訊的js),實時更新頁面。googletalk使用的就是這種方法。

不妥

  • HTTP請求每次都要攜帶完整(包含協議控制的資料包)的頭部,開銷有點大。
  • HTTP輪詢中,服務端需要接收到客戶端的請求,才能傳送實時資料給客戶端,實時性還是不夠強。
  • HTTP是無狀態協議,也就意味著,每次傳送請求時,客戶端需要攜帶狀態資訊,可能時候請求引數的資料都不及這些狀態資訊的體積大。
  • Comet,依然需要反覆發出請求,而且採用的是長連結(是使用同一個tcp連線來傳送和接收多個HTTP請求/應答,而不是為每一個新的請求/應答開啟新的連線的方法,connection: keep-alive,Apache預設保持15秒),消耗伺服器資源(對於單個檔案被不斷請求的服務(例如圖片存放網站),Keep-Alive可能會極大的影響效能,因為它在檔案被請求之後還保持了不必要的連線很長時間)。

websocket

備受矚目的websocket橫空出世,解決了一系列問題,使用起來也是極為方便的。 websocket是一個基於TCP的全雙工通訊協議,HTTP一樣,都是一種處於應用層的通訊協議,並不是什麼特別存在的協議。websocket使用的是ws或者wss的統一資源識別符號,寫法和http的相似:

http://a.b.com/xxx
https://secure.b.com/xxx

ws://a.b.com/wsapi
wss://secure.b.com/
複製程式碼

在websocket的世界,實時通訊相對來說不那麼難了。服務端和客戶端完成一次握手後,建立了一次永續性的連線,彼此可以主動向對方傳送實時資訊。 websocket相比於傳統方式,有哪些優勢呢?

  • 實時性較強,服務端可隨時主動向客戶端推送資訊。
  • 保持連線狀態,建立連線後的每次通訊,可以省略攜帶狀態資訊。
  • 支援二進位制
  • 更好的壓縮效果

常用api

在前端,通過ajax傳送HTTP請求。而websocket則是通過Websocket建構函式,傳入符合websocket規範的url,建立一個例項,建立連線,這個例項本身含有多個可供開發者呼叫的api。

建構函式Websocket

const socket = new Websocket('ws://examp.b.com');
複製程式碼

屬性

  • onopen 監聽事件,當websocket連線建立,可以收發資料。
socket.onpen = (event) => {
    console.log('the websocket is opened now');
}
複製程式碼
  • onmessage 監聽事件,當websocket收到資訊時觸發。
socket.onmessage = (event) => {
    console.log('the websocket receive data');
}
複製程式碼
  • onclose 監聽事件,當websocket關閉時
socket.onclose = (event) => {
    console.log('the websocket is closed');
}
複製程式碼
  • readyState websocket狀態:0(CONNECTING正在建立連線),1(OPEN,連線開啟,可以收發資訊), 2(CLOSING正在關閉連線), 3(CLOSED連線關閉)
console.log(socket.readyState);
複製程式碼
  • bufferedAmount 表示還有多少位元組的二進位制資料沒有被髮送出去,一般可以用來判斷資料是否傳送完成,畢竟,目前沒有一個可以監聽資料傳送是否完成的函式存在。
console.log(socket.bufferAmount)
複製程式碼

方法

  • send 傳送資訊,傳送的資訊可以string,blob,ArrayBuffer.
socket.send('hello')
複製程式碼
  • close  主動關閉websocket連線.傳遞的引數有兩個,code, reason。 第一個引數為code(number型別),解釋socket關閉的原因(code),若不傳code,預設code的值為1005,表明無code可以傳。 第二個引數reason(string型別),人為解釋關閉的原因,字串的位元組長度不能超過UTF-8文字的123個位元組。
socket.close(code, reason);
複製程式碼

Websocket連線的建立過程

都說websocket都是一個基於TCP的全雙工通訊協議,websocket有何關係?同樣作為應用層的通訊協議,除了上述的區別之外,websocket和http有什麼聯絡?這些可從websocket連線的建立過程得知。

計算機網路的五層模型:物理層、鏈路層、網路層、傳輸層、應用層。TCP是傳輸層協議,websocket是應用層協議,建立websocket連線的過程中,必定要經過傳輸層,所以,在建立連線之前,還是需要經過TCP的三次握手。

在TCP三次成功握手之後,意味著服務端和客戶端可以進行資訊的傳輸,這時候,使用websocket協議傳遞資訊還是不可以的,需要客戶端向服務端傳送HTTP請求,表示客戶端希望雙方使用websocket協議來進行通訊。在這次握手後,雙方可以通過websocket協議主動向對方傳送資訊。下面這兩段來自於RFC。

  • 客戶端傳送升級為websocket協議的請求
GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
複製程式碼

在客戶端傳送的這個特殊的HTTP請求中,有幾個特殊的欄位: Upgrade:websocket,Connection: Upgrade這兩組鍵值對錶明客戶端請求升級到websocket通訊協議。 Sec-webSecket-version,採用的websocket協議版本。

  • 伺服器返回同意升級為websocket協議的資訊
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat
複製程式碼

響應資訊的第一行,狀態碼為101,服務端根據客戶端的請求切換協議,服務端也返回了Upgrade:websocket,Connection: Upgrade,服務端將切換到websocket協議。

同時,可以看到客戶端請求攜帶了Sec-websocket-key,這是一個由客戶端產生的一個base64編碼的隨機字串,這個字串和服務端返回的Sec-Websocket-Accept欄位有著密切的聯絡。這兩者使用者保護websocket通訊的安全性。在服務端收到來自客戶端開始的握手後,將客戶端攜帶的Sec-websocket-key和全域性唯一一個識別符號258EAFA5-E914-470A-95CA-C5ABOdc85b11結合,然後加密,作為Sec-websocket-Accept的值返回給客戶端。那這麼做的意義是什麼呢?

服務端向客戶端證明自己收到了來自他的websocket握手,服務端將不再接受來自客戶端的其他非websocket通訊(指的是基於這次tcp連線和websocket握手完成後的通訊,並不針對客戶端和服務端之間所有的介面通訊)。這麼做有什麼好處呢?服務端將可避免攻擊者使用XMLHttpRequest或者表單提交傳送精心設計的包來進行攻擊。當然,並不意味者websocket通訊絕對安全,就像http對應還有一個相對安全的https,ws還有一個相對來說比較安全的wss。

抓包分析

目前,我所瞭解到的websocket抓包工具有兩種:chrome的開發者模式中的Network,filder。有一篇文章寫的很棒,推薦一下:點我點我!

參考文件

我只是個搬運工!!!

相關文章