【通訊】WebSocket

  • 2019 年 11 月 26 日
  • 筆記

概述

通常,當客戶端訪問一個網頁時,會向Web伺服器發送一個HTTP請求,Web伺服器接收該請求,並返迴響應,客戶端在接收到響應後再將資訊呈現出來。

對於那些資訊變化不是特別頻繁的應用來說,也許不會造成多大的影響,但是對於那些對實時性要求比較高即資訊經常變化的應用來說(比如在線遊戲,資訊推送等),就必須採用某種機制來確保伺服器與瀏覽器間的資訊同步。

在WebSocket規範出來之前,可供選擇的機制一般三種:

  1. 輪詢(Polling)
  2. Comet
  3. Flash插件

輪詢

這是最早的一種實現實時Web應用的方案,客戶端按照一定的時間間隔頻繁的向伺服器發送請求,來實現服務端與客戶端的同步。這種方案十分低效,因為並沒有什麼機制能確定每次發送的請求都能從服務端獲得更新的數據(由於伺服器更新數據的延時性,會造成客戶端發送很多無用的請求,從而浪費了很多通訊資源)。

comet

Comet本質上還是輪詢,只是對上述輪詢的缺點上做了些改進,最大限度的降低無效的網路傳輸。 Comet又分為長輪詢技術和流技術,長輪詢技術的實現是,給輪詢設置條件(比如設置過期時間),當該條件被觸發時再發送請求。流技術通常就是在客戶端的頁面使用一個隱藏窗口向服務端發出一個長連接請求,服務端響應該請求並不斷更新連接狀態以保證客戶端和服務端的連接不過期,在面對並發量比較大的應用時,採用這一方案會消耗很多服務端的資源。

Flash 插件

AdobeFlash通過自己的Socket完成數據交換,JavaScript調用Flash提供的API,來實現數據的實時傳輸。這種方式比輪詢要高效得多,但由於需要使用Flash插件,在一些不支援Flash插件或支援得不好客戶端上,仍然不能實現實時需求。

不管是輪詢還是comet,這些技術都不能稱之為真正的實時技術,它們只是通過Ajax方式來模擬實時效果,客戶端和服務端的每次交互都是一次完整的HTTP協議的傳輸過程(HTTP頭資訊作為傳輸內容),大大增加了應用的資訊傳輸量,而且為了實現這些方案,往往需要構建較為複雜的服務端和客戶端的編程實現。總體而言,這些技術是即增加了服務端的負載又增加了編程複雜度。

針對以上技術的缺陷以及web進一步的高並發和實時性需求的環境下,基於HTML5規範的WebSocket應運而生。

WebSocket是一個基於TCP協議之上解決客戶端和服務端之間雙向通訊的協議,它能高效的實現實現需求。目前有關實時功能的實現基本上都採用WebSocket來實現。

程式碼示例

WebSocket的實現分為客戶端和服務端兩部分,客戶端發出WebSocket連接請求,服務端響應,實現類似TCP握手的動作,客戶端和服務端可以通過這個連接通道傳遞消息,這個連接會持續存在直到一方主動關閉連接時為止。

服務端

rails 5中引入了一個全新的基於WebSocket的框架—Action Cable,可以很方便的構建實時通知系統。下面簡單列一下基礎程式碼,有興趣的朋友也可以點擊後面的參考鏈接作深入的學習。

def push_to_client     user = User.find(self.user_id)     user.following_by_type("User").distinct.pluck("id").each do |uid|         ActionCable.server.broadcast "notifications/#{uid}", {id: self.id, notifyType: "createTweet"}     end rescue     nil end

擴展鏈接:

https://github.com/rails/actioncable-examples

https://www.sitepoint.com/create-a-chat-app-with-rails-5-actioncable-and-devise/

客戶端

在默認情況下,cookie會在瀏覽器關閉的時候消除,但可通過expires來設置cookie的有效期。語法如下:

// 發布消息 var initWebsocket = function() {     var self = this;     if (window.UasApp && window.UasApp.cable) {     if (!this.notificationChannel) {     this.notificationChannel = window.UasApp.cable.subscriptions.create("NotificationsChannel", {     connected: function() {         console.log("connected ................");     },     received: function(data) {         self.trigger("notified", {             data: data         });       }     });   }   } }; // 監聽消息 var bindNotifications = function() {     // 監聽websocket發布的消息,構建從關注者過來的內容     var self = this;     this.own(runtime.on("notified", function(evt) {         var data = evt.data;         if (data.notifyType === "createTweet") {             self.newTweetIds.push(data.id);             self.addToNoti();         }     })); };

程式碼說明

1. 上面的rails程式碼主要用到了Action Cable模組,目前已整合到rails 5.0版本中,屬於rails的一部分,源程式碼。Action Cable 包含了後台和前端的實現,可以方便的為項目添加基於websocket的通訊功能。

2. 上面的前端程式碼,主要實現了事件分發的功能,首先訂製了action cable提供的received方法,該方法會觸發notified事件的執行,然後在各實例DOM中監聽notified事件,處理其對應的DOM操作,比如樣例中的添加新tweet。

總結:如果需要在客戶端與服務端之間建立極低延遲、近乎即時的連接,則可以使用WebSocket,比如下面的一些實用場景:

  1. 多人在線遊戲
  2. 即時聊天
  3. 體育賽況直播
  4. 即時更新社交資訊流