HTML5新特性之WebSocket

一、概述

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協議的處理,無非就是三件事:

  • 創建鏈接和斷開鏈接
  • 發送數據和接收數據
  • 處理錯誤

2.1 創建鏈接和斷開鏈接

首先,客戶端要檢查瀏覽器是否支持WebSocket,使用的方法是查看window對像是否具備WebSocket屬性。

if (window.WebSocket != undefined) {
    // 支持
}

而後,開始與服務器創建鏈接(這裏假定服務器就是本機的1740端口,須要使用ws協議)。

if (window.WebSocket != undefined) {
    var connection = new WebSocket('ws://localhost:1740');
}

創建鏈接之後的WebSocket實例對象(即上面代碼中的connection),有一個readyState屬性,表示目前狀態,能夠取4個值:

  • 0:正在鏈接
  • 1:鏈接成功
  • 2:正在關閉
  • 3:鏈接關閉

握手協議成功之後,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();

2.2 發送數據和接收數據

鏈接創建後,客戶端經過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屬性包含了服務器返回的數據。

2.3 處理錯誤

若是出現錯誤,瀏覽器會觸發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簡介

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

相關文章
相關標籤/搜索