postgres+socket.io+nodejs实时地图应用实践

简介: nodejs一直以异步io著称,其语言特性尤其擅长于在realtime应用中,如聊天室等。在进行实时应用开发时,必不可少的需要用到 socket.io库,可以说,nodejs+socket.io在实时应用中具有较好的表现能力。

nodejs一直以异步io著称,其语言特性尤其擅长于在realtime应用中,如聊天室等。在进行实时应用开发时,必不可少的需要用到 socket.io库,可以说,nodejs+socket.io在实时应用中具有较好的表现能力。
  本文既然选择以实时地图应用做个小例子,那么选择经典的PostgreSQL/PostGIS作为地图的数据库。希望实现的是模拟数据库数据插入了新的GPS坐标,而一旦数据发生改变,立刻将插入的GPS坐标广播到服务端,服务端广播到所有的客户端地图上,进行定位展示。早期作者使用的是redis的广播/订阅机制,最近发现Pg数据库的listen/notify也具备这种消息传递机制。
  本文主要的socke.io广播/订阅参考官网,Pg的listen/notify自行谷歌,作者仅简述一下自己如何考虑应用的。

一 服务器端

        var fs = require('fs');
    var http = require('http');
    var socket = require('socket.io');
    var pg = require('pg');
    var util=require('util');
    
    var constr=util.format('%s://%s:%s@%s:%s/%s', 'postgres','postgres','123456','192.168.43.125',5432,'Test');
    var server = http.createServer(function(req, res) {
        res.writeHead(200, { 'Content-type': 'text/html'});
        res.end(fs.readFileSync(__dirname + '/index.html'));
    }).listen(8081, function() {
        console.log('Listening at: http://localhost:8081');
    });

    var pgClient = new pg.Client(constr);//数据库连接
    var socketio=socket.listen(server);//socketio
    socketio.on('connection', function (socketclient) {
        console.log('已连接socket:');
        //socketclient.broadcast.emit('GPSCoor', data.payload);//广播给别人
        //socketclient.emit('GPSCoor', data.payload);//广播给自己
    
    });
    var sql = 'LISTEN gps'; //监听数据库的gps消息
    var query = pgClient.query(sql);//开始数据库消息监听
        //数据库一旦获取通知,将通知消息通过socket.io发送到各个客户端展示。
    pgClient.on('notification', function (data) {
        console.log(data.payload);
        //socketio.sockets.emit('GPSCoor', data.payload); //与下面的等价
        socketio.emit('GPSCoor', data.payload);//广播给所有的客户端
            
    });
    pgClient.connect();
    

二 数据库端

建立一个测试表如下:

create table t_gps(
          id serial not null,
          geom geometry(Point,4326),
          constraint t_gps_pkey primary key (id)
);
--建立索引
create index t_gps_geom_idx on t_gps using gist(geom);

对表的增删改建立一个触发器,触发器中发送变化数据出去:

CREATE OR REPLACE FUNCTION process_t_gps() RETURNS TRIGGER AS $body$
    DECLARE
        rec record;
    BEGIN
        IF (TG_OP = 'DELETE') THEN
            --插入的GPS都是4326的经纬度,我们将在3857的谷歌底图上显示数据,发送转换后的3857出去
            select TG_OP TG_OP,OLD.id,ST_AsText(ST_Transform(OLD.geom,3857)) geom into rec;
            perform pg_notify('gps',row_to_json(rec)::text);
            RETURN OLD;
        ELSIF (TG_OP = 'UPDATE') THEN 
            select TG_OP TG_OP,NEW.id,ST_AsText(ST_Transform(NEW.geom,3857)) geom into rec;
            perform pg_notify('gps',row_to_json(rec)::text);
            RETURN NEW;
        ELSIF (TG_OP = 'INSERT') THEN
            select TG_OP TG_OP,NEW.id,ST_AsText(ST_Transform(NEW.geom,3857)) geom into rec;
            perform pg_notify('gps',row_to_json(rec)::text);
            RETURN NEW;
        END IF;
        RETURN NULL;
    END;
$body$ LANGUAGE plpgsql;

CREATE TRIGGER T_GPS_TRIGGER
AFTER INSERT OR UPDATE OR DELETE ON T_GPS
    FOR EACH ROW EXECUTE PROCEDURE process_t_gps();

三 客户端

