,, ,
(中國(guó)石油大學(xué)(北京)地球物理與信息工程學(xué)院,北京 102249)
目前,4G網(wǎng)絡(luò)已經(jīng)全面覆蓋,移動(dòng)互聯(lián)網(wǎng)也處于高速發(fā)展中,高科技信息化技術(shù)已經(jīng)深入到生活的各個(gè)方面,隨之給我們的工作生活帶來了更加便捷、靈活的工作方式。警用摩托車管理系統(tǒng)讓用戶通過瀏覽器來直觀地獲取車輛的地理位置以及基本信息數(shù)據(jù),并且能夠直接播放車輛的行車記錄視頻。
警用摩托車管理系統(tǒng)由安裝于摩托車上的智能化終端設(shè)備、服務(wù)器、MySQL數(shù)據(jù)庫(kù)組成。終端采集上傳摩托車數(shù)據(jù)、視頻信息;服務(wù)器接收終端上傳的數(shù)據(jù),并將數(shù)據(jù)存入數(shù)據(jù)庫(kù),并接受前端發(fā)起的請(qǐng)求,根據(jù)請(qǐng)求調(diào)用數(shù)據(jù)庫(kù)提取相應(yīng)信息,打包處理后通過響應(yīng)的方式交給前端。
系統(tǒng)整體設(shè)計(jì)如圖1所示。系統(tǒng)由安裝于摩托車上的智能化終端設(shè)備、服務(wù)器、MySQL數(shù)據(jù)庫(kù)組成。智能化終端設(shè)備完成對(duì)摩托車位置、車速故障信息等數(shù)據(jù)的采集、上傳。服務(wù)器將終端上傳的數(shù)據(jù)解碼存入數(shù)據(jù)庫(kù);接收來自前端的請(qǐng)求,包括數(shù)據(jù)請(qǐng)求和視頻直播請(qǐng)求,收到請(qǐng)求后會(huì)根據(jù)數(shù)據(jù)請(qǐng)求的具體內(nèi)容,從數(shù)據(jù)庫(kù)檢索數(shù)據(jù),并將數(shù)據(jù)轉(zhuǎn)換成JSON格式給前端應(yīng)答,視頻請(qǐng)求則不作處理,以廣播的形式直接下發(fā)給終端。前端接收用戶請(qǐng)求向服務(wù)器提取數(shù)據(jù),并將數(shù)據(jù)進(jìn)行包裝,直觀地展現(xiàn)給用戶。
圖1 系統(tǒng)整體設(shè)計(jì)框圖
服務(wù)器分為兩部分:一部分為TCP服務(wù)器,與硬件終端設(shè)備通信,接收、解析、保存數(shù)據(jù);另一部分為HTTP服務(wù)器,與前端通信,提取、打包、發(fā)送數(shù)據(jù)。
由于摩托車終端數(shù)量較多且數(shù)據(jù)發(fā)送頻繁,故選用NETTY框架搭建TCP服務(wù)器。
Netty是一個(gè)基于非阻塞IO的快速開發(fā)高性能、高可靠性的網(wǎng)絡(luò)服務(wù)器-客戶端架構(gòu)。Netty封裝了Java NIO那些復(fù)雜的底層細(xì)節(jié),提供簡(jiǎn)單好用的抽象概念來編程,廣泛應(yīng)用于客戶端與服務(wù)器長(zhǎng)連接、高并發(fā)的場(chǎng)景。最近幾年,Netty 在計(jì)算機(jī)互聯(lián)網(wǎng)行業(yè)迅速流行開來,已經(jīng)成為 Java 通信編程架構(gòu)的第一選擇。
對(duì)于NETTY服務(wù)器端而言,屬于被動(dòng)接收請(qǐng)求,服務(wù)端bind端口采用隨機(jī)的方式,以避免單臺(tái)服務(wù)器多端口之間的沖突。通過ServerBootstrap創(chuàng)建服務(wù)器端通訊連接。此外,采用ChannelGroup類,既可以自動(dòng)將被關(guān)閉的Channel從ChannelGroup中刪除,還可以統(tǒng)一關(guān)閉ChannelGroup中的所有通道。
部分代碼如下:
public void bind() {
EventLoopGroup bossGroup = new NioEventLoopGroup();//用于接收客戶端連接
EventLoopGroup workGroup = new NioEventLoopGroup(); //用于進(jìn)行網(wǎng)絡(luò)讀寫通信
try {ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workGroup);//綁定兩個(gè)線程
b.channel(NioServerSocketChannel.class);
b.option(ChannelOption.SO_BACKLOG, 1024);
b.childHandler(new ChildChannelHandler());
// 綁定端口
ChannelFuture f = b.bind(53606).sync();
// 等待服務(wù)端監(jiān)聽端口關(guān)閉
f.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally { //退出
bossGroup.shutdownGracefully();
workGroup.shutdownGracefully();
}
}
服務(wù)端在初始化的時(shí)候,由于我們需要對(duì)接收的數(shù)據(jù)進(jìn)行處理,于是在通道中添加我們自己接收數(shù)據(jù)實(shí)現(xiàn)的方法類,以及5分鐘判斷客戶端的在線離線狀態(tài)。并且對(duì)于粘包拆包問題的處理上,采用的了基于換行符的處理方式。
部分代碼如下:
protected void initChannel(SocketChannel ch) throws Exception {
System.out.println("IP:" +
ch.localAddress().getHostName());
System.out.println("Port:" +
ch.localAddress().getPort());
// 5分鐘判斷在線離線
ch.pipeline().addLast(new IdleStateHandler(300,0,0));
// 半包處理[基于換行符]
ch.pipeline().addLast(new
LineBasedFrameDecoder(1024));
// 字符串編碼
ch.pipeline().addLast(new StringDecoder());
// 字符串解碼
ch.pipeline().addLast(new StringEncoder());
// 在管道中添加我們自己的接收數(shù)據(jù)實(shí)現(xiàn)方法
ch.pipeline().addLast(new MyServerHanlder());
}
在保證服務(wù)器能夠接收到所有終端的數(shù)據(jù)并且不丟失的前提下,還要根據(jù)終端用戶ID號(hào),對(duì)數(shù)據(jù)包是否有效進(jìn)行判斷。如果ID與數(shù)據(jù)庫(kù)中注冊(cè)ID符合則服務(wù)器接收數(shù)據(jù)并存入數(shù)據(jù)庫(kù),如不符合,則拒絕接收。判斷用戶ID流程圖如圖2所示。
圖2 判斷ID流程圖
對(duì)于終端的在線離線狀態(tài),將借助NETTY提供的IdleStateHandler類,如下:
public IdleStateHandler(int readerIdleTimeSeconds, int writerIdleTimeSeconds, int allIdleTimeSeconds) {this((long)readerIdleTimeSeconds, (long)writerIdleTimeSeconds, (long)allIdleTimeSeconds, TimeUnit.SECONDS);}
即實(shí)現(xiàn)心跳機(jī)制的類,來處理客戶端的連接狀態(tài)。其中:readerIdleTimeSeconds為讀超時(shí);
writerIdleTimeSeconds為寫超時(shí);
allIdleTimeSeconds為所有超時(shí)。
而根據(jù)實(shí)際情況以及產(chǎn)品需求,在服務(wù)器初始化時(shí),通道中添加的方法為:
ch.pipeline()addLast(new IdleStateHandler(300,0,0));即讀超時(shí)設(shè)置為5分鐘。系統(tǒng)認(rèn)為5分鐘內(nèi)未發(fā)送數(shù)據(jù),服務(wù)器將斷開連接,并將狀態(tài)記錄存儲(chǔ)到數(shù)據(jù)庫(kù)中,同時(shí)不再刷新該終端的實(shí)時(shí)狀態(tài)。判斷在線、離線狀態(tài)流程圖如圖3所示。
圖3 判斷狀態(tài)流程圖
HTTP用于接受前端的請(qǐng)求,根據(jù)請(qǐng)求調(diào)用數(shù)據(jù)庫(kù)提取相應(yīng)信息,打包處理后通過響應(yīng)的方式交給前端,數(shù)據(jù)交換格式為JSON,一種輕量級(jí)的數(shù)據(jù)交換格式。
HTTP服務(wù)器接收前端的請(qǐng)求,并對(duì)請(qǐng)求進(jìn)行解析,解析方式為字符串匹配,根據(jù)解析出來的條件對(duì)數(shù)據(jù)庫(kù)執(zhí)行檢索。完成對(duì)數(shù)據(jù)庫(kù)的操作后將數(shù)據(jù)打包成JSON格式,響應(yīng)給前端。
JSON作為更輕、更便捷的Web service客戶端格式,目前廣泛應(yīng)用于編程開發(fā)中。相較于XML格式,它更加便于讀取,JSON中的分隔符限于單引號(hào)、小括號(hào)、大括號(hào)等,而JavaScript引擎對(duì)數(shù)據(jù)結(jié)構(gòu)的內(nèi)部表示正好與這些符號(hào)相同,以此簡(jiǎn)化了數(shù)據(jù)的訪問。此外,它的另一個(gè)優(yōu)點(diǎn)是其非亢長(zhǎng)性。傳統(tǒng)的XML標(biāo)記會(huì)增加數(shù)據(jù)交換時(shí)間,必須包括打開和關(guān)閉標(biāo)記,才能滿足標(biāo)記的依從性,而在JSON中所有這些要求只需要通過括號(hào)即可滿足。所有JSON的線上傳輸效率更高。
部分請(qǐng)求響應(yīng)JSON格式如下:
if(包含all_motor)//實(shí)時(shí)位置
{
"Motor":
[
{
"ID":"4661",
"data":
{
"longitude":"XXXX",
"latitude":"XXXX",
……}
},
{
"ID":"4662",
"data":
{ ……}
},…… ]
}
if(包含real)//實(shí)時(shí)軌跡
{
截取命令字段中的ID,通過ID在數(shù)據(jù)庫(kù)moto_real_time表中查找 最新更新時(shí)間,根據(jù)這個(gè)時(shí)間以及ID,在moto_historic表中檢索 當(dāng)天該ID的所有數(shù)據(jù),數(shù)據(jù)格式同上。
}
if(包含video)//視頻開關(guān)
{
直接將video字段后的命令廣播發(fā)送給終端。
數(shù)據(jù)格式:ID=XXXX/state=X/channel=X/
}
MySQL數(shù)據(jù)庫(kù)體積小、速度快,多用戶、多線程并且能夠處理大量數(shù)據(jù)[9],主要以小型應(yīng)用為主,主要設(shè)計(jì)目標(biāo)是實(shí)現(xiàn)數(shù)據(jù)操作速度優(yōu)化且不影響SQL支持性能。應(yīng)用程序通過JDBC API接口引入JDBC驅(qū)動(dòng)對(duì)數(shù)據(jù)完成操作,部分程序如下:
public static void Table_Create() {
Class.forName("com.mysql.jdbc.Driver");
try(
//使用DriverManager獲取數(shù)據(jù)庫(kù)連接,
//其中返回的Connection就代表了Java程序和數(shù)據(jù)庫(kù)的連接
//不同數(shù)據(jù)庫(kù)的URL寫法需要查驅(qū)動(dòng)文檔知道,用戶名、密碼由DBA分配
Connection conn = DriverManager.getConnection(
"jdbc:mysql://127.0.0.1:3306/mysql"
, "root", "123456");
//使用Connection來創(chuàng)建一個(gè)Statment對(duì)象
Statement stmt = conn.createStatement()){
stmt.executeUpdate("create table if not exists motor_register"
//創(chuàng)建表格字段名
+"(XXX int not null primary key,"
+" XXX varchar(255),"
……
+" XXX varchar(255));" );
}
}
根據(jù)數(shù)據(jù)量分布以及客戶需求,設(shè)計(jì)三個(gè)車輛信息表,分別為摩托車登記表、摩托車實(shí)時(shí)狀態(tài)表和摩托車歷史數(shù)據(jù)表。登記表和實(shí)時(shí)狀態(tài)表中都設(shè)置以clientID為主鍵且非空,由于在此兩表中clientID不可重復(fù)存儲(chǔ),此外實(shí)時(shí)狀態(tài)表中的uploadDate字段是聯(lián)合主鍵,為確保實(shí)時(shí)表格的準(zhǔn)確性。三個(gè)表格之間的關(guān)系如圖4所示。
圖4 數(shù)據(jù)庫(kù)E-R圖
FieldTypeNullKeyDefaultclientIDInt(11)NOPRINULLmotobrandvarchar(255)YESNULLmotolicensevarchar(255)NONULLpoliceIDvarchar(255)NONULLmotostatusvarchar(255)YESNULL
摩托車登記表用于記錄摩托車序列號(hào)、摩托車品牌、摩托車車牌、歸屬警員號(hào)、摩托車狀態(tài)等基礎(chǔ)信息,每輛摩托車擁有唯一序列號(hào),只有經(jīng)注冊(cè)將信息錄入登記表,服務(wù)器才會(huì)接收ID號(hào)符合的數(shù)據(jù)包存入數(shù)據(jù)庫(kù),進(jìn)而調(diào)取車輛狀態(tài)信息。數(shù)據(jù)名稱以及數(shù)據(jù)類型屬性如表1所示。
摩托車當(dāng)前狀態(tài)表存儲(chǔ)摩托車最新狀態(tài),數(shù)據(jù)包括用戶ID、用戶名稱、狀態(tài)更新時(shí)間、速度、里程、當(dāng)天里程在線離線狀態(tài)等,一個(gè)ID 對(duì)應(yīng)一條記錄。當(dāng)終端被判斷離線后,狀態(tài)變?yōu)椤?”,再將數(shù)據(jù)異步存入至歷史表。歷史狀態(tài)表存儲(chǔ)所有車輛的所有信息,方便前端調(diào)取查閱車輛歷史軌跡,也可以檢索某時(shí)間段某輛車的詳細(xì)數(shù)據(jù)。當(dāng)前狀態(tài)表如表2所示。當(dāng)前狀態(tài)表如表3所示。
表2 摩托車當(dāng)前狀態(tài)表
表3 摩托車歷史狀態(tài)表
視頻直播是通過基于Android操作系統(tǒng)的終端設(shè)備,搭建基于RTMP傳輸協(xié)議的流媒服務(wù)器,最終在前端流媒體播放器上實(shí)現(xiàn)。攝像頭開關(guān)由前端控制,媒體流通過RTMP協(xié)議進(jìn)行傳輸和播放。
RTMP(Real Time Messaging Protocol),即實(shí)時(shí)消息傳送協(xié)議,是由Adobe公司提出的一種應(yīng)用層的協(xié)議,用來解決多媒體數(shù)據(jù)傳輸流的多路復(fù)用和分包問題。并且隨著VR技術(shù)的發(fā)展和視頻直播等領(lǐng)域的發(fā)展,RTMP協(xié)議逐漸熱門起來,在高速發(fā)展的流媒體市場(chǎng)中得到廣泛的應(yīng)用[10]。
視頻直播流程分為以下幾步:(1)采集;(2)處理;(3)編碼和封裝;(4)推流到服務(wù)器;(5)服務(wù)器流分發(fā);(6)播放器流播放。
當(dāng)前端發(fā)送開啟直播命令時(shí),終端攝像頭啟動(dòng)采集實(shí)時(shí)音視頻,并編碼成H.264碼流,視頻服務(wù)器啟動(dòng)建立與終端的鏈接,然后接收碼流并推流至前端播放以及進(jìn)行實(shí)時(shí)存儲(chǔ),以MP4格式保存至本地。
終端視頻格式不是可以直接觀看的MP4格式,而是封裝為H.264,且每個(gè)視頻時(shí)長(zhǎng)10秒。由于前端HTML5不支持此類視頻的播放,需要視頻服務(wù)器對(duì)視頻進(jìn)行實(shí)時(shí)的格式轉(zhuǎn)換處理,服務(wù)器中調(diào)用了FFmpeg應(yīng)用程序,將H264轉(zhuǎn)換為MP4,處理后的視頻放到指定路徑,供前端調(diào)用。
警用摩托車管理系統(tǒng)前端主要包括系統(tǒng)登錄界面、車輛注冊(cè)界面、實(shí)時(shí)監(jiān)控界面、信息檢索界面、車輛信息界面五個(gè)部分。
警用摩托車登錄系統(tǒng)界面部分用于保障系統(tǒng)安全,設(shè)置使用權(quán)限,用戶只需要輸入提前設(shè)置的用戶名、密碼便能夠成功登錄該管理系統(tǒng),然后進(jìn)入主界面。
警用摩托車系統(tǒng)注冊(cè)界面,用于錄入摩托車基本信息,包括摩托車序列號(hào)、摩托車品牌、摩托車車牌、警員號(hào)、摩托車狀態(tài)信息等,限制摩托車序列號(hào)位數(shù)字,各欄均不為空,否則會(huì)提示錯(cuò)誤,注冊(cè)不成功。注冊(cè)成功后,會(huì)將信息寫入數(shù)據(jù)庫(kù)motor_register表,只有在motor_register表中錄入信息的車輛,在界面中才可以查詢其信息。
實(shí)時(shí)監(jiān)控界面如圖5所示,主要功能為在地圖上直觀顯示所有車輛位置,同時(shí)在最標(biāo)點(diǎn)標(biāo)簽上顯示車輛ID以及電量信息等簡(jiǎn)要信息。
圖5 實(shí)時(shí)監(jiān)控界面圖
信息檢索界面如圖6所示,可通過輸入ID號(hào)查找某輛車信息,顯示實(shí)時(shí)軌跡并播放實(shí)時(shí)視頻。視頻窗口上方顯示該車輛的狀態(tài),包括實(shí)時(shí)速度、里程等。具體操作是在ID輸入框內(nèi)輸入車輛ID,單擊ID輸入框后的確定按鈕,跳轉(zhuǎn)查看該車輛的信息,若輸入ID不存在,則會(huì)彈框提示。選擇好起止時(shí)間后,單擊時(shí)間選擇框后的確定按鈕,可以查看該車輛在某個(gè)時(shí)間段的軌跡以及錄像。
圖6 信息檢索界面圖
車輛信息界面默認(rèn)顯示所有車輛最新信息如圖7所示,信息包括車輛ID、摩托車品牌、經(jīng)緯度、里程、故障信息以及更新時(shí)間等??筛鶕?jù)表頭某一項(xiàng)信息對(duì)表中元素進(jìn)行排序。表格右上方Search框可輸入車輛ID,檢索某一車輛的所有信息,點(diǎn)擊表格標(biāo)題欄可切換顯示所有車輛當(dāng)天所有信息。
圖7 車輛信息界面圖
在4G網(wǎng)絡(luò)全面覆蓋和互聯(lián)網(wǎng)產(chǎn)業(yè)的迅速發(fā)展的背景下,使人們的工作方式越來越多樣化,利用網(wǎng)絡(luò)的便捷設(shè)計(jì)的警用摩托車管理系統(tǒng),方便了單位實(shí)現(xiàn)內(nèi)部車輛合理調(diào)配以及有效排查車輛故障獲取車輛基本信息,可以極大地提高公安人員的執(zhí)法效率。本文設(shè)計(jì)的軟件主要是基于B/S架構(gòu),實(shí)現(xiàn)了警用摩托車基本數(shù)據(jù)采集、數(shù)據(jù)存儲(chǔ)管理和行車記錄視頻直播等功能。
本文完成的主要工作有以下兩大部分內(nèi)容,即后臺(tái)服務(wù)器和前端瀏覽器。
系統(tǒng)服務(wù)器車輛基本信息通訊部分主要由NETTY框架、HTTP協(xié)議和MySQL數(shù)據(jù)庫(kù)搭建而成,行車記錄視頻由RTMP搭建而成。實(shí)現(xiàn)了接收終端發(fā)送的protobuf數(shù)據(jù)并解碼存入已設(shè)計(jì)好的數(shù)據(jù)庫(kù),數(shù)據(jù)庫(kù)的設(shè)計(jì)滿足每次訪問數(shù)據(jù)庫(kù)的效率最優(yōu)。HTTP協(xié)議用于接收前端請(qǐng)求數(shù)據(jù)并作出響應(yīng),根據(jù)請(qǐng)求提取數(shù)據(jù)庫(kù)相應(yīng)信息,數(shù)據(jù)將以JSON格式發(fā)送給前端[8]。行車記錄視頻通過搭建基于RTMP傳輸協(xié)議的服務(wù)器實(shí)現(xiàn)視頻流推送到RTMP服務(wù)器,再將視頻發(fā)送給前端進(jìn)行直播。此外,終端錄制的視頻可以通過服務(wù)器轉(zhuǎn)化為MP4格式保存至本地。
主要應(yīng)用HTML、CSS和JavaScript技術(shù)設(shè)計(jì)網(wǎng)頁(yè),可實(shí)現(xiàn)在地圖上展示車輛位置以及車輛基本信息,可針對(duì)某輛車某時(shí)間段行車軌跡進(jìn)行查詢和觀看行車記錄視頻。
本文運(yùn)用了目前最流行的框架和直播技術(shù),設(shè)計(jì)的警用摩托車管理系統(tǒng)軟件,最終通過測(cè)試成功實(shí)現(xiàn)以上所介紹的功能。為辦公人員提供了便捷的管理服務(wù),大大優(yōu)化了工作效率。