基于 WebSocket 实现 WebGL 3D 拓扑图实时数据通信同步(二)

咱们上一篇《基于 WebSocket 实现 WebGL 3D 拓扑图实时数据通信同步(一)》主要讲解了如何搭建一个实时数据通信服务器,客户端与服务端是如何通信的,相信经过上一篇的讲解,再配合上数据库的数据储存,咱们就能够实现一个简易版的 Web 聊天工具了,有空的朋友能够本身尝试下实现,那么咱们今天的主要内容真的是实现 WebGL 3D 拓扑图实时数据通信了,请你们接着往下看。html

591709-20160726005316575-127128847.png

有了前面的知识储备,咱们就能够来真正实现咱们 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 的函数接口。segmentfault

固然光这些是不够的,这些还不可以达成实时数据通信的功能,咱们还须要监听和派发一些事件才可以达到效果,那么咱们都监听了什么借口,派发了什么事件呢?服务器

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 相关的组件时贴出来的代码中就告诉我么怎么作了。 socket

在建立 ForceLayout 组件的代码后面,紧跟着就是重载 ForceLayout 组件的 onRelaxed 方法,每次布局玩后,都会调用这个方法,这样咱们就能够在这个方法中,编辑获取到 DataModel 中的全部节点的当前位置,并经过 io.emit 方法通知给全部的客户端,让客户端去实时更新对应节点的坐标位置。函数

可是还有一个问题,咱们要怎么样让客户端显示的节点和服务端上的节点一一对应呢?首先不能让客户端本身建立节点,咱们的作法其实也很简单,虽然不能保证客户端的节点 ID 会和服务端的节点 ID 如出一辙,可是咱们能够保证其余关键属性是同样,由于咱们利用了 HT 的序列化功能,当有客户端链接到服务器时,就会向客户端派发 ready 事件,将 DataModel 序列化的结果返回到客户端,让客户端反序列化,从而达到数据基本一致的效果。工具

那么客户端和服务端的节点是如何保持一一对应的呢?首先咱们得了解 HT 在获取节点对象上提供了几个方法,熟悉的朋友应该知道,有 getDataById 和 getDataByTag 两个方法,其中 ID 是 HT 系统本身维护的属性,Tag 是提供给用户本身维护其惟一性的属性,通常不建议使用 ID 做为业务上面的惟一标识,由于在序列化和反序列化时候可能会有细微的差异,很难保证反序列话后的节点 ID 和序列化前的 ID 是同样的。所以在本文中,咱们是经过 Tag 属性来控制服务器和客户端的节点一一对应的。布局

接下来咱们来看看客户端的实现吧:

<!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 事件,在事件的回调中,跟新回调参数中对应节点的位置信息,可是其中作了些过滤,这是过滤正在移动的节点,由于正在移动的节点位置是认为控制的,全部不须要更新其节点位置信息。

那么实时数据通信系列到这里就介绍完了,若有什么问题,欢迎批评指正。

相关文章
相关标签/搜索