服務端和客戶端單雙工通訊方案

輪詢

短輪詢

普通的輪詢。指在特定的的時間間隔(如每1秒,影響實時性與性能),由瀏覽器對服務器發出HTTP 請求,而後由服務器返回最新的數據給客戶端的瀏覽器。javascript

長輪詢

當服務器收到客戶端發來的請求後,服務器端不會直接進行響應,而是先將這個請求掛起,而後判斷服務器端數據是否有更新。若是有更新,則進行響應,若是一直沒有數據,直到超時(服務端設置)才返回。客戶端響應處理完服務器返回的信息後,再次發出請求,從新創建鏈接。html

1)相同點:當服務器的數據不可達時,基於http長輪詢和短輪詢 的請求,都會停留一段時間;
2)不一樣點:http長輪詢是在服務器端的停留,而http 短輪詢是在 瀏覽器端的停留;
3)性能總結:不論是長輪詢仍是短輪詢,形成服務器資源浪費。java

服務器發送事件(Server-Sent Event)

服務器發送事件(如下簡稱SSE)是HTML 5規範的一個組成部分,能夠實現服務器到客戶端的單向數據通訊。經過SSE,客戶端能夠自動獲取數據更新,而不用重複發送HTTP請求。一旦鏈接創建,「事件」便會自動被推送到客戶端。服務器端SSE經過「事件流(Event Stream)」的格式產生並推送事件。事件流對應的MIME類型爲「text/event-stream」,並且其基於HTTP長鏈接。web

SSE規範:https://html.spec.whatwg.org/...canvas

原理:

SSE本質是發送的不是一次性的數據包,而是一個數據流。可使用 HTTP 301 和 307 重定向與正常的 HTTP 請求同樣。服務端接二連三的發送,客戶端不會關閉鏈接,若是鏈接斷開,瀏覽器會嘗試從新鏈接。若是鏈接被關閉,客戶端能夠被告知使用 HTTP 204 無內容響應代碼中止從新鏈接。跨域

特色

  • SSE 使用 HTTP 協議,現有的服務器軟件都支持。WebSocket 是一個獨立協議。
  • SSE 屬於輕量級,使用簡單;WebSocket 協議相對複雜。
  • SSE 默認支持斷線重連,WebSocket 須要本身實現。
  • SSE 通常只用來傳送文本,二進制數據須要編碼後傳送,WebSocket 默認支持傳送二進制數據。
  • SSE 支持自定義發送的消息類型。

客戶端API

EventSource對象

SSE 的客戶端 API 部署在EventSource對象上。
使用 SSE 時,瀏覽器首先生成一個EventSource實例,向服務器發起鏈接。瀏覽器

var source = new EventSource(url);

上面的url能夠與當前網址同域,也能夠跨域。跨域時,能夠指定第二個參數,打開withCredentials屬性,表示是否一塊兒發送 Cookie。服務器

var source = new EventSource(url, { withCredentials: true });

readyState

代表鏈接的當前狀態。該屬性只讀,能夠取如下值。babel

  • 0:至關於常量EventSource.CONNECTING,表示鏈接還未創建,或者斷線正在重連。
  • 1:至關於常量EventSource.OPEN,表示鏈接已經創建,能夠接受數據。
  • 2:至關於常量EventSource.CLOSED,表示鏈接已斷,且不會重連。

事件

一、onopen
鏈接一旦創建,就會觸發open事件,能夠在onopen屬性定義回調函數。網絡

source.onopen = function (event) {
  // ...
};

// 另外一種寫法
source.addEventListener('open', function (event) {
  // ...
}, false);

二、onmessage
客戶端收到服務器發來的數據,就會觸發message事件,能夠在onmessage屬性的回調函數。

source.onmessage = function (event) {
  var data = event.data;
  // handle message
};

事件對象的data屬性就是服務器端傳回的數據(文本格式)。

