咱們上一篇《基於 WebSocket 實現 WebGL 3D 拓撲圖實時數據通信同步(一)》主要講解了如何搭建一個實時數據通信服務器,客戶端與服務端是如何通信的,相信經過上一篇的講解,再配合上數據庫的數據儲存,咱們就能夠實現一個簡易版的 Web 聊天工具了,有空的朋友能夠本身嘗試下實現,那麼咱們今天的主要內容真的是實現 WebGL 3D 拓撲圖實時數據通信了,請你們接着往下看。html
有了前面的知識儲備,咱們就能夠來真正實現咱們 3D 拓撲圖組件上節點位置信息的實時數據同步了,毋庸置疑,節點的位置信息必須是在服務端統籌控制,才能達到實時數據同步,也就是說,咱們必須在服務端建立 DataModel 來管理節點,建立 ForceLayout 彈力佈局節點位置,並在節點位置改變的過程當中,實時地將位置信息推送到客戶端,讓每一個客戶端都更新各自頁面上面的節點位置。node
在服務端咱們該如何建立 HT 的 DataModel 和 ForceLayout 呢?其實也很簡單,咱們能夠看看下面的代碼:數據庫
var ht = global.ht = this.ht = require('../../../build/ht-debug.js').ht,
dataModel = new ht.DataModel(), reloadModel = require("../util.js").reloadModel; reloadModel(dataModel, { A: 3, B: 5 }); require("../../../build/ht-forcelayout-debug.js"); var forceLayout = new ht.layout.Force3dLayout(dataModel); forceLayout.onRelaxed = function() { var result = {}; dataModel.each(function(data) { if (data instanceof ht.Node) { result[data.getTag()] = data.p3(); } }); io.emit('result', result); }; forceLayout.start();
咱們經過 require 將非 Node.js 模塊包引入到程序中,並加以使用。在上面的代碼中,咱們確實建立了 HT 的拓撲節點,是經過 util.js 文件中的 relowdModel 方法建立的節點,那這個文件中究竟是怎麼實現建立 HT 拓撲節點的呢?接下來就來看看具體的實現:json
function createNode(dataModel, id){
var node = new ht.Node(); node.setId(id); node.setTag(id); node.s3(40, 40, 40); node.s({ 'shape3d': 'sphere', 'note': id, 'note.position': 17, 'note.background': 'yellow', 'note.color': 'black', 'note.autorotate': true, 'note.face': 'top' }); dataModel.add(node); return node; } function createEdge(dataModel, source, target){ var edge = new ht.Edge(source, target); edge.s({ 'edge.width': 10, 'shape3d.color': '#E74C3C', 'edge.3d': true }); dataModel.add(edge); return edge; } function reloadModel(dataModel, info){ dataModel.clear(); var ip = "192.168.1."; var count = 0; var root = createNode(dataModel, ip + count++); for (var i = 0; i < info.A; i++) { var iNode = createNode(dataModel, ip + count++); createEdge(dataModel, root, iNode); for (var j = 0; j < info.B; j++) { var jNode = createNode(dataModel, ip + count++); createEdge(dataModel, iNode, jNode); } } } this.reloadModel = reloadModel;
在這個文件中,封裝了建立節點的方法 createNode,和建立連線的方法 createEdge,最後是經過 reloadModel 方法將前面的兩個方法鏈接起來,在這個文件的最後,咱們能夠看到,只公開了 reloadModel 的函數接口。服務器
固然光這些是不夠的,這些還不可以達成實時數據通信的功能,咱們還須要監聽和派發一些事件纔可以達到效果,那麼咱們都監聽了什麼藉口,派發了什麼事件呢?socket
io.on('connection', function(socket) {
socket.emit('ready', dataModel.serialize(0)); console.log('a user connected'); socket.on('disconnect', function() { console.log('user disconnected'); }); socket.on('moveMap', function(moveMap) { dataModel.sm().cs(); for (var id in moveMap) { var data = dataModel.getDataByTag(id); if (data) { data.p3(moveMap[id]); dataModel.sm().as(data); } } }); });
上面那串代碼是咱們的事件監聽,咱們經過監聽 moveMap 的事件,並獲取從客戶端傳遞上來的移動的節點座標信息,根據參數的內容,咱們將其改變服務端的 DataModel 中對應節點的座標,改變後 ForceLayout 就會根據當前的狀態去調整整個拓撲上全部節點的位置。那麼在調節的過程當中,咱們是怎麼知道 ForceLayout 是正在調整的呢?在前面介紹如何在 Node.js 上面建立 HT 相關的組件時貼出來的代碼中就告訴我麼怎麼作了。 函數
在建立 ForceLayout 組件的代碼後面,緊跟着就是重載 ForceLayout 組件的 onRelaxed 方法,每次佈局玩後,都會調用這個方法,這樣咱們就能夠在這個方法中,編輯獲取到 DataModel 中的全部節點的當前位置,並經過 io.emit 方法通知給全部的客戶端,讓客戶端去實時更新對應節點的座標位置。工具
可是還有一個問題,咱們要怎麼樣讓客戶端顯示的節點和服務端上的節點一一對應呢?首先不能讓客戶端本身建立節點,咱們的作法其實也很簡單,雖然不能保證客戶端的節點 ID 會和服務端的節點 ID 如出一轍,可是咱們能夠保證其餘關鍵屬性是同樣,由於咱們利用了 HT 的序列化功能,當有客戶端鏈接到服務器時,就會向客戶端派發 ready 事件,將 DataModel 序列化的結果返回到客戶端,讓客戶端反序列化,從而達到數據基本一致的效果。佈局
那麼客戶端和服務端的節點是如何保持一一對應的呢?首先咱們得了解 HT 在獲取節點對象上提供了幾個方法,熟悉的朋友應該知道,有 getDataById 和 getDataByTag 兩個方法,其中 ID 是 HT 系統本身維護的屬性,Tag 是提供給用戶本身維護其惟一性的屬性,通常不建議使用 ID 做爲業務上面的惟一標識,由於在序列化和反序列化時候可能會有細微的差異,很難保證反序列話後的節點 ID 和序列化前的 ID 是同樣的。所以在本文中,咱們是經過 Tag 屬性來控制服務器和客戶端的節點一一對應的。ui
接下來咱們來看看客戶端的實現吧:
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title></title>
<script src="/socket.io/socket.io.js"></script>
<script src="/build/ht-debug.js"></script>
<script>
var socket = io(); var init = function() { var dm = window.dataModel = new ht.DataModel(), sm = dm.sm(), g3d = new ht.graph3d.Graph3dView(dm); g3d.setGridSize(100); g3d.setGridGap(100); g3d.setGridVisible(true); g3d.addToDOM(); var moveNodes = null; g3d.mi(function(evt){ if ( evt.kind === 'beginMove'){ moveNodes = sm.getSelection(); } else if (evt.kind === 'betweenMove'){ moveMap = {}; g3d.sm().each(function(data){ if(data instanceof ht.Node){ moveMap[data.getTag()] = data.p3(); console.info(data.p3()); } }); socket.emit('moveMap', moveMap); } else if (evt.kind === 'endMove') { moveNodes = null; } }); socket.on('ready', function(json) { dm.clear(); dm.deserialize(json); }); socket.on('result', function (result) { for(var id in result){ var data = dm.getDataByTag([id]); if (!data) continue; if (moveNodes && moveNodes.indexOf(data) >= 0) continue; data.p3(result[id]); } }); }; </script> </head> <body onload="init();"> </body> </html>
代碼並不長,我來介紹下具體的實現。首先是建立 3D 拓撲圖組件,並作一些設置,讓場景上出現線條,而後就是監聽拓撲圖上面的操做,當監聽到 betweenMove 時,或許當前被移動的節點位置信息,向服務器派發該信息;接下來是監聽服務器的 ready 事件,在事件回調中作了反序列化的操做,可是在反序列化以前,爲何要將場景中的全部節點 Clear 掉呢?是由於頁面有多是斷線重連,若是是斷線重連的話,沒有將場景中的節點都 Clear 掉的話,反序列化後就會有節點重疊了,並且 Tag 屬性也再也不是惟一的了,因此這時候操做節點的話,將會很混亂;最後呢,就是監聽服務器的 result 事件,在事件的回調中,跟新回調參數中對應節點的位置信息,可是其中作了些過濾,這是過濾正在移動的節點,由於正在移動的節點位置是認爲控制的,全部不須要更新其節點位置信息。
那麼實時數據通信系列到這裏就介紹完了,若有什麼問題,歡迎批評指正。