近年来, 随着电子信息技术、移动通信技术、物联网技术的快速发展, 车联网技术也有长足的进步[1,2]. 与此同时, 物流运输企业和政府监管部门都对车辆安全更加重视. 这也推动了新技术在车联网中的应用. 在车联网系统中, 报警通知和处理是非常重要的功能模块. 按照交通部相关法规及各省交管部门的要求[3], 当车辆发生超速、疲劳驾驶等危险驾驶行为时, 交通部联网联控平台的车联网运营商需要及时收到通知, 针对车辆异常行为进行相应处理. 因此, 报警通知显得尤为重要.
当车辆发生异常报警时, 终端通过socket实时上报到车联网服务器. 服务器需要将报警信息实时通知到用户. 传统BS架构的车联网系统中, 一般通过AJAX轮询[4]的方式拉取报警信息. 这种方式实现简单, 但它的弊端也是显而易见: (1) 轮询是有时间间隔的, 所以报警是有延迟的. 过短的轮询间隔产生大量的无效请求并增大服务器压力. (2) 当用户数量增多时, 轮询会产生大量的网络流量, 增加不必要的带宽损耗, 并导致服务器产生很大并发压力. (3) 当终端数据上报频率高于AJAX轮询频率时, Web客户端还没来得及做一次AJAX请求, 新的报警数据已经覆盖了旧的报警数据, 会造成报警数据丢失. 因此, 需要采用新的技术手段解决车联网系统中海量报警数据推送问题.
近年来, 由于HTML5技术的广泛普及, 主流浏览器对HTML5标准支持更加完善, 客户端和服务器之间的实时数据交换变得更加容易. Websocket是HTML5新标准中的通信机制[5,6], 能够实现稳定全双工实时通信, 具有简洁高效的特点. 在Websocket API中, 浏览器和服务器只需要完成一次握手, 两者之间就可以直接创建持久性的连接, 并进行双向数据传输, 占用的网络带宽较少. 它不需要安装浏览器插件, 所以跨浏览器兼容性更好. 因此, Websocket技术可作为车联网系统中报警推送的理想方案.
2 系统设计针对上述AJAX问题并考虑到Websocket的优点, 我们设计一套基于Websocket技术的报警推送系统. 该系统主要包含4个模块: 网关、Redis数据库、Websocket推送服务、Websocket客户端及前端页面. 整个系统的架构如图1所示.
在车联网系统中, 终端通过接入网络[7](包括2G/3G/4G等无线移动通信网络或WLAN等网络)按照《道路运输车辆卫星定位系统北斗兼容车载终端通讯协议技术规范》(简称JT/T 808)协议[8]上报定位数据、报警数据和其它附加数据. 网关模块负责维持与众多终端的socket长连接, 接收终端上报的数据, 解析数据并写入数据库. 根据上报数据的类型不同, 一些数据写入关系型数据库以便长期持久化. 这些历史数据将用于车辆轨迹查询、行程分析、报警统计等功能. 另外一些需要实时查询和推送的数据写入Redis缓存数据库. 网关通过两种方式写入Redis: 通过set/hset方式写入Redis以便其它模块查询实时数据; 通过发布模式(publish)写入Redis以便实时通知其它模块.
Redis是一个开源的内存数据结构存储系统, 它可以用作数据库、缓存和消息中间件[9]. Redis的所有数据均位于服务器的主内存中, 因此读写速度非常快. 每秒可以执行10万次以上的写入操作. Redis的发布订阅模式实现了一个简单的消息队列的功能. 引入Redis的目的在于: (1) 降低网关与后续Websocket推送服务间的耦合, 从而保障网关的稳定性. 即使网络异常导致Websocket推送服务产生积压或阻塞, 也不会影响网关与终端的正常通信, 不会影响数据写入关系型数据库. (2) 当多个程序订阅Redis消息时, 数据可以在系统内不同模块间实时分发, 实现数据同步. (3) Redis本身也承担缓存的作用, 可以直接在Redis里查询所有车辆的最新一次位置信息、报警状态.
Websocket推送服务主要承担权限判断、Websocket推送、推送统计等功能. 它从Redis订阅了报警频道(channel). 当有报警信息从Redis推送过来, 它提取报警内容以及该报警对应的车辆信息、终端信息, 然后从数据库查询用户与车辆的对应关系, 判断是否需要将本条报警信息推送给用户. 由此可以做到报警推送与用户权限绑定, 具有查看车辆权限的用户才会收到报警推送. 另外, 可也根据用户喜好设置, 过滤特定类型的报警推送, 只推送用户关注的报警类型.
Web前端采用Socket.io的js库. Socket.io是一个跨浏览器、支持Websocket实时通讯的js库[10]. 它支持实时、双向、基于事件的数据通信, 可在不同平台、浏览器、设备上工作. Socket.io除了支持Websocket通讯协议外, 还支持许多种轮询(Polling)机制以及其它实时通信方式, 并封装成了通用的接口. Socket.io能够根据浏览器对通讯机制的支持情况自动从Websocket, Adobe Flash Socket, AJAX long pulling, AJAX multipart streaming, Forever IFrame, JSONP polling中选择最佳的方式来实现网络实时应用. 这样前端开发Websocket变得更简单.
3 系统实现整个车联网系统的网关和后端使用Java语言开发, Web前端基于vue.js框架开发.
车联网平台接入的终端较多, 在线的终端可能数以万计. 这些终端通过TCP长链接与平台通信. 因此需要一个性能强大的网络连接库. 本系统网关部分采用Netty库处理socket连接和数据收发. Netty是一个基于Java NIO的客户端/服务器网络应用框架[11]. 它隐藏背后复杂的网络操作, 提供一个易于使用的API框架.
Netty框架的一大亮点是基于EventLoop的高效线程模型. 它是一个高性能、异步事件驱动的NIO框架, 所有IO操作都是异步非阻塞的. 高性能的Netty可以处理数万终端的连接和数据收发.
Netty通过bind()方法监听9300端口, 等待终端连接. 当终端上报数据时, 继承自ChannelInboundHandlerAdapter的GatewayHandler类的channelRead方法接收数据并解析报警. 然后通过jedis的publish方法发布报警到Redis里面. 核心代码如下:
ServerBootstrap b = new ServerBootstrap();
b. group(bossGroup, workerGroup)
. channel(NioServerSocketChannel. class)
. childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ch. pipeline(). addLast(new GatewayHandler());
}
});
//网关监听9300端口
ChannelFuture f=b. bind(9300). sync();
f. channel(). closeFuture(). sync();
…
//网关将报警发布到redis
jedis. publish("channel _alarm", alarm);
在本系统中, Redis既用作了缓存数据库, 也充当了消息队列. Redis服务器采用了6个Redis节点集群的方式部署, 包括3个主节点, 3个从节点. 这样可以做到自动分配数据到不同的节点上, 提高了性能. 部分节点失效的情况下还能够继续提供服务, 提高了系统的高可用性.
Websocket推送服务采用开源netty-socketio方案(
Netty-socketio推送服务的ConnectListener监听了Websocket连接事件. 当用户通过浏览器登录本系统, 服务端会收到一个唯一标识(UUID). Clients集合保存了所有已登录用户的标识. 当终端上报一条报警数据后, 程序从数据库查询该报警对应的车辆, 再根据车辆和用户的关系, 查找到该报警需要通知的一个或多个用户. 如果用户的个人设置里配置了接收报警推送, 则从clients集合里查找到该用户对应的Websocket连接, 将该报警通过此连接推送到web前端. Websocket推送服务核心代码如下:
//保存所有已登录用户建立的Websocket连接标识
List<UUID> clients=new ArrayList<UUID>();
//配置websocket
Configuration conf=new Configuration();
conf. setPort(9092);
conf. setBossThreads(128);
conf. setWorkerThreads(128);
conf. setUseLinuxNativeEpoll(true); //epoll性能更高
SocketIOServer server=new SocketIOServer(conf);
ConnectListener listener=new ConnectListener() {
@Override
public void onConnect(SocketIOClient client) {
//用户登录系统, 建立websocket连接
clients. add(client. getSessionId());
}
};
server. addConnectListener(listener);
server. start();
…
//向Web前端推送报警
socket. sendEvent(topic, alarm);
前端引入socket.io.js监听Websocket事件, 例如建立连接、断开连接、收到数据等事件[12]. 当收到报警时, js解析报警内容, 将结果展示在web界面. 核心代码如下所示:
// 建立连接
var socket=io. connect(’http://192.168.0.2:9092’);
// 监听message
socket. on(’message’, function (data) {
var html=document. createElement(’p’)
html. innerHTML=’车牌为: ’+data. vehicle+’的车辆发生了类型为’+data.alarm+’的报警, 请及时处理’;
document. getElementById(’content’). appendChild(html);
});
整个前端页面包含了一套完整的车联网平台, 包括车辆监控、轨迹回放、报警处理、指令下发、报表查询等功能. 报警推送只是其中一个模块. 当前端收到报警推送时, 通过弹框和播放声音等方式明显提示用户. 车辆监控、报警处理、报表查询等功能也会同步刷新报警数据.
4 系统测试与分析系统测试环境如下: 服务器硬件配置为16核, 8 GB内存. 服务器操作系统为Centos 7. Java使用Oracle JDK1.8.0_171. Web服务器使用Tomcat 8.5.32. Redis版本为4.0.10. Netty-socketio版本为1.7.13. 客户端环境为Windows 10操作系统, 浏览器为支持Websocket的Google Chrome 67.0.3396.99.
我们定义报警的延迟为: 从终端产生报警的时间到用户浏览器收到报警的时间差. 严格测试报警延迟, 需要先将终端和用户电脑校时. 这里我们使用模拟终端巧妙回避校时的问题. 模拟终端是一个可运行在Windows上的程序. 它模拟了车载终端的数据采集、网络数据收发逻辑. 将模拟终端和浏览器运行在同一台Windows电脑上, 比较终端产生报警的时间戳和浏览接收到报警的时间戳, 即可计算出报警推送的延迟.
使用图2所示模拟终端发送测试数据. 通过模拟终端数据发送时间和Chrome开发者工具控制台日志输出时间可以计算报警延迟. 每秒推送一次报警, 共推送100条报警数据, 从终端上传报警数据到浏览器收到报警推送的时间延迟平均值为8 ms. 传统AJAX轮询间隔大约是10到30 s. 可见报警实时性大大提高.
采用开源工具apache jmeter对netty-socketio服务器作了压力测试. Jmeter的Websocket Sampler插件可以模拟Websocket并发连接. 在每个并发连接数下, 推送100条数据, 取推送延迟的平均值. 测试结果如图3所示. 结果表明, 随着Websocket并发连接数增长, 推送延迟并未出现大幅度增长, 而是在7.5 ms左右波动. 这也反映出netty-socketio的高并发、低延迟特性.
把模拟终端的数据上报时间间隔设定成1 s, 连续发送1000条报警数据. 浏览器控制台输出数据表明接收到1000条报警. 这说明Websocket并未丢失任何一条数据, 不会出现AJAX方式的报警覆盖问题, 报警推送的准确性得到保证.
通过Chrome浏览器开发者工具查看网络连接, 当页面加载时, 服务响应的HTTP头信息中包含了这两项: Connection: Upgrade和Upgrade: Websocket, 可知客户端和服务器在握手期间从HTTP协议升级为Websocket协议. 之后Websocket一直保持长连接. 而AJAX轮询时, 每次建立HTTP连接, 都要发送HTTP报头等信息. 通过wireshark抓包软件可知, 通常一次HTTP请求和响应, 需要发送大约570 B的HTTP头部信息[5]. 这个HTTP报头甚至比消息本身还要大. 而使用Websocket的额外开销只有2 B, 建立连接之后便可以二进制帧格式传输纯数据. 我们假定一次报警的数据为50 B. 通过AJAX方式拉取1000条报警生产的网络流量为: (570+50)×1000=620 000 B. 而通过Websocket传输的网络流量为(2+50)×1000=52 000 B. AJAX方式产生的网络流量是Websocket网络流量的11倍. 当报警推送量大后, Websocket比AJAX轮询节省大量的网络流量.
由于主流的浏览器已经支持Websocket, 前端socket.io.js也具有较好的自适应性, 所以整个系统具有较好的浏览器兼容性. 经过测试, 报警推送功能在最新版的Chrome, Firefox, IE11等常用浏览器上均能正常工作并具有较好的性能.
本系统已经在生产环境中使用. 该平台包含4.5万台入网终端, 其中大约2.1万在线终端. 日均产生报警数据750万条左右. 平均每秒钟推送86条报警. 服务器的CPU、内存和网络均保持较低压力状态. 客户端Chrome浏览器的CPU和内存占用保持平稳, 说明浏览器能承受如此大量的报警推送. 系统已经稳定运行数月, 证明这是一套可用于生产环境的稳定系统.
5 结束语根据车联网的特定业务场景, 设计了一种基于Websocket技术的车联网报警推送系统. 对比AJAX轮询技术和Websocket技术可知, 使用Websocket方案以后, 大大降低报警的推送延迟, 提高了报警推送的吞吐量, 保证报警信息及时准确地推送到客户. 随着HTML5技术的普及, Websocket将逐渐成为Web实时通信的主流技术.
本文介绍的方案解决了报警实时性和准确性等问题. 但是当数万终端每天产生750万报警数据时, 如何让用户方便查看并处理海量报警成了新的问题. 报警聚合和报警联动将是后续研究的方向.
[1] |
程刚, 郭达. 车联网现状与发展研究. 移动通信, 2011, 35(17): 23-26. DOI:10.3969/j.issn.1006-1010.2011.17.004 |
[2] |
王建强, 吴辰文, 李晓军. 车联网架构与关键技术研究. 微计算机信息, 2011, 27(4): 156-158, 130. DOI:10.3969/j.issn.2095-6835.2011.04.064 |
[3] |
中华人民共和国交通运输部. JT/T 796-2011道路运输车辆卫星定位系统 平台技术要求. 北京: 人民交通出版社, 2011.
|
[4] |
孙清国, 朱玮, 刘华军, 等. Web应用中的服务器推送技术研究综述. 计算机系统应用, 2008, 17(11): 116-120. DOI:10.3969/j.issn.1003-3254.2008.11.030 |
[5] |
李代立, 陈榕. WebSocket在Web实时通信领域的研究. 电脑知识与技术, 2010, 6(28): 7923-7925, 7935. |
[6] |
薛陇彬, 刘钊远. 基于WebSocket的网络实时通信. 计算机与数字工程, 2014, 42(3): 478-481. DOI:10.3969/j.issn.1672-9722.2014.03.030 |
[7] |
任开明, 李纪舟, 刘玲艳, 等. 车联网通信技术发展现状及趋势研究. 通信技术, 2015, 48(5): 507-513. DOI:10.3969/j.issn.1002-0802.2015.05.001 |
[8] |
中华人民共和国交通运输部. JT/T 808-2011道路运输车辆卫星定位系统 终端通讯协议及数据格式. 北京: 人民交通出版社, 2011.
|
[9] |
曾超宇, 李金香. Redis在高速缓存系统中的应用. 微型机与应用, 2013, 32(12): 11-13. DOI:10.3969/j.issn.1674-7720.2013.12.004 |
[10] |
黄经赢. 基于Socket. io+Node. js+Redis构建高效即时通讯系统. 现代计算机, 2014(13): 62-64, 69. DOI:10.3969/j.issn.1007-1423(z).2014.13.016 |
[11] |
金志国, 李炜. 基于Netty的HTTP客户端的设计与实现. 电信工程技术与标准化, 2014(1): 84-88. DOI:10.3969/j.issn.1008-5599.2014.01.027 |
[12] |
王佃来, 宿爱霞, 安晏辉. 基于WebSocket的消息推送系统. 计算机系统应用, 2017, 26(9): 87-92. DOI:10.15888/j.cnki.csa.005973 |