業務場景:後端更新數據推送到客戶端(Java部分使用Tomcat服務器)。前端
後端推送數據的解決方案有不少,好比輪詢、Comet、WebSocket。node
1. 輪詢對於後端來講開發成本最低,就是按照傳統的方式處理Ajax請求並返回數據,在學校的時候實驗室的項目一直都採用輪詢,由於它最保險也最容易實現。但輪詢帶來的通訊資源的浪費是沒法忽視的,不管數據是否改變,都照常發送請求並響應,並且每次HTTP請求都帶有很長的頭部信息。web
2. Comet的概念是長鏈接,客戶端發送請求後,後端將鏈接保持下來,直到鏈接超時或後端返回數據時再從新創建鏈接,有效的將通訊資源轉移到了服務器上,實際消耗的是服務器資源。json
3. WebSocket是HTML5提供的一種全雙工通訊技術,經過「握手」實現客戶端與服務器之間的通訊,實時性好,攜帶的頭部也較小,目前支持的瀏覽器以下:後端
理想的狀況是採起WebSocket與Comet結合的方式,對IE8等瀏覽器採起Comet方式,作降級處理。可是這樣一來,後端須要實現兩種處理請求的邏輯,即WebSocket與Comet。因此,本文加入Node.js,之因此這樣作,是將處理WebSocket(或Comet)的邏輯轉移到Node.js部分,不給後端「添麻煩」,由於在實際狀況下,前端開發人員推進後端開發人員並不容易。Node.js做爲瀏覽器與Java業務邏輯層通訊的中間層,鏈接客戶端與Tomcat,經過Socket與Tomcat進行通訊(是Socket,不是WebSocket,後端須要實現Socket接口。瀏覽器
在客戶端,WebSocket與Comet經過Socket.io實現,Socket.io會針對不一樣的瀏覽器版本或者不一樣客戶端選擇合適的實現方式(WebSocket, long pull..),Socket.io的引入讓處理WebSocket(或長鏈接)變的很容易。Socket.io緩存
客戶端引入socket.io:服務器
1 <script src="static/js/socket.io.js"></script>
客戶端JavaScript代碼:session
1 var socket = io.connect('127.0.0.1:8181'); 2 // 發送數據至服務器 3 socket.emit('fromWebClient', jsonData); 4 // 從服務器接收數據 5 socket.on('pushToWebClient', function (data) { 6 // do sth. 7 });
Node.js服務器代碼:app
1 var http = require('http'), 2 app = http.createServer().listen('8181'), 3 io = require('socket.io').listen(app); 4 io.sockets.on('connection', function (socketIO) { 5 // 從客戶端接收數據 6 socketIO.on('fromWebClient', function (webClientData) { 7 // do sth. 8 }); 9 // 客戶端斷開鏈接 10 socketIO.on('disconnect', function () { 11 console.log('DISCONNECTED FROM CLIENT'); 12 }); 13 // 向客戶端發送數據 14 socketIO.emit('pushToWebClient', jsonData); 15 });
創建好客戶端同Node.js服務器的鏈接只是第一步,下面還須要創建Node.js服務器與Java業務邏輯層的聯繫。這時,Node.js服務器則做爲客戶端,向Tomcat發送TCP鏈接請求。鏈接成功後,Node.js服務器和Tomcat創建了一條全雙工的通道,並且是惟一的一條,不論有多少個客戶端請求,都從Node.js服務器轉發至Tomcat;一樣,Tomcat推送過來的數據,也經由Node.js服務器分發至各個客戶端。
這裏存在一個問題,就是在WebSocket鏈接與Socket鏈接都創建好以後,兩次鏈接彼此之間是屏蔽的。Tomcat不知道是哪次WebSocket鏈接發送過來的數據,也不知道是哪一個客戶端發來的數據。固然,Node.js能夠利用session id發送至Tomcat來標識是哪個客戶端,但本文采用的是另一種辦法。
客戶端同Node.js創建WebSocket鏈接時,每一個鏈接都會包含一個實例,這裏稱它爲socketIO。每一個socketIO都有一個id屬性用來惟一標識這個鏈接,這裏稱它爲socket_id。利用socket_id,在Node.js服務器創建一個映射表,存儲每個socketIO與socket_id的映射關係。Node.js服務器發送數據給Tomcat時帶上這個socket_id,再由Java部分進行一系列處理之後封裝好每一個客戶端須要的不一樣數據一併返回,返回的數據裏要有與socket_id的對應關係。這樣,Node.js服務器收到Tomcat發來的數據時,經過前面提到的映射表由不一樣的socketIO分發至不一樣的客戶端。
Node.js服務器代碼:
1 var http = require('http'), 2 net = require('net'), 3 app = http.createServer().listen('8181'), 4 io = require('socket.io').listen(app), 5 nodeServer = new net.Socket(); 6 // 鏈接到Tomcat 7 nodeServer.connect(8007, '127.0.0.1', function() { 8 console.log('CONNECTED'); 9 }); 10 // 存儲客戶端的WebSocket鏈接實例 11 var aSocket = {}; 12 // 同客戶端創建鏈接 13 io.sockets.on('connection', function (socketIO) { 14 // 從客戶端接收數據,而後發送至Tomcat 15 socketIO.on('fromWebClient', function (webClientData) { 16 // 存儲至映射表 17 aSocket[socketIO.id] = socketIO; 18 // 發送至Tomcat的數據中添加socket_id 19 webClientData['sid'] = socketIO.id; 20 // 發送String類型的數據至Tomcat 21 nodeServer.write(JSON.stringify(webClientData)); 22 }); 23 // 客戶端斷開鏈接 24 socketIO.on('disconnect', function () { 25 console.log('DISCONNECTED FROM CLIENT'); 26 }); 27 }); 28 29 // 從Tomcat接收數據 30 nodeServer.on('data', function (data) { 31 var jsonData = JSON.parse(data.toString()); 32 // 分發數據至客戶端 33 for (var i in jsonData.list) { 34 aSocket[jsonData.list[i]['sid']].emit('pushToWebClient', jsonData.list[i].data); 35 } 36 });
上面的代碼省略了一些邏輯,好比Node.js服務器從Tomcat接收的數據分爲兩種,一種是推送過來的數據,另一種是響應請求的數據,這裏統一處理推送過來的數據。
在處理通訊時,Node.js發送至Tomcat的數據是String格式,而從Tomcat接收的數據爲Buffer對象(8進制),須要轉化爲String以後再轉化爲json發送至客戶端。
本文只是給出一個這樣兩次鏈接的簡單例子,具體的業務中須要加入許多東西。既然在項目中引入了Node.js,就須要前端承擔更多的事情,好比對數據的處理、緩存、甚至加入不少業務邏輯。