HTTP協議是一種無狀態協議,服務端自己不具備識別客戶端的能力,必須藉助外部機制,好比session和cookie,才能與特定客戶端保持對話。這多多少少帶來一些不便,尤爲在服務器端與客戶端須要持續交換數據的場合(好比網絡聊天),更是如此。爲了解決這個問題,HTML5提出了瀏覽器的WebSocket API。javascript
WebSocket的主要做用是,容許服務器端與客戶端進行全雙工(full-duplex)的通訊。舉例來講,HTTP協議有點像發電子郵件,發出後要等待對方回信;WebSocket則是像打電話,服務器端和客戶端能夠同時向對方發送數據,它們之間存在着一條持續打開的數據通道。html
WebSocket協議徹底能夠取代Ajax方法,用來向服務器端發送文本和二進制數據,並且尚未「同域限制」。html5
WebSocket不使用HTTP協議,而是使用本身的協議。瀏覽器發出的WebSocket請求相似於下面的樣子:java
GET / HTTP/1.1 Connection: Upgrade Upgrade: websocket Host: example.com Origin: null Sec-WebSocket-Key: sN9cRrP/n9NdMgdcy2VJFQ== Sec-WebSocket-Version: 13
上面的頭信息顯示,有一個HTTP頭是Upgrade。HTTP1.1協議規定,Upgrade頭信息表示將通訊協議從HTTP1.1轉向該項所指定的協議。「Connection: Upgrade」就表示瀏覽器通知服務器,若是能夠,就升級到webSocket協議。Origin用於驗證瀏覽器域名是否在服務器許可範圍內。Sec-WebSocket-Key則是用於握手協議的密鑰,是base64編碼的16字節隨機字符串。node
服務端的WebSocket迴應則是: web
HTTP/1.1 101 Switching Protocols Connection: Upgrade Upgrade: websocket Sec-WebSocket-Accept: fFBooB7FAkLlXgRSz0BT3v4hq5s= Sec-WebSocket-Origin: null Sec-WebSocket-Location: ws://example.com
服務端一樣用「Connection: Upgrade」通知瀏覽器,須要改協議。Sec-WebSocket-Accept是服務器在瀏覽器提供的Sec-WebSocket-Key字符串後面,添加「258EAFA5-E914-47DA-95CA-C5AB0DC85B11」字符串,然的再取sha-1的hash值。瀏覽器將對這個值進行驗證,以證實確實是目標服務器迴應了webSocket請求。Sec-WebSocket-Location表示通訊的WebSocket網址。ajax
注意:WebSocket協議用ws表示。此外,還有wss協議,表示加密的WebSocket協議,對應HTTPs協議。express
完成握手之後,WebSocket協議就在TCP協議之上,開始傳送數據。npm
WebSocket協議須要服務器支持,目前比較流行的實現是基於node.js的socket.io,更多實現可參閱Wikipedia。至於瀏覽器端,目前主流瀏覽器都支持WebSocket協議(包括IE10+),僅有的例外是手機端的Opera Mini和Android Browser。canvas
瀏覽器端對WebSocket協議的處理,無非就是三件事:
首先,客戶端要檢查瀏覽器是否支持WebSocket,使用的方法是查看window對像是否具備WebSocket屬性。
if (window.WebSocket != undefined) { // 支持 }
而後,開始與服務器創建鏈接(這裏假定服務器就是本機的1740端口,須要使用ws協議)。
if (window.WebSocket != undefined) { var connection = new WebSocket('ws://localhost:1740'); }
創建鏈接之後的WebSocket實例對象(即上面代碼中的connection),有一個readyState屬性,表示目前狀態,能夠取4個值:
握手協議成功之後,readyState就從0變成1,並觸發open事件,這時就能夠向服務器發送信息了。咱們能夠指定open事件的回調函數。
connection.onopen = wsOpen; function wsOpen(event) { console.log('Connected'); }
關閉WebSocket會觸發close事件。
connection.onclose = wsClose; function onClose(event) { var wasClean = event.wasClean; //bool值,鏈接是否關閉了 var code = event.code; //鏈接狀態(數字),由服務端提供 var reason = event.reason; //關閉緣由,由服務端提供 console.log('Closed'); } connection.close();
鏈接創建後,客戶端經過send方法向服務器端發送數據。
connection.send(message);
除了發送字符串,也可使用Blob或ArrayBuffer對象發送二進制數據。
//使用ArrayBuffer發送Canvas圖像數據 var img = canvas_context.getImageData(0, 0, 400, 320); var binary = new Unit8Array(img.data.length); for(var i = 0; i < img.data.length; i++) { binary[i] = img.data[i] } connection.send(binary.buffer); //使用Blob發送文件 var file = document.querySelector('input[type="file"]').files[0]; connection.send(file);
客戶端收到服務器發送的數據,會觸發message事件。能夠經過定義message事件的回調函數,來處理服務端返回的數據。
connection.onmessage = wsMessage; function wsMessage(event) { console.log(event.data); }
上面代碼的回調函數wsMessage的參數爲事件對象evnet,該對象的data屬性包含了服務器返回的數據。
若是出現錯誤,瀏覽器會觸發WebSocket實例對象的error事件。
connection.onerror = wsError; function wsError(event) { console.log('Error: ' + event.data); }
服務器端須要單獨部署處理WebSocket的代碼。下面用node.js搭建一個服務器環境。
var http = require('http'); var server = http.createServer(function(request, response){});
假設監聽1740端口。
server.listen(1740, function() { console.log((new Date()) + ' Server is listening on port 1740'); });
接着啓動WebSocket服務器。這須要加載websocket庫,若是沒有安裝,能夠先使用命令安裝。
var WebSocketServer = require('websocket').server; var wsServer = new WebSocketServer({ httpServer: server });
WebSocket服務器創建request事件的回調函數。
var connection; wsServer.on('request', function(req){ connect = req.accept('echo-protocol', req.origin); });
上面代碼的回調函數接受一個參數req,表示request請求對象。而後,在回調函數內部,創建WebSocket鏈接Connection。接着,就要對connection的message事件指定回調函數。
wsServer.on('request', function(r) { connection = req.accept('echo-protocol', req.origin); connection.on('message', function(message) { var msgString = message.utf8Data; connection.sendUTF(msgString); }); });
最後,監聽用戶的disconnect事件。
connection.on('close', function(reasonCode, description) { console.log(connection.remoteAddress + ' disconnected'); });
Socket.io是目前最流行的WebSocket實現,包括服務器和瀏覽器兩個部分。它不只簡化了接口,使得操做更容易,並且對於那些不支持WebSocket的瀏覽器,會自動降爲Ajax鏈接,最大限度地保證了兼容性。
第一步,在服務器端的項目根目錄下,安裝socket.io模塊。
npm install socket.io
第二步,在根目錄下創建app.js,並寫入如下代碼(使定使用了Express框架)。
var app = require('express')(); var server = require('http').createServer(app); var io = require('socket.io').listen(server); server.listen(80); app.get('/', function (req, res) { res.sendfile(__dirname + '/index.html'); });
上面代碼表示,先創建並運行HTTP服務器。Socket.io的運行創建在HTTP服務器之上。
第三步,將Socket.io插入客戶端網頁。
<script src="/socket.io/socket.io.js"></script>
而後,在客戶端腳本中,創建WebSocket鏈接。
var socket = io.connect('http://localhost');
因爲本例假定WebSocket主機與客戶端是同一臺機器,因此connect方法的參數是http://localhost。接着,指定news事件(即服務器端發送news)的回調函數。
socket.on('news', function (data){ console.log(data); });
最後,用emit方法向服務器端發送信號,觸發服務器端的anotherNews事件。
請注意:emit方法能夠取代Ajax請求,而on方法指定的回調函數,也等同於Ajax的回調函數。
第四步,在服務器端的app.js中加入如下代碼。
io.sockets.on('connection', function (socket) { socket.emit('news', { hello: 'world' }); socket.on('anotherNews', function (data) { console.log(data); }); });
上面代碼的io.sockets.on方法指定connect事件(WebSocket鏈接創建)的回調函數。在回調函數中,用emit方法向客戶端發送數據,觸發客戶端的news事件。而後,再用on方法指定服務器端anotherNews事件的回調函數。
[1] Ryan Stewart, Real-time data exchage in HTML5 with WebSockets
[2] Malte Ubl & Eiji Kitamura, WEBSOCKETS 簡介:將套接字引入網絡
[3] Jack Lawson, WebSockets: A Guide
[4] Michael W., Starting with Node and Web Sockets
[5] Jesse Cravens, Introduction to WebSockets
[6] Matt West, An Introduction to Websockets
[7] Maciej Sopyło, Node.js: Better Performance With Socket.IO and doT
[8] Jos Dirksen, Capture Canvas and WebGL output as video using websockets
[9] Ruanyf, WebSocket