nodejs一直以异步io著称,其语言特性尤为擅长于在realtime应用中,如聊天室等。在进行实时应用开发时,必不可少的须要用到 socket.io库,能够说,nodejs+socket.io在实时应用中具备较好的表现能力。
本文既然选择以实时地图应用作个小例子,那么选择经典的PostgreSQL/PostGIS做为地图的数据库。但愿实现的是模拟数据库数据插入了新的GPS坐标,而一旦数据发生改变,马上将插入的GPS坐标广播到服务端,服务端广播到全部的客户端地图上,进行定位展现。早期做者使用的是redis的广播/订阅机制,最近发现Pg数据库的listen/notify也具有这种消息传递机制。
本文主要的socke.io广播/订阅参考官网,Pg的listen/notify自行谷歌,做者仅简述一下本身如何考虑应用的。css
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();
创建一个测试表以下:html
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坐标位置。node
连开三个客户端链接以下:ios
初始化三个客户端.pngredis
服务器端socket链接成功.pngsql
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));
页面自动响应效果以下:数据库
服务器端监听到的数据库消息.pngjson
服务器端socket到客户端的效果.png服务器
查看下当前的数据以下:
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
服务器端打印以下:
显示一条更新语句.png
更新效果.png
Test=# delete from t_gps where id=25; DELETE 1
显示一条删除.png
删除效果.png
全部以上操做,只是数据的增删改指令,服务器和客户端都是自动响应的。
结论:本文实现了,数据库一旦广播了消息,服务器端监听,并继续以sockeio广播到客户端。所有过程,只是数据库发送了一个坐标消息无任何其余操做。pg的notify和listen消息机制,真实应用通常好比写在触发器中,触发器监听是否有数据采集终端将新坐标写入或者更新,而后在触发器中notify消息,这样,前端实时响应。能够作到将终端应用位置无任何操做的一波流发送到所有客户端实时展现。