三、onerror
若是發生通訊錯誤(好比鏈接中斷),就會觸發error事件,能夠在onerror屬性定義回調函數。

source.addEventListener('error', function (event) {
  // handle error event
}, false);

四、close
close方法用於關閉 SSE 鏈接。

source.close();

默認狀況下,服務器發來的數據,老是觸發瀏覽器EventSource實例的message事件。開發者還能夠自定義 SSE 事件,這種狀況下,發送回來的數據不會觸發message事件。

五、自定義事件

source.addEventListener('foo', function (event) {
  var data = event.data;
  // handle message
}, false);

服務端實現

數據格式

服務器向瀏覽器發送的 SSE 數據,必須是 UTF-8 編碼的文本,具備以下的 HTTP 頭信息。

Content-Type: text/event-stream
Cache-Control: no-cache
Connection: keep-alive

每一次發送的信息,由若干個message組成,每一個message之間用nn分隔。每一個message內部由若干行組成,每一行都是以下格式。

[field]: valuen

上面的field能夠取四個值。

*   data
*   event
*   id
*   retry

data

數據內容用 data字段表示。若是數據很長,能夠分紅多行,最後一行用 \n\n結尾,不然不會觸發,前面行都用 \n結尾。

下面是一個發送 JSON 數據的例子。

data: {\n
data: "foo": "bar",\n
data: "baz", 555\n
data: }\n\n

id

數據標識符用 id字段表示,至關於每一條數據的編號。

瀏覽器用lastEventId屬性讀取這個值。一旦鏈接斷線,瀏覽器會發送一個 HTTP 頭,裏面包含一個特殊的Last-Event-ID頭信息,將這個值發送回來,用來幫助服務器端重建鏈接。所以,這個頭信息能夠被視爲一種同步機制。

event

event字段表示自定義的事件類型,默認是message事件。瀏覽器能夠用addEventListener()監聽該事件。

retry

retry設置當前http鏈接失敗後,從新鏈接的間隔。EventSource規範規定,客戶端在http鏈接失敗後默認進行從新鏈接,重連間隔爲3s,經過設置retry字段可指定重連間隔;

兩種狀況會致使瀏覽器從新發起鏈接:一種是時間間隔到期,二是因爲網絡錯誤等緣由,致使鏈接出錯。

此外,還能夠有冒號開頭的行,表示註釋。因爲EventSource是基於HTTP鏈接之上的,所以在一段沒有數據的時期會出現超時問題。 所以須要服務端作心跳保活。

註釋行能夠用來防止鏈接超時,服務器能夠按期發送一條消息註釋行,以保持鏈接不斷。

: This is a comment

使用場景

  1. erver到Client單向通訊,例如更新、上下通知
  2. 只能傳輸文本
  3. IE不兼容,babel-polyfill中有作兼容處理
  4. SSE 比 Websocket 輕量。固然功能要簡單的多。開發便利,實現 SSE 的工做量比 WebSocket 要少得多。
  5. SSE 自然支持斷線重連

6.對於須要在客戶端與服務器之間頻繁通訊的多用途 Web 應用程序,顯然應該選擇 WebSocket。對於但願從服務器向客戶端傳輸異步數據,而不須要響應的應用程序,SSE 更適合一些。

WebSocket

WebSocket 協議在2008年誕生,2011年成爲國際標準。全部瀏覽器都已經支持了。

它的最大特色就是,服務器能夠主動向客戶端推送信息,客戶端也能夠主動向服務器發送信息,是真正的雙向平等對話,屬於服務器推送技術的一種。

image.png

特色

(1)創建在 TCP 協議之上,服務器端的實現比較容易。
(2)與 HTTP 協議有着良好的兼容性。默認端口也是80和443,而且握手階段採用 HTTP 協議,所以握手時不容易屏蔽,能經過各類 HTTP 代理服務器。
(3)數據格式比較輕量,性能開銷小,通訊高效。
(4)能夠發送文本,也能夠發送二進制數據。
(5)沒有同源限制,客戶端能夠與任意服務器通訊。
(6)協議標識符是 ws(若是加密,則爲 wss),服務器網址就是 URL。
ws://example.com:80/some/path

