初探WebSocket node websocket socket.io 我們平常開發的大部分web頁面都是主動‘拉’的形式,如果需要更新頁面內容,則需要“刷新”一個,但Slack工具卻能主動收到信息,好像服務端能主動給客戶端推送信息,請研究一下這是怎麼實現的。 WebSocket websocke ...
初探WebSocket
node websocket socket.io
我們平常開發的大部分web頁面都是主動‘拉’的形式,如果需要更新頁面內容,則需要“刷新”一個,但Slack工具卻能主動收到信息,好像服務端能主動給客戶端推送信息,請研究一下這是怎麼實現的。
WebSocket
websocket是HTML5中新引進的一種 協議
,它是一種協議就像(HTTP,FTP在tcp/ip協議棧中屬於應用層)而不是簡單的一個函數。它本身及基於TCP協議的一種新的協議。
WebSocket的產生
websocket是基於web的實時性而產生的,說到這裡就不得不要追溯一下web的歷史了,在2005年(也就是ajax還沒誕生)以前,我們如果想要在一個頁面顯示顯示不同的內容,或者說頁面內跳轉,只能是通過點擊然後路由跳轉,在ajax誕生之後,網頁開始變得動態了。但是所有的HTTP通信還都是由客戶端控制的,這就要需要長連接,定期輪詢或者長輪詢,來和伺服器溝通來更新數據。
WebSocket之前的伺服器“推”的技術
- 定期輪詢(ajax輪詢):瀏覽器在特定的時間給伺服器發送請求,查看伺服器是否有信息數據。
優點:後端程式編寫比較容易。
缺點:請求中有大半是無用,浪費帶寬和伺服器資源。
實例:適於小型應用。
- 長輪詢:其實和上面的原理差不多,是對ajax輪詢進行了改進和提高。客戶端和服務端建立連接之後,一直保持通信(阻塞模式),如果伺服器沒有新消息就一直保持通信,直到伺服器有新的消息,然後返回給客戶端,客戶端與伺服器斷開連接,此時客戶端可以繼續和伺服器進行連接。
優點:在無消息的情況下不會頻繁的請求,耗費資源小。
缺點:伺服器hold連接會消耗資源,返回數據順序無保證,難於管理維護。
實例:舊的 WebQQ、Hi網頁版、Facebook IM。
- 流控制:通常就是在客戶端的頁面使用一個隱藏的視窗向服務端發出一個長連接的請求。伺服器端接到這個請求後作出回應並不斷更新連接狀態以保證客戶端和服務 器端的連接不過期。通過這種機制可以將伺服器端的信息源源不斷地推向客戶端。比如在頁面里嵌入一個隱蔵iframe,將這個隱蔵iframe的src屬性設為對一個長連接的請求或是採用xhr請求,伺服器端就能源源不斷地往客戶端輸入數據。
SSE,Comet,使用長鏈接進行通訊。
優點:消息即時到達,不發無用請求;管理起來也相對方便。
缺點:伺服器維護一個長連接會增加開銷。
實例:Gmail聊天
- Flash Socket:在頁面中內嵌入一個使用了Socket類的 Flash 程式JavaScript通過調用此Flash程式提供的Socket介面與伺服器端的Socket介面進行通信,JavaScript在收到伺服器端傳送的信息後控制頁面的顯示。
優點:實現真正的即時通信,而不是偽即時。
缺點:客戶端必須安裝Flash插件;非HTTP協議,無法自動穿越防火牆。
實例:網路互動游戲。
HTTP1.1和長鏈接
以上幾種伺服器“推”的技術中:長輪詢和流控制其實都是基於長鏈接來實現的,也就是 http1.1
中所謂的 keep-alive
。在一個TCP連接上可以傳送多個HTTP請求和響應,減少了建立和關閉連接的消耗和延遲。
HTTP是無狀態的,也就是說,瀏覽器和伺服器每進行一次HTTP操作,就建立一次連接,但任務結束就中斷連接。如果客戶端瀏覽器訪問的某個HTML或其他類型的Web頁中包含有其他的Web資源,如JavaScript文件、圖像文件、CSS文件等;當瀏覽器每遇到這樣一個Web資源,就會建立一個HTTP會話
HTTP1.1和HTTP1.0相比較而言,最大的區別就是HTTP1.1預設支持持久連接(最新的 http1.0 可以顯示的指定 keep-alive),但還是無狀態的,或者說是不可以信任的。
在向客戶發送所請求文件的同時,伺服器並沒有存儲關於該客戶的任何狀態信息。即便某個客戶在幾秒鐘內再次請求同一個對象,伺服器也不會響應說:自己剛剛給它發送了這個對象。相反,伺服器重新發送這個對象,因為它已經徹底忘記早先做過什麼。既然HTTP伺服器不維護客戶的狀態信息,我們於是 說HTTP是一個無狀態的協議(stateless protocol)。
基於http協議的長連接減少了請求,減少了建立連接的時間,但是每次交互都是由客戶端發起的,客戶端發送消息,服務端才能返回客戶端消息。因為客戶端也不知道服務端什麼時候會把結果準備好,所以客戶端的很多請求是多餘的,僅是維持一個心跳,浪費了帶寬。
WebSocket
WebSocket簡介
WebSocket 協議在2008年誕生,2011年成為國際標準。所有瀏覽器都已經支持了。WebSocket通信協議於2011年被IETF定為標準RFC 6455,並被RFC7936所補充規範。
關於HTML5的故事很多人都是知道的,w3c放棄了HTML,然後有一群人(也有說是這些人供職的公司,不過官方的文檔上是說的個人)創立了WHATWG組織來推動HTML語言的繼續發展,同時,他們還發展了很多關於Web的技術標準,這些標準不斷地被官方所接受。WebSocket就屬於WHATWG發佈的Web Application的一部分(即HTML5)的產物。
它的最大特點就是,伺服器可以主動向客戶端推送信息,客戶端也可以主動向伺服器發送信息,是真正的雙向平等對話,屬於伺服器推送技術的一種。
建立在 TCP 協議之上,伺服器端的實現比較容易。
與 HTTP 協議有著良好的相容性。預設埠也是80和443,並且握手階段採用 HTTP 協議,因此握手時不容易屏蔽,能通過各種 HTTP 代理伺服器。
數據格式比較輕量,性能開銷小,通信高效。
可以發送文本,也可以發送二進位數據。
沒有同源限制,客戶端可以與任意伺服器通信。
協議標識符是ws(如果加密,則為wss),伺服器網址就是 URL。
其中 Upgrade: websocket Connection: Upgrade
告訴伺服器我們發起的是一個 WebSocket
請求。Sec-WebSocket-Key
是一個 Base64encode
的值,這個是瀏覽器隨機生成的,驗證伺服器是不是真的是Websocket助理。
然後,Sec_WebSocket-Protocol
是一個用戶定義的字元串,用來區分同URL下,不同的服務所需要的協議。
最後,Sec-WebSocket-Version
是告訴伺服器所使用的WebsocketDraft(協議版本)。
HTML5 Web Socket API
詳細介面文檔:MDN WebSocket
創建對象:
var ws = new WebSocket(url,name);
url為WebSocket伺服器的地址,name為發起握手的協議名稱,為可選擇項。
發送文本消息:
ws.send(msg);
msg為文本消息,對於其他類型的可以通過二進位形式發送。
接收消息:
ws.onmessage = (function(){...})();
錯誤處理:
ws.onerror = (function(){...})();
關閉連接:
ws.close();
我們藉助這個測試介面 wss://echo.websocket.org
來做一個小demo。
公用html(下麵的代碼基本也是這個結構):
<h1>客戶端簡單例子</h1>
<i>這裡我們走Kaazing WebSocket為我們提供的介面,這個介面將完整返回我們所發送的數據。</i>
<p>狀態:<strong id="state"></strong></p>
<p>返回數據:<strong id="msg"></strong></p>
<input id="sendText" type="text" name="">
<button id="sendBtn">發送</button>
<button id="closeBtn">關閉</button>
JS:
var show = document.getElementById('state'),
msg = document.getElementById('msg'),
st = document.getElementById('sendText'),
sb = document.getElementById('sendBtn');
if ("WebSocket" in window) {
var ws = new WebSocket('wss://echo.websocket.org');
ws.onopen = function(e) {
show.innerText = 'WebSocket連接成功~';
ws.send('Hello WebSockets!');
};
ws.onmessage = function(e) {
msg.innerText = e.data;
};
ws.onclose = function(e) {
show.innerText = 'WebSocket連接關閉~';
}
sb.addEventListener('click',function(){
ws.send(st.value);
})
}else{
alert('你的瀏覽器不支持WebSocket');
}
nodejs-websocket
nodejs-websocket是一個nodeJs的模塊,我們可以用它來輕易地為我們之前的代碼單獨搭建一個WebSocket的nodeJs服務端。
yarn add nodejs-websocket
var ws = require("nodejs-websocket")
// Scream server example: "hi" -> "HI!!!"
var server = ws.createServer(function (conn) {
console.log("New connection")
conn.on("text", function (str) {
console.log("Received "+str)
conn.sendText(str.toUpperCase()+"!!!")
})
conn.on("close", function (code, reason) {
console.log("Connection closed")
})
}).listen(8001)
Socket.io
在某種程度上,socket.io就是websocket,其實socket.io與websocket不是一回事,而且websocket可以說是socket.io的一個子集,socket.io的底層實現其實有5種方式,websocket只是其中一種,只不過在預設的情況下,我們建立的socket.io連接,底層也是調用websocket的實例。當我們io.connect()建立一個socket連接的時候,返回的是namespace實例,namespace實例中有個socket實例,當新建一個連接,或者發送一條消息的時候,namespace->socket->transport->websocket(xhrpolling...),其實發送一條消息真正的發送者還是底層的websocket或是xhrpolling或其他的幾種,而socket.io只是一個組織者,當我們需要建立連接的時候,它自己會在其內部挑選一種連接方式,然後實現連接。
Socket.io都實現了Polling中的那些通信機制呢?
- Adobe® Flash® Socket
- AJAX long polling
- AJAX multipart streaming
- Forever Iframe
- JSONP Polling
WebSocket和HTTP和Socket
應用層的協議,WebSocket在現代的軟體開發中被越來越多的實踐,和HTTP有一些相似的地方,而且有人也會把WebSocket和Socket混為一談,那麼他們之間到底有什麼異同呢?
WebSocket和HTTP
我們先看兩個協議的截圖來領會下。
相同點
- 都是基於TCP的應用層協議。
- 都使用Request/Response模型進行連接的建立。
- 在連接的建立過程中對錯誤的處理方式相同,在這個階段WS可能返回和HTTP相同的返回碼。
- 都可以在網路中傳輸數據。
不同點
- WS使用HTTP來建立連接,但是定義了一系列新的header域,這些域在HTTP中並不會使用。
- WS的連接不能通過中間人來轉發,它必須是一個直接連接。
- WS連接建立之後,通信雙方都可以在任何時刻向另一方發送數據。
- WS連接建立之後,數據的傳輸使用幀來傳遞,不再需要Request消息。
- WS的數據幀有序。
WebSocket和Socket
其實就像Java和JavaScript一樣,WebSocket和Socket並沒有太大的關係。
Socket可以有很多意思,和IT較相關的本意大致是指在端到端的一個連接中,這兩個端叫做Socket。對於IT從業者來說,它往往指的是TCP/IP網路環境中的兩個連接端,大多數的API提供者(如操作系統,JDK)往往會提供基於這種概念的介面,所以對於開發者來說也往往是在說一種編程概念。同時,操作系統中進程間通信也有Socket的概念,但這個Socket就不是基於網路傳輸層的協議了。
Socket 其實並不是一個協議。它工作在 OSI 模型會話層(第5層),是為了方便大家直接使用更底層協議(一般是 TCP 或 UDP )而存在的一個抽象層。
Socket是應用層與TCP/IP協議族通信的中間軟體抽象層,它是一組介面。在設計模式中,Socket其實就是一個門面模式,它把複雜的TCP/IP協議族隱藏在Socket介面後面,對用戶來說,一組簡單的介面就是全部,讓Socket去組織數據,以符合指定的協議。
主機 A 的應用程式要能和主機 B 的應用程式通信,必須通過 Socket 建立連接,而建立 Socket 連接必須需要底層 TCP/IP 協議來建立 TCP 連接。建立 TCP 連接需要底層 IP 協議來定址網路中的主機。我們知道網路層使用的 IP 協議可以幫助我們根據 IP 地址來找到目標主機,但是一臺主機上可能運行著多個應用程式,如何才能與指定的應用程式通信就要通過 TCP 或 UPD 的地址也就是埠號來指定。這樣就可以通過一個 Socket 實例唯一代表一個主機上的一個應用程式的通信鏈路了。
而 WebSocket 則不同,它是一個完整的 應用層協議,包含一套標準的 API 。
所以,從使用上來說,WebSocket 更易用,而 Socket 更靈活。
瀏覽器支持
websocket api在瀏覽器端的廣泛實現似乎只是一個時間問題了, 值得註意的是伺服器端沒有標準的api, 各個實現都有自己的一套api, 並且tcp也沒有類似的提案, 所以使用websocket開發伺服器端有一定的風險.可能會被鎖定在某個平臺上或者將來被迫升級。
本文相關的Demo已經放在作者的Github上:小樓蘭的github