做爲新一代的web標準,HTML5爲咱們提供了不少有用的東西,好比canvas,本地存儲(已經分離出去了),多媒體編程接口,固然還有咱們的WebSocket。WebSocket是HTML5開始提供的一種瀏覽器與服務器間進行全雙工通信(full-duplex)的網絡技術,能夠傳輸基於信息的文本和二進制的數據。它於2011年被IETF定爲標準 RFC 6455,同時WebSocket API也被W3C定爲標準。html
web應用的信息交互過程我想你們或多或少都知道一些,一般是客戶端經過瀏覽器發出一個請求,而後服務器端在接受和審覈請求後,進行處理並將結果返回給客戶端,最後由客戶端的瀏覽器將信息呈現出來。這種通訊機制在信息交互不是特別頻繁的狀況下並無太大的問題,但對於那些實時性要求高、海量數據併發的應用來講,就顯得捉襟見肘了,好比如今常見的網頁遊戲,證券網站,RSS訂閱推送,網頁實時對話,打車軟件等。一般當客戶端準備呈現一些信息時,這些信息在服務器端頗有可能就已通過時了。爲了知足以上那些場景,大佬們研究出來了一些折衷方案,其中最經常使用的就是普通輪詢和Comet技術,而Comet技術實際上就是輪詢的改進,細分起來Comet有兩種實現方式:前端
長輪序是對普通輪詢的改進和提升。普通輪詢簡單來講,就是客戶端每隔必定的時間就向服務器端發送請求,從而以頻繁請求的方式來保持客戶端和服務器端的同步。這種同步方案的最大問題是,客戶端已固定的頻率發送請求時,極可能服務端的數據沒有更新,產生不少無用的網絡傳輸,很是低效。node
爲了減小無效的網絡傳輸,長輪詢對普通輪詢進行了改進和提升,當服務器端沒有數據更新時,連接會保持一段時間的週期,直到數據或狀態發生改變或鏈接時間過時,經過這種機制咱們就能夠減小不少無效的客戶端和服務器間的交互。固然,若是服務器端的數據變動很是頻繁的話,這種機制並無有效的提升性能,和普通輪詢沒有太大的區別,且長輪詢也會耗費更多的資源,好比CPU,內存,帶寬等。web
流技術機制簡單來講就是客戶端的頁面使用一個隱藏的窗口向服務端發出一個長鏈接的請求。服務器接到請求後做出迴應,並不斷更新狀態,以保證客戶端和服務器端的鏈接不過時。經過這種機制就能夠將服務器端的信息不斷傳向客戶端,從而保證信息的時效性。但這種機制對於用戶體驗並不友好,須要針對不一樣的瀏覽器升級不一樣的方案來改進用戶體驗,同時這種機制若是在併發狀況下發生時,會對服務器的資源形成很大壓力。npm
正是出於以上幾種解決方案都有着各自的侷限性,HTML5 WebSocket也就應運而生了,瀏覽器能夠經過JavaScript藉助現有的HTTP協議來向服務器發出WebSocket鏈接的請求,當鏈接創建後,客戶端和服務器端就能夠直接經過TCP鏈接來直接進行數據交換。這是因爲websocket協議本質上就是一個TCP鏈接,因此在數據傳輸的穩定性和傳輸量上有所保證,且相對於以往的輪詢和Comet技術在性能方面也有了長足的進步: 編程
有一點須要注意的是雖然websocket在通訊時須要藉助HTTP,但它本質上和HTTP有着很大的區別:canvas
他們的關係其實就和這張圖表現的同樣,雖然有相交的部分,但依然有着很大的區別:數組
因爲每一個服務器端的語言都有着本身的API,所以首先咱們來討論客戶端的API:瀏覽器
// 建立一個socket實例:
const socket = new WebSocket(ws://localhost:9093')
// 打開socket
socket.onopen = (event) => {
// 發送一個初始化消息
socket.send('Hello Server!')
// 服務器有響應數據觸發
socket.onmessage = (event) => {
console.log('Client received a message',event)
}
// 出錯時觸發,而且會關閉鏈接。這時能夠根據錯誤信息進行按需處理
socket.onerror = (event) => {
console.log('error')
}
// 監聽Socket的關閉
socket.onclose = (event) => {
console.log('Client notified socket has closed',event)
}
// 關閉Socket
socket.close(1000, 'closing normally')
}
複製代碼
是否是感受HTML5 websocket所提供的API賊雞兒簡單,沒錯,就是這麼簡單。但有幾點咱們須要注意:安全
咱們在上面提到過,建立一個socket實例時能夠選填ws和wss來進行通訊協議的肯定。他們兩個其實很像HTTP和HTTPS之間的關係。其中ws表示純文本通訊,而wss表示使用加密信道通訊(TCP+TLS)。那爲啥不直接使用HTTP而要自定義通訊協議呢?這就要從WebSocket的目的提及來,WebSocket的主要功能就是爲了給瀏覽器中的應用與服務器端提供優化的,雙向的通訊機制,但這不表明WebScoket只能侷限於此,它固然還可以用於其餘的場景,這就須要他能夠經過非HTTP協議來進行數據交換,所以WebSocket也就採用了自定義URI模式,以確保就算沒有HTTP,也能進行數據交換。
ws和wss:
注:有些HTTP中間設備有時候可能會不理解WebSocket,而致使各類諸如:盲目鏈接升級,亂修改內容等問題。而WSS就很好的解決了這個問題,它創建了一臺哦端到端的安全通道,這個通道對中間設備模糊了數據,所以中間設備就不能感知到數據,也就沒法對請求作一些特殊處理了。
如下是一個典型的WebSocket發起請求到響應請求的示例:
客戶端到服務端:
GET / HTTP/1.1
Connection:Upgrade
Host:127.0.0.1:8088
Origin:null
Sec-WebSocket-Extensions:x-webkit-deflate-frame
Sec-WebSocket-Key:puVOuWb7rel6z2AVZBKnfw==
Sec-WebSocket-Version:13
Upgrade:websocket
服務端到客戶端:
HTTP/1.1 101 Switching Protocols
Connection:Upgrade
Server:beetle websocket server
Upgrade:WebSocket
date: Thu, 10 May 2018 07:32:25 GMT
Access-Control-Allow-Credentials:true
Access-Control-Allow-Headers:content-type
Sec-WebSocket-Accept:FCKgUr8c7OsDsLFeJTWrJw6WO8Q=
複製代碼
咱們能夠看到,WebSocket協議和HTTP協議乍看並無太大的區別,但細看下來,區別仍是有些的,這實際上是一個握手的http請求,首先請求和響應的,」Upgrade:WebSocket」表示請求的目的就是要將客戶端和服務器端的通信協議從 HTTP 協議升級到 WebSocket協議。從客戶端到服務器端請求的信息裏包含有」Sec-WebSocket-Extensions」、「Sec-WebSocket-Key」這樣的頭信息。這是客戶端瀏覽器須要向服務器端提供的握手信息,服務器端解析這些頭信息,並在握手的過程當中依據這些信息生成一個28位的安全密鑰並返回給客戶端,以代表服務器端獲取了客戶端的請求,贊成建立 WebSocket 鏈接。
當握手成功後,這個時候TCP鏈接就已經創建了,客戶端與服務端就可以直接經過WebSocket直接進行數據傳遞。不過服務端還須要判斷一次數據請求是何時開始的和何時是請求的結束的。在WebSocket中,因爲瀏覽端和服務端已經打好招呼,如我發送的內容爲utf-8 編碼,若是我發送0x00,表示包的開始,若是發送了0xFF,就表示包的結束了。這就解決了黏包的問題。
瀏覽器 支持狀況
Chrome Supported in version 4+
Firefox Supported in version 4+
Internet Explorer Supported in version 10+
Opera Supported in version 10+
Safari Supported in version 5+
複製代碼
簡單來講Socket.IO就是對WebSocket的封裝,而且實現了WebSocket的服務端代碼。Socket.IO將WebSocket和輪詢(Polling)機制以及其它的實時通訊方式封裝成了通用的接口,而且在服務端實現了這些實時機制的相應代碼。也就是說,WebSocket僅僅是Socket.IO實現實時通訊的一個子集。Socket.IO簡化了WebSocket API,統一了返回傳輸的API。傳輸種類包括:
咱們來看一下服務端的Socket.IO基本API:
// 引入socke.io
const io = require('socket.io')(80)
// 監聽客戶端鏈接,回調函數會傳遞本次鏈接的socket
io.on('connection',function(socket)) // 給全部客戶端廣播消息 io.sockets.emit('String',data) // 給指定的客戶端發送消息 io.sockets.socket(socketid).emit('String', data) // 監聽客戶端發送的信息 socket.on('String',function(data)) // 給該socket的客戶端發送消息 socket.emit('String', data) 複製代碼
另外,Socket.IO還提供了一個Node.JS API,它看起來很像客戶端API。因此咱們來看看它的實際應用吧:
// socket-server.js
// 須要使用HTTP模塊來啓動服務器和Socket.IO
const http= require('http'),
const io= require('socket.io')
const server= http.createServer(function(req, res){
// 發送HTML的headers和message
res.writeHead(200,{ 'Content-Type': 'text/html' })
res.end('<p>Hello Socket.IO!<p>')
});
// 在8080端口啓動服務器
server.listen(8080)
// 建立一個Socket.IO實例,並把它傳遞給服務器
const socket= io.listen(server)
// 添加一個鏈接監聽器
socket.on('connection', function(client) {
// 鏈接成功,開始監聽
client.on('message',function(event){
console.log('Received message from client!',event)
})
// 鏈接失敗
client.on('disconnect',function(){
clearInterval(interval)
console.log('Server has disconnected')
})
})
複製代碼
而後咱們就能夠啓動這個文件了:
node socket-server.js
複製代碼
而後咱們就能夠建立一個每秒鐘發送消息到客戶端的發送器了;
var interval= setInterval(function() {
client.send('This is a message from the server,hello world' + new Date().getTime());
},1000);
複製代碼
注:須要注意的是,若是咱們想在前端使用socket.IO,咱們須要下載這個:
npm install socket.io-client --save
複製代碼
而後再鏈接網絡:
import io from 'socket.io-client'
const socket = io('ws://localhost:8080')
複製代碼
圖片拍攝於:廣州中山記念堂