image.png

客戶端API

WebSocket對象

WebSocket 對象做爲一個構造函數,用於新建 WebSocket 實例。

var ws = new WebSocket('ws://localhost:8080');

webSocket.readyState

readyState屬性返回實例對象的當前狀態,共有四種。

  • CONNECTING:值爲0,表示正在鏈接。
  • OPEN:值爲1,表示鏈接成功,能夠通訊了。
  • CLOSING:值爲2,表示鏈接正在關閉。
  • CLOSED:值爲3,表示鏈接已經關閉,或者打開鏈接失敗。

onopen

實例對象的onopen屬性,用於指定鏈接成功後的回調函數。

ws.onopen = function () {
  ws.send('Hello Server!');
}

onclose

實例對象的onclose屬性,用於指定鏈接關閉後的回調函數。

onmessage

實例對象的onmessage屬性,用於指定收到服務器數據後的回調函數。

注意,服務器數據多是文本,也多是二進制數據(blob對象或Arraybuffer對象)。

ws.onmessage = function(event){
  if(typeof event.data === String) {
    console.log("Received data string");
  }

  if(event.data instanceof ArrayBuffer){
    var buffer = event.data;
    console.log("Received arraybuffer");
  }
}

除了動態判斷收到的數據類型,也可使用binaryType屬性,顯式指定收到的二進制數據類型。

// 收到的是 blob 數據 
ws.binaryType = "blob";
ws.onmessage = function(e) {
  console.log(e.data.size);
};
 // 收到的是 ArrayBuffer 數據 
ws.binaryType = "arraybuffer";
ws.onmessage = function(e) {
  console.log(e.data.byteLength);
};

send()

實例對象的send()方法用於向服務器發送數據。

發送文本的例子。

ws.send('your message');

發送 Blob 對象的例子。

var file = document
  .querySelector('input[type="file"]')
  .files[0];
ws.send(file);

發送 ArrayBuffer 對象的例子。

var img = canvas_context.getImageData(0, 0, 400, 320);
var binary = new Uint8Array(img.data.length);
for (var i = 0; i < img.data.length; i++) {
  binary[i] = img.data[i];
}
ws.send(binary.buffer);
bufferedAmount

實例對象的bufferedAmount屬性,表示還有多少字節的二進制數據沒有發送出去。它能夠用來判斷髮送是否結束。

var data = new ArrayBuffer(10000000);
socket.send(data);

if (socket.bufferedAmount === 0) {
 // 發送完畢 } else {
 // 發送還沒結束 }

onerror

實例對象的onerror屬性,用於指定報錯時的回調函數。

服務端的實現

服務端通常須要引庫實現,能夠查看維基百科的列表https://en.wikipedia.org/wiki...

使用場景

  1. 適用於server到Client雙向通訊,例如聊天
  2. 只能傳輸二進制和文本
  3. 主流瀏覽器都支持

對比

兼容性 協議 服務器負載 客戶端負載 數據實時性 實現複雜度
SSE IE不兼容,使用babel-polyfill進行兼容處理(IE11) HTTP 與傳統輪詢類似,可是佔用帶寬較少 瀏覽器中原生實現,佔用資源很小 非實時,默認3秒延遲,延遲可自定義 須要服務器配合,客戶端、服務端實現更簡單
WebSocket 主流瀏覽器都兼容 CPU和內存資源不以客戶端數量衡量,而是以客戶端事件數衡量。與長、短輪詢、sse相比,性能最佳。 WS 同Server-Sent Event。 實時 須要Socket程序實現和額外端口,客戶端實現簡單。
相關文章
相關標籤/搜索