小學生都能讀懂的網路協議之:WebSocket

flydean 發表於 2021-09-23
WebSocket

簡介

服務端和客戶端應該怎麼進行通訊呢?我們常見的方法就是客戶端向伺服器端傳送一個請求,然後伺服器端向客戶端傳送返回的響應。這種做法比較簡單,邏輯也很清晰,但是在某些情況下,這種操作方式並不好使。

比如在伺服器端的某些變動需要通知客戶端的情況,因為客戶端並不知道伺服器端的變動是否完成,所以需要不停的使用輪循去檢測伺服器的狀態。這種做法的缺點就是太過於浪費資源。如果希望及時性好的話,需要不斷的減少輪循的時間間隔,導致極大的伺服器壓力和資源的浪費。

那麼有沒有好的解決辦法呢?

既然不能使用查詢,那麼就改成伺服器推送就行了。我們知道在HTTP/2中,提供了一種伺服器推送的方式,但是這種方式是單向的,也就是說在同一個TCP連線之上,並不能實現客戶端和伺服器端的互動。

於是我們需要一個能夠雙向互動的網路協議,這個協議就是WebSocket。

webSocket vs HTTP

webSocket是一個基於底層TCP協議的一個雙向通訊網路協議。這個雙向通訊是通過一個TCP連線來實現的。webSocket於2011年以RFC 6455釋出成為IETF的標準。

同樣作為基於TCP協議的標準協議,它和HTTP有什麼區別呢?

如果以OSI的七層模型來說,兩者都位於七層協議的第四層。但是兩者是兩種不同的協議。鑑於HTTP已經如此流行了,為了保證webSocket的通用性,webSocket也對HTTP協議進行了相容。也就是說能夠使用HTTP協議的地方也就可以使用webScoket。

這個和之前討論的HTTP3有點類似,雖然HTTP3是一個新的協議,但是為了保證其廣泛的應用基礎,HTTP3還是在現有的UDP協議上進行重寫和構建。目的就是為了相容。

實時上,webSocket使用的是HTTP upgrade header,從HTTP協議升級成為webSocket協議。

HTTP upgrade header

什麼是HTTP upgrade header呢?

HTTP upgrade header是在HTTP1.1中引入的一個HTTP頭。當客戶端覺得需要升級HTTP協議的時候,會向伺服器端傳送一個升級請求,伺服器端會做出相應的響應。

對於websocket來說,客戶端在和伺服器端建立連線之後,會首先傳送給伺服器端 Upgrade: WebSocket 和 Connection: Upgrade 頭。伺服器端接收到客戶端的請求之後,如果支援webSocket協議,那麼會返回同樣的Upgrade: WebSocket和Connection: Upgrade 頭到客戶端。客戶端接收到伺服器端的響應之後,就知道伺服器端支援websocket協議了,然後就可以使用WebSocket協議傳送訊息了。

websocket的優點

其實前面我們也講過了,相對於傳統的HTTP拉取,webSocket可以藉助於一個TCP連線實現資料的實時傳輸。可以在減少伺服器壓力的同時,實現伺服器和客戶端的實時通訊。

webScoket的應用

WebSocket使用的是ws和wss作為URI的標記符。其中ws表示的是websocket,而wss表示的是WebSocket Secure。

因為通常來說我們使用的web瀏覽器來和伺服器進行通訊。瀏覽器就是我們的web客戶端,對於現代瀏覽器來說,基本上都支援WebSocket協議,所以大家可以放心應用,不用擔心協議相容的問題。

對於瀏覽器客戶端來說,可以使用標準的瀏覽器WebSocket物件,來和伺服器進行通訊,我們看一個簡單的javascript客戶端使用webSocket進行通訊的例子:

// 使用標準的WebSocket API建立一個socket連線
const socket = new WebSocket('ws://www.flydean.com:8000/webscoket');

// 監聽webSocket的open事件
socket.onopen = function () {
  setInterval(function() {
    if (socket.bufferedAmount == 0)
      socket.send(getUpdateData());
  }, 50);
};

// 監聽接收訊息事件
socket.onmessage = function(event) {
  handleUpdateData(event.data);
};

// 監聽socket關閉事件
socket.onclose = function(event) {
  onSocketClose(event);
};

// 監聽error事件
socket.onerror = function(event) {
  onSocketError(event);
};

上述程式碼主要就是各種監聽socket的事件,然後進行處理,非常簡單。

websocket的握手流程

上面我們講過了,websocket是從HTTP協議升級的,客戶端通過傳送:

Upgrade: websocket
Connection: Upgrade

到伺服器端,對協議進行升級。我們舉一個具體的例子:

GET /webscoket HTTP/1.1
Host: www.flydean.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: x123455688xafe=
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13
Origin: http://flydean.com

對應的server端的返回:

HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: Qhfsfew12445m=
Sec-WebSocket-Protocol: chat

在上面的例子中,除了使用Upgrade頭之外,客戶端還向伺服器端傳送了Sec-WebSocket-Key header。這個header包含的是一個 base64 編碼的隨機位元組。server對應的會返回這個key的hash值,並將其設定在Sec-WebSocket-Accept header中。

這裡並不是為了安全操作,而是為了避免上一次的連線快取情況。

WebSocket API

要想在瀏覽器端使用WebSocket,那麼就需要用到客戶端API,而客戶端API中最主要的就是WebSocket。

它提供了對websocket的功能封裝。它的建構函式是這樣的:

WebSocket(url[, protocols])

url就是要連線的websocket的地址,那麼可選的protocols是什麼呢?protocols可以傳入單個協議字串或者是協議字串陣列。它指的是 WebSocket 伺服器實現的子協議。

子協議是在WebSocket協議基礎上發展出來的協議,主要用於具體的場景的處理,它是是在WebSocket協議之上,建立的更加嚴格的規範。

比如,客戶端請求伺服器時候,會將對應的協議放在Sec-WebSocket-Protocol頭中:

GET /socket HTTP/1.1
...
Sec-WebSocket-Protocol: soap, wamp

伺服器端會根據支援的型別,做對應的返回,如:

Sec-WebSocket-Protocol: soap

WebSocket API有四種狀態,分別是:

狀態定義取值
WebSocket.CONNECTING0
WebSocket.OPEN1
WebSocket.CLOSING2
WebSocket.CLOSED3

通過呼叫close或者Send方法,會觸發相應的events事件,WebSocket API 的事件主要有:close,error,message,open這4種。

下面是一個具體使用的例子:

// 建立連線
const socket = new WebSocket('ws://localhost:8000');

// 開啟連線
socket.addEventListener('open', function (event) {
    socket.send('沒錯,開啟了!');
});

// 監聽訊息
socket.addEventListener('message', function (event) {
    console.log('監聽到伺服器的訊息 ', event.data);
});

總結

以上就是websocket的簡單介紹和使用,有想知道Websocket到底是怎麼進行訊息傳輸的,敬請期待我的下一篇文章。

本文已收錄於 http://www.flydean.com/06-websocket/

最通俗的解讀,最深刻的乾貨,最簡潔的教程,眾多你不知道的小技巧等你來發現!

歡迎關注我的公眾號:「程式那些事」,懂技術,更懂你!