摘要:水文監(jiān)測(cè)系統(tǒng)是一個(gè)提供水文部門監(jiān)測(cè)各類水文信息的平臺(tái)。業(yè)務(wù)功能比較豐富,主要包括遙測(cè)站的定位、狀態(tài)的查詢、實(shí)時(shí)信息以及往年水文信息的查詢等。水文監(jiān)測(cè)系統(tǒng)需要異步處理各個(gè)遙測(cè)站發(fā)來的消息,包括信息的收集、分析與存儲(chǔ)以及網(wǎng)頁(yè)端的實(shí)時(shí)信息的查詢。傳統(tǒng)的水文監(jiān)測(cè)服務(wù)端多采用BIO的方式接受處理數(shù)據(jù),該文在比較傳統(tǒng)BIO技術(shù)的基礎(chǔ)上,采用NIO的方式處理水文類數(shù)據(jù)。通過對(duì)NIO框架Netty源碼的封裝與擴(kuò)展,提供能接受處理客戶端異步請(qǐng)求的通用的可重用的水文監(jiān)測(cè)服務(wù)端的設(shè)計(jì)思路與具體實(shí)現(xiàn)。
關(guān)鍵詞:水文監(jiān)測(cè);NIO;Netty框架;異步
1 背景
水文監(jiān)測(cè)系統(tǒng)主要應(yīng)用與水文部門對(duì)各個(gè)地區(qū)的降水量、河道、水庫(kù)(湖泊)以及地下水等的監(jiān)測(cè)與統(tǒng)計(jì),從而制定出相應(yīng)的對(duì)策。傳統(tǒng)的水文監(jiān)測(cè)普遍采用人工監(jiān)測(cè)和有線傳播等方式進(jìn)行,這種方式存在很嚴(yán)重的弊端,人工監(jiān)測(cè)的成本高,信息采集的準(zhǔn)確度不高,并且信息的實(shí)時(shí)性也不高,這樣給水文部門制定相應(yīng)的策略的時(shí)候造成很大的干擾。隨著科學(xué)技術(shù)的不斷發(fā)展,現(xiàn)如今的水文監(jiān)測(cè)技術(shù)已經(jīng)發(fā)展的比較成熟,其中主要涉及傳感器技術(shù)、嵌入式技術(shù)、通信技術(shù)、存儲(chǔ)技術(shù)、信息處理技術(shù)以及人工智能等多種高新信息技術(shù)[1]。
網(wǎng)絡(luò)編程的基本模型是Client/Server模型,也就是兩個(gè)進(jìn)程之間進(jìn)行相互通信,其中服務(wù)端提供位置信息(綁定的IP地址和監(jiān)聽端口),客戶端通過連接操作向服務(wù)端監(jiān)聽的地址發(fā)起連接請(qǐng)求,通過三次握手建立連接,如果連接成功,雙方就可以通過網(wǎng)絡(luò)套接字(Socket)進(jìn)行通信[2]。
2 BIO與NIO的比較
傳統(tǒng)的網(wǎng)絡(luò)服務(wù)器使用BIO(阻塞IO)開發(fā),多采用一連接一線程(One thread per connection)的線程模型,即每接受一個(gè)連接請(qǐng)求則產(chǎn)生一個(gè)子線程處理該請(qǐng)求。如圖1所示,Acceptor負(fù)責(zé)監(jiān)聽各個(gè)Client的連接請(qǐng)求,當(dāng)接收到Client的連接請(qǐng)求之后為每個(gè)Client創(chuàng)建一個(gè)新的線程進(jìn)行處理,處理完成之后再通過輸出流返回應(yīng)答消息給每個(gè)Client,之后線程銷毀。
該架構(gòu)最大的問題就是不具備彈性伸縮能力,當(dāng)客戶端數(shù)量增加后,服務(wù)端的線程個(gè)數(shù)和并發(fā)訪問數(shù)成線性正比,由于線程是JAVA虛擬機(jī)非常寶貴的系統(tǒng)資源,當(dāng)線程數(shù)膨脹之后,系統(tǒng)的性能急劇下降,隨著并發(fā)量的繼續(xù)增加,可能會(huì)發(fā)生句柄溢出、線程堆棧溢出等問題,并導(dǎo)致服務(wù)器最終宕機(jī)[3]。
3 Netty原理及其概述
NIO類庫(kù)和API繁雜,使用麻煩工作量和難度都非常大,并且JDK NIO本身存在BUG,例如epoll bug,它會(huì)導(dǎo)致Selector空輪詢,最終導(dǎo)致CPU100%[2]。目前主流的NIO框架包括Mina、Netty、Grizzly等。Mina是Apache組織的一個(gè)開源的項(xiàng)目,Netty從某種程度上來說是Mina的延伸和擴(kuò)展,對(duì)Mina的設(shè)計(jì)上進(jìn)行了一些優(yōu)化。而Grizzly由sun公司開發(fā),專門解決多客戶端訪問服務(wù)器時(shí)產(chǎn)生的各種問題。本文通過調(diào)研,決定采用Netty作為底層源碼實(shí)現(xiàn)服務(wù)端。
Netty是一款NIO的客戶端服務(wù)器框架,能夠快速而簡(jiǎn)單的開發(fā)出高性能的、可維護(hù)的網(wǎng)絡(luò)應(yīng)用比如協(xié)議服務(wù)器和客戶端[6]。Netty 可以通過調(diào)整參數(shù)靈活配置成Reactor 單線程、多線程和主從多線程模型,用少量的線程即可以處理上萬(wàn)條TCP 連接,同時(shí)Netty 中集成了主流的編解碼框架和靈活的自定義編解碼器實(shí)現(xiàn),能輕松實(shí)現(xiàn)私有的協(xié)議棧[5]。 同時(shí)Netty具有以下優(yōu)點(diǎn)[2]:
1)對(duì)原生的NIO類庫(kù)進(jìn)行封裝,并且預(yù)置了多種編解碼器,便于使用;
2)同時(shí)支持阻塞以及非阻塞的socket;
3)支持多種主流的協(xié)議,如TCP,UDP,HTTP,SMTP等;
4)可擴(kuò)展性高,可以通過自定義的ChannelHandler對(duì)框架進(jìn)行靈活的擴(kuò)展;
5)與其他主流NIO框架相比,性能最優(yōu)。
4 設(shè)計(jì)與分析
4.1 總體設(shè)計(jì)
本文是以Netty[6]為底層框架,將Netty對(duì)私有協(xié)議編解碼的支持封裝,提供可以解析符合水利行業(yè)標(biāo)準(zhǔn)信息報(bào)文的通用型水文監(jiān)測(cè)服務(wù)端。其硬件部署圖如圖3所示,遙測(cè)站監(jiān)測(cè)數(shù)據(jù),通過無(wú)線通信網(wǎng)絡(luò)發(fā)送到遠(yuǎn)端中心站進(jìn)行處理。
4.2 詳細(xì)設(shè)計(jì)
水文監(jiān)測(cè)服務(wù)端的主要任�帳譴�理與遙測(cè)站之間的數(shù)據(jù)交互,這里主要指的是各類水文信息報(bào)文。水文信息報(bào)文幀結(jié)構(gòu)如表1所示,采用中華人民共和國(guó)水利部規(guī)定的水文監(jiān)測(cè)數(shù)據(jù)通信規(guī)約[7] ,報(bào)文正文主要由兩類報(bào)文構(gòu)成,一是遙測(cè)站主動(dòng)發(fā)送給服務(wù)端的報(bào)文,包括鏈路維持報(bào),測(cè)試報(bào),均勻時(shí)段水文信息報(bào),遙測(cè)站定時(shí)報(bào),遙測(cè)站小時(shí)報(bào)等;二是中心站查詢信息報(bào)文,包括中心站查詢遙測(cè)站實(shí)時(shí)數(shù)據(jù),中心站查詢遙測(cè)站時(shí)段數(shù)據(jù),中心站查詢遙測(cè)站指定要素實(shí)時(shí)數(shù)據(jù),中心站修改遙測(cè)站基本配置數(shù)據(jù),中心站查詢遙測(cè)站狀態(tài)和報(bào)警信息等。
4.2.1 TCP粘包/拆包
服務(wù)端從網(wǎng)絡(luò)中接收到的是一系列二進(jìn)制的數(shù)據(jù),這些數(shù)據(jù)在未處理之前,用戶是無(wú)法識(shí)別的,因此,對(duì)這些數(shù)據(jù)的解析就顯得尤為重要。
數(shù)據(jù)傳輸?shù)倪^程中,TCP底層并不了解上層的業(yè)務(wù),在包劃分時(shí),只會(huì)根據(jù)自身緩沖區(qū)進(jìn)行,因此,不能保證接收到的消息為整包消息,F(xiàn)假設(shè)客戶端向服務(wù)端發(fā)送兩個(gè)數(shù)據(jù)包B1,B2,則服務(wù)端在接收客戶端數(shù)據(jù)時(shí)可能存在以下幾種情況[2]:
1)服務(wù)端分兩次收到了兩個(gè)完整的獨(dú)立的數(shù)據(jù)包;
2)服務(wù)端一次收到了兩個(gè)粘在一起的包,即TCP粘包;
3)服務(wù)端分兩次收到兩個(gè)數(shù)據(jù)包,第一次的包是完整的B1和部分B2,第二次的包是余下的B2;
4)服務(wù)端分兩次收到兩個(gè)數(shù)據(jù)包,第一次是部分B1,第二次是余下的B1和完整的B2;
5)當(dāng)數(shù)據(jù)包比較大而緩沖區(qū)比較小的時(shí)候,服務(wù)端分多次才能將B1,B2接收完全。
針對(duì)TCP粘包問題提出以下拆包策略:
6)根據(jù)特定的分隔符對(duì)報(bào)文進(jìn)行分割
7)在包尾增加回車換行符進(jìn)行分割;
8)消息長(zhǎng)度固定;
9)根據(jù)消息中的長(zhǎng)度字段解析報(bào)文。
4.2.2 業(yè)務(wù)數(shù)據(jù)編碼/解碼
ObjectEncoder和ObjectDecoder是Netty提供的一組用于POJO的序列化及反序列化即編碼和解碼的處理類。對(duì)象序列化是對(duì)象持久化的一種實(shí)現(xiàn)方法,它是將一個(gè)對(duì)象的屬性和方法轉(zhuǎn)化為一種序列化的格式以用于存儲(chǔ)和傳輸,反序列化就是根據(jù)這些保存的信息重建對(duì)象的過程[8]。
當(dāng)服務(wù)端向客戶端發(fā)送消息時(shí),需要對(duì)業(yè)務(wù)消息進(jìn)行編碼,即將實(shí)體類對(duì)象序列化為ByteBuf,不需要與TCP層打交道,也就不存在粘包問題。此時(shí)只需考慮編碼問題,ObjectEncoder是Java序列化編碼器,它負(fù)責(zé)將實(shí)現(xiàn)Serializable接口的對(duì)象序列化為byte [],然后寫入到ByteBuf中用于消息的跨網(wǎng)絡(luò)傳輸。編碼過后的數(shù)據(jù)結(jié)構(gòu)如圖7所示,經(jīng)過ObjectEncoder編碼之后的數(shù)據(jù)包會(huì)默認(rèn)在包頭添加length字段默認(rèn)為4Byte,用來標(biāo)識(shí)數(shù)據(jù)包正文部分的長(zhǎng)度。
4.2.3 業(yè)務(wù)數(shù)據(jù)處理
經(jīng)過ObjectDecoder解碼過后的數(shù)據(jù)包是一個(gè)完整的包,對(duì)應(yīng)一個(gè)業(yè)務(wù)消息,需要做進(jìn)一步的業(yè)務(wù)處理。MyServerHandler是繼承自ChannelHandlerAdapter的一個(gè)業(yè)務(wù)處理類,并覆蓋了其messageReceived、exceptionCaught等方法,用來對(duì)業(yè)務(wù)數(shù)據(jù)進(jìn)行處理。MyServerHandler基于事件驅(qū)動(dòng)機(jī)制,當(dāng)接收到消息時(shí),會(huì)激活messageReceived方法,獲取功能碼,由此來判斷消息類型,然后獲取校驗(yàn)碼,做循環(huán)冗余校驗(yàn),經(jīng)過校驗(yàn)無(wú)誤之后,存儲(chǔ)到相應(yīng)的數(shù)據(jù)庫(kù)表中供整個(gè)系統(tǒng)其他模塊的使用。
5 實(shí)現(xiàn)與測(cè)試
5.1 實(shí)現(xiàn)步驟
1)創(chuàng)建一組EventLoopGroup,分別用來負(fù)責(zé)接收客戶端和處理客戶端連接,并創(chuàng)建一個(gè)ServerBootstrap實(shí)例;
2)設(shè)置channel屬性:ChannelOption.SO_BACKLOG使消息立即發(fā)出去,不用等到一定的數(shù)據(jù)量才發(fā)出去,ChannelOption.SO_KEEPALIVE保持長(zhǎng)連接;
3)定制自己的ChannelPipeline,加入相應(yīng)的ChannelHandler;
4)綁定端口,并監(jiān)聽在該端口上的客戶端的請(qǐng)求并異步的處理;
5)開啟存數(shù)據(jù)庫(kù)與發(fā)送消息線程;
6)編寫自己的ChannelHandler。
5.2 測(cè)試結(jié)果
按照實(shí)現(xiàn)步驟開啟服務(wù)端,通過一臺(tái)PC機(jī)模擬客戶端,PC機(jī)的配置為:32位Win7操作系統(tǒng),2GB內(nèi)存,Inter Core i3處理器,通過該P(yáng)C機(jī)網(wǎng)服務(wù)端發(fā)送消息,服務(wù)端接收結(jié)果如圖9所示。
6 結(jié)束語(yǔ)
本文設(shè)計(jì)并實(shí)現(xiàn)了基于Netty的水文監(jiān)測(cè)服務(wù)端,借鑒了許多開源軟件的設(shè)計(jì)思想,始終保持高內(nèi)聚低耦合的設(shè)計(jì)理念,為今后程序的擴(kuò)展提供了方便。
雖然,本文所設(shè)計(jì)的服務(wù)端已經(jīng)能基本滿足項(xiàng)目的需求,但是功能上也有待進(jìn)一步完善。下一步所要完善的方向?yàn)槿绾卧诙嗫蛻舳诉B接的同時(shí),服務(wù)端向指定客戶端的消息推送問題以及網(wǎng)頁(yè)客戶端如何查詢指定遙測(cè)站客戶端信息的問題,同時(shí)系統(tǒng)的性能還需進(jìn)一步優(yōu)化以滿足更多客戶端的接入。
參考文獻(xiàn):
[1] 陳威, 郭書普. 中國(guó)農(nóng)業(yè)信息化技術(shù)發(fā)展現(xiàn)狀及存在的問題[J]. 農(nóng)業(yè)工程學(xué)報(bào), 2013(22): 196-204.
[2] 李林峰. Netty權(quán)威指南[M]. 北京: 電子工業(yè)出版社, 2014.
[3] 李林峰. NIO框架Netty解析[EB/OL]. (2016-04-11).http://www.jiagoushuo.com/article/1000126.html.
[4] 李林峰. Netty 系列之Netty 線程模型[EB/OL].( 2014-07-11)http://www.infoq.com/cn/articles/netty-threading- model.
[5] 代超, 鄧中亮. 基于Netty的面向移動(dòng)終端的推送服務(wù)設(shè)計(jì)[J]. 軟件, 2015, 36(12): 01-04.
[6] JBoss. Netty project[CP/OL]. http://netty.io/,2012-4-1
[7] 中華人民共和國(guó)水利部. 水文監(jiān)測(cè)數(shù)據(jù)通信規(guī)約[M]. 北京: 中國(guó)水利水電出版社, 2014.
[8] 郭荷清, 王增勛. XML數(shù)據(jù)綁定及對(duì)象序列化的應(yīng)用研究[J]. 計(jì)算機(jī)應(yīng)用與軟件, 2006, 23(5).
[9] Norman Maurer. Netty in Action[M]. 5th ed.Connecticut: Manning Publications Co., 2013.
[10] Alan Bateman. New I /O in JDK 7[R]. Java-One, 2008.