<html>
<head>
    <meta charset='utf-8'>
    <title>实时地图应用</title>
    <link rel="stylesheet" href="http://openlayers.org/en/v3.18.2/css/ol.css" type="text/css">
    <script src="http://openlayers.org/en/v3.18.2/build/ol.js"></script>
    <script src="/socket.io/socket.io.js"></script>
    <script>
            var wktform=new ol.format.WKT();//wkt解析
            var gpsSource=new ol.source.Vector();
            function init(){
                var gpsLayer=new ol.layer.Vector({
                    source:gpsSource,
                    style:new ol.style.Style({
                        image: new ol.style.Icon(({
                            anchor: [0.5, 1],
                            src: 'http://openlayers.org/en/v3.18.2/examples/data/icon.png'
                        }))
                    })
                });
                var map = new ol.Map({
                    layers : [
                        new ol.layer.Tile({
                            title : '街道图',
                            visible : true,
                            source : new ol.source.XYZ({
                                url : 'http://www.google.cn/maps/vt?pb=!1m5!1m4!1i{z}!2i{x}!3i{y}!4i256!2m3!1e0!2sm!3i342009817!3m9!2szh-CN!3sCN!5e18!12m1!1e47!12m3!1e37!2m1!1ssmartmaps!4e0&token=32965'
                            })
                        }),
                        gpsLayer
                    ],
                    target : 'map',
                    controls : ol.control.defaults({
                        attributionOptions : 
                        ({
                            collapsible : false
                        })
                    }),
                    view : new ol.View({
                        center : [0, 0],
                        zoom : 2
                    })
                });
                var iosocket = io.connect();
                //接受服务端消息
                iosocket.on('GPSCoor', function(data) {
                    data=JSON.parse(data);
                    switch(data.tg_op){
                        case 'INSERT':
                            var feature=new ol.Feature({
                                geometry:wktform.readGeometry(data.geom)
                            });
                            feature.setId(data.id);
                            gpsSource.addFeature(feature);//地图新增点
                            break;
                        case 'UPDATE':
                            var geom=wktform.readGeometry(data.geom);
                            var feature=gpsSource.getFeatureById(data.id);
                            if(feature)
                                feature.setGeometry(geom);//修改已有点
                            break;
                        case 'DELETE':
                            var feature=gpsSource.getFeatureById(data.id);
                            if(feature)
                                gpsSource.removeFeature(feature);//删除点
                            break;
                    }
                });
            }
            
           
    </script>
</head>
<body onload="init()">
    <div id="map"></div>
</body>
</html>

客户端接收到消息后,改变当前地图上的图标gps坐标位置。

四 测试与结果

连开三个客户端连接如下:


img_fe819504fccedc385358106a29aa36c7.png
初始化三个客户端.png
img_638acfb06d00c78a260d791d7cd26c6b.png
服务器端socket连接成功.png

4.1 数据库新增GPS坐标

insert into t_gps(geom) values (st_geomfromtext('Point(0 0)',4326));
insert into t_gps(geom) values (st_geomfromtext('Point(118 32)',4326));
insert into t_gps(geom) values (st_geomfromtext('Point(-118 -32)',4326));

页面自动响应效果如下:

img_aa0f36c48089b0708b6124485592cc5c.png
服务器端监听到的数据库消息.png
img_e69b0af6a717b252a6b85117a1576937.png
服务器端socket到客户端的效果.png

4.2 数据库修改GPS坐标

查看下当前的数据如下:

Test=# select id,st_astext(geom) from t_gps;
 id |    st_astext    
----+-----------------
 24 | POINT(0 0)
 25 | POINT(118 32)
 26 | POINT(-118 -32)
(3 rows)

将id=25的坐标改成 150,40:

Test=# update t_gps set geom=st_geomfromtext('Point(150 40)',4326) where id=25;
UPDATE 1

服务器端打印如下:

img_d13ba866fca404ac6f547eac050db58d.png
显示一条更新语句.png
img_3113b2130780ffca02bf39021864daa3.png
更新效果.png

4.3 数据库删除GPS坐标

Test=# delete from t_gps where id=25;
DELETE 1
img_10f53d4a5546c7273e5590710d9f3a05.png
显示一条删除.png
img_75669da750e697196d085555701959e4.png
删除效果.png

所有以上操作,只是数据的增删改指令,服务器和客户端都是自动响应的。

结论:本文实现了,数据库一旦广播了消息,服务器端监听,并继续以sockeio广播到客户端。全部过程,只是数据库发送了一个坐标消息无任何其他操作。pg的notify和listen消息机制,真实应用一般比如写在触发器中,触发器监听是否有数据采集终端将新坐标写入或者更新,然后在触发器中notify消息,这样,前端实时响应。可以做到将终端应用位置无任何操作的一波流发送到全部客户端实时展示。

