WebSocket 技術已經逐漸成熟,在生產環境下也已經帶給咱們很是多的便利。本文首先會努力闡明 WebSocket 的基本原理,而後會結合實際敘述如何使用它。javascript
首先須要明確的是 WebSocket 的定位。WebSocket 是創建在 HTTP 基礎上,爲客戶端與服務端之間提供文本和二進制數據的全雙工通訊的技術。這裏有幾個地方須要注意:html
WebSocket 是爲了知足基於 Web 的日益增加的實時通訊需求而產生的。在傳統的 Web 中,要實現實時通訊(好比網頁版 QQ),經常使用的方式是經過輪詢。在特定的時間間隔,由瀏覽器向服務器發送 HTTP 請求,而後將最新的數據返回給瀏覽器。這樣的方法最明顯的缺點就是須要不斷的發送請求,不只佔用了寬帶,也佔用服務器 CPU 資源(沒有信息也要接受請求)。java
如此看來,WebSocket 的出現相比於純 HTTP 實現實時通訊至少有兩個優點:node
然而,WebSocket 在實現高效實時通訊的過程卻也再也不享有在一些本由瀏覽器提供的服務和優化,如狀態管理、壓縮、緩存等。之後瀏覽器廠商是否是會針對 WebSocket 做出一些服務和優化不得而知,但至少如今 WebSocket 還徹底不足以撼動 HTTP 的地位。總的來講,WebSocket 彌補了 HTTP 在某些通訊領域的短板,但毫不可能徹底取代 HTTP。react
WebSocket 本來是 HTML5 標準的一部分,隨其發展壯大,如今已逐漸變成了獨立的協議標準。在客戶端,HTML5 提供了一套很是簡潔的 API 供咱們使用。git
WebSocket(url: string, protocols?: string[] | string)
複製代碼
ws
或者 wss
。一共就只有兩個。github
close(code?: number, reason?: string): void
複製代碼
關閉WebSocket鏈接或中止正在進行的鏈接請求。若是鏈接的狀態已是closed,這個方法不會有任何效果。web
send(data: string | ArrayBuffer | Blob): void
複製代碼
經過WebSocket鏈接向服務器發送數據。express
事件聽從原生 Javascript 的兩種寫法:npm
onevent = handler
addEventListener(event, handler)
支持的事件有 open
,message
,close
和 error
。注意 message
事件在接收到全部數據時出發。
WebSocket 對象的屬性用來描述通訊細節和狀態。
binaryType: string
表示被傳輸二進制的內容的類型。取值應當是 'blob'
或者 'arraybuffer'
var ws = new WebSocket('wss://example.com/socket');
ws.binaryType = "arraybuffer"; // 強制將接收的二進制數據轉爲 ArrayBuffer 類型
ws.onmessage = function(msg) {
if(msg.data instanceof ArrayBuffer) {
processArrayBuffer(msg.data);
} else {
processText(msg.data);
}
}
複製代碼
bufferedAmount: number
調用 send()
方法將多字節數據加入到隊列中等待傳輸,可是還未發出。該值會在全部隊列數據被髮送後重置爲 0。而當鏈接關閉時不會設爲0。若是持續調用 send()
,這個值會持續增加。只讀。
protocol: string
一個代表服務器選定的子協議名字的字符串。這個屬性的取值爲構造器傳入的 protocols
參數或者之一。
readyState: number
鏈接的當前狀態。取值是 Ready state constants之一。 只讀。
url: string
傳入構造器的URL。它必須是一個絕對地址的URL。只讀。
// Create WebSocket connection.
const socket = new WebSocket('ws://localhost:8080');
// Connection opened
socket.addEventListener('open', function (event) {
socket.send('Hello Server!');
});
// Listen for messages
socket.addEventListener('message', function (event) {
console.log('Message from server', event.data);
});
複製代碼
然而以上只是在客戶端的 WebSocket 實現,若是沒有服務端的配合,WebSocket 是不能進行通訊的。關於 WebSocket 的在服務端的實現有多種方案,下一節會在 Node.js 上進行實現。
以上部分是我的簡單總結,若是想詳細瞭解其 API 能夠查閱 MDN 上的文檔。
使用 WebSocket 其實並不複雜,本節將結合 socket.io 模塊進行說明。接下來看看如何構建一個最簡單的 WebSocket 應用。
第一步,安裝依賴庫:
npm install socket.io express
複製代碼
第二步,構建客戶端 /public/index.html:
<body>
<script src="/socket.io/socket.io.js"></script>
<script> const socket = io('http://localhost:3231'); socket.on('connect', () => { socket.emit('greet', 'Hello, websocket!'); socket.on('uppergreet', data => console.log(data)); }); </script>
</body>
複製代碼
注意上面引入 socket.io 的方式,不用懷疑路徑是否正確,由於 socket.io 會自動引入相應客戶端部分代碼,而且暴露一個 io
全局變量。
第三步,構建服務端 server.js:
const express = require('express');
const app = express();
const http = require('http').Server(app);
const io = require('socket.io')(http);
const port = process.env.PORT || 3000;
app.use(express.static(__dirname + '/public'));
io.on('connection', socket => {
socket.on('greet', data => socket.emit('uppergreet', data.toUpperCase()));
});
http.listen(port, () => console.log('listening on port ' + port));
複製代碼
最後,運行 node server.js
並用瀏覽器訪問 http://localhost:3000
,打開控制檯將會看到輸出:
HELLO, WEBSOCKET!
複製代碼
能夠看到,使用 socket.io 構建 WebSocket 應用相比上一節提到的 WebSocket API 在寫法上仍是有比較大的區別的。
除了本節提到的 socket.io 模塊,在 Node.js 上還有多種實現,好比 websocket 和 nodejs-websocket,他們不只實現了服務端的 WebSocket 通訊協議,同時也對上一節提到的客戶端的 WebSocket API 進行了或多或少的封裝,功能完善了不少,在實際工做中已經獲得了普遍應用,我的比較傾向於 socket.io 庫。
WebSocket 適用於須要高效實時通訊的場景,好比網頁聊天,對戰遊戲等。本節將使用 socket.io 模塊進行說明,在 socket.io 的官網上有多個典型使用場景,這裏咱們使用官網給出的較爲簡單的一個畫板案例進行說明。 socket.io 官網還給出一個使用 jQuery 開發的聊天應用示例,我根據其最終效果實現了一個 react 版本。
畫板案例的關鍵之處是多個客戶端能夠同時在一張畫板上進行繪畫,繪畫結果全部客戶端都能實時看到,(這個場景是否是和你畫我猜的遊戲相似?一人畫圖,多個用戶能實時看到畫圖結果),這裏使用 WebSocket 是很是合適的。
首先是服務端的代碼 index.js,和上一節的代碼很是相似:
const express = require('express');
const app = express();
const http = require('http').Server(app);
const io = require('socket.io')(http);
const port = process.env.PORT || 3000;
app.use(express.static(__dirname + '/public'));
io.on('connection', socket => {
socket.on('drawing', data => socket.broadcast.emit('drawing', data));
});
http.listen(port, () => console.log('listening on port ' + port));
複製代碼
接下來運行以下命令:
node index.js
複製代碼
而後打開兩個瀏覽器窗口都訪問 http://localhost:3000
,而後在一個瀏覽器內進行繪畫,另一個瀏覽器也能實時看到結果。
能夠看到服務端代碼很是簡潔,在這裏關鍵步驟就是 connection
事件的處理方法,在該方法內部 socket
會監聽 drawing
事件,在監聽到 drawing
事件以後的回調函數中,再次廣播發送 drawing
事件,廣播發送的意思就是向全部連上服務端的客戶端發送。
而後是客戶端的關鍵代碼 /public/main.js:
var socket = io();
socket.on('drawing', onDrawingEvent);
var canvas = document.getElementsByClassName('whiteboard')[0];
canvas.addEventListener('mousedown', onMouseDown, false);
canvas.addEventListener('mouseup', onMouseUp, false);
canvas.addEventListener('mouseout', onMouseUp, false);
canvas.addEventListener('mousemove', throttle(onMouseMove, 10), false);
複製代碼
上面代碼中的 onDrawingEvent
、mouseUp
和 onMouseMove
方法內部都調用了一個關鍵的 drawLine
方法:
function drawLine(x0, y0, x1, y1, color, emit){
// ... 繪製 canvas ...
if (!emit) return;
socket.emit('drawing', {
// ... 繪製 canvas 的狀態信息 ...
});
}
複製代碼
drawLine
方法根據傳入的 emit
位來判斷是否要向服務器發送 drawing
事件,若是是客戶端自己進行的繪製,則會向服務端發送 drawing
事件,不然不會。
以上就是實現一個多人同時繪畫的實時應用的總體思路,能夠看到當咱們使用 WebSocket 進行通訊,尤爲是使用相似 socket.io 這種封裝程度很高的工具庫時,通訊過程是很是簡潔的,咱們把大部分精力放在業務邏輯中便可。
WebSocket 技術的發展已經讓開發者無須瞭解協議內部便可實現實時通訊邏輯,尤爲是配合使用 Node.js 做爲服務端時,相似 socket.io 的工具庫爲服務端和客戶端都提供了很是方便的調用方法,上手很是快。若是在工做中須要構建高效實時應用,WebSocket 將會是不二選擇。