相关文章
|
6月前
|
JavaScript 前端开发 Python
Node.js在Python中的应用实例demo
Node.js在Python中的应用实例demo
|
4月前
|
负载均衡 JavaScript 算法
Node.js 多进程的概念、原理、优势以及如何使用多进程来提高应用程序的性能和可伸缩性
Node.js 多进程的概念、原理、优势以及如何使用多进程来提高应用程序的性能和可伸缩性
41 1
|
12天前
|
存储 JavaScript 前端开发
Angular 应用 node_modules 子文件夹 @types 的作用介绍
Angular 应用 node_modules 子文件夹 @types 的作用介绍
12 1
|
1月前
|
Web App开发 JavaScript 前端开发
深入浅出:Node.js 在后端开发中的应用与实践
【2月更文挑战第13天】本文旨在探讨Node.js这一流行的后端技术如何在现代Web开发中被应用以及它背后的核心优势。通过深入分析Node.js的非阻塞I/O模型、事件驱动机制和单线程特性,我们将揭示其在处理高并发场景下的高效性能。同时,结合实际开发案例,本文将展示如何利用Node.js构建高性能、可扩展的后端服务,以及在实际项目中遇到的挑战和解决方案。此外,我们还将讨论Node.js生态系统中的重要工具和库,如Express.js、Koa.js等,它们如何帮助开发者快速搭建和部署应用。通过本文的探讨,读者将获得对Node.js在后端开发中应用的深入理解,以及如何有效利用这一技术来提升开发效率
|
1月前
|
JavaScript 前端开发
node.js第四天--ajax在项目中的应用
node.js第四天--ajax在项目中的应用
26 0
|
2月前
|
JavaScript NoSQL Redis
深入浅出:使用 Docker 容器化部署 Node.js 应用
在当今快速发展的软件开发领域,Docker 作为一种开源的容器化技术,已经成为了提高应用部署效率、实现环境一致性和便于维护的关键工具。本文将通过一个简单的 Node.js 应用示例,引导读者从零开始学习如何使用 Docker 容器化技术来部署应用。我们不仅会介绍 Docker 的基本概念和操作,还会探讨如何构建高效的 Docker 镜像,并通过 Docker Compose 管理多容器应用。此外,文章还将涉及到一些最佳实践,帮助读者更好地理解和应用 Docker 在日常开发和部署中的强大功能。
66 0
|
2月前
|
Web App开发 JavaScript 前端开发
构建现代Web应用:Vue.js与Node.js的完美结合
在当今快速发展的Web技术领域,选择合适的技术栈对于开发高效、响应迅速的现代Web应用至关重要。本文深入探讨了Vue.js和Node.js结合使用的优势,以及如何利用这两种技术构建一个完整的前后端分离的Web应用。不同于传统的摘要,我们将通过一个实际的项目示例,展示从搭建项目架构到实现具体功能的整个过程,着重介绍了Vue.js在构建用户友好的界面方面的能力,以及Node.js在处理服务器端逻辑和数据库交互中的高效性。通过本文,读者不仅能够理解Vue.js与Node.js各自的特点,还能学习到如何将这两种技术融合应用,以提升Web应用的开发效率和用户体验。
|
2月前
|
JavaScript 前端开发 网络协议
轻松搭建远程Node.js服务端,让你的应用在公共网络中畅行无阻!
Node.js 是能够在服务器端运行 JavaScript 的开放源代码、跨平台运行环境。Node.js 由 OpenJS Foundation(原为 Node.js Foundation,已与 JS Foundation 合并)持有和维护,亦为 Linux 基金会的项目。Node.js 采用 Google 开发的 V8 运行代码,使用事件驱动、非阻塞和异步输入输出模型等技术来提高性能,可优化应用程序的传输量和规模。这些技术通常用于资料密集的即时应用程序。
|
2月前
|
运维 JavaScript 开发者
深入浅出:使用Docker容器化部署Node.js应用
在当今快速发展的软件开发领域,构建一套高效、可靠且易于扩展的开发环境成为了许多开发者和企业的首要任务。本文将探讨如何利用Docker这一强大的容器化技术,实现对Node.js应用的快速部署和管理。不同于传统的摘要方式,我们将通过一个实际操作的视角,逐步引导读者理解Docker的基本概念、容器与镜像的区别、以及如何构建自己的Node.js应用Docker镜像,最终实现应用的容器化部署。此外,文章还将简要介绍Docker Compose的使用,帮助读者管理包含多个服务的复杂应用。无论是刚接触Docker的新手,还是希望深化理解容器化技术的资深开发者,本文都将提供有价值的见解。
37 0
|
3月前
|
JavaScript 前端开发 数据安全/隐私保护
NodeJS 下构建 命令行工具(CLI) 与 交互式命令界面 的实践
NodeJS 下构建 命令行工具(CLI) 与 交互式命令界面 的实践
216 1