摘要:水文監(jiān)測系統(tǒng)是一個提供水文部門監(jiān)測各類水文信息的平臺。業(yè)務功能比較豐富,主要包括遙測站的定位、狀態(tài)的查詢、實時信息以及往年水文信息的查詢等。水文監(jiān)測系統(tǒng)需要異步處理各個遙測站發(fā)來的消息,包括信息的收集、分析與存儲以及網頁端的實時信息的查詢。傳統(tǒng)的水文監(jiān)測服務端多采用BIO的方式接受處理數據,該文在比較傳統(tǒng)BIO技術的基礎上,采用NIO的方式處理水文類數據。通過對NIO框架Netty源碼的封裝與擴展,提供能接受處理客戶端異步請求的通用的可重用的水文監(jiān)測服務端的設計思路與具體實現。 

  關鍵詞:水文監(jiān)測;NIO;Netty框架;異步  

  1 背景 

  水文監(jiān)測系統(tǒng)主要應用與水文部門對各個地區(qū)的降水量、河道、水庫(湖泊)以及地下水等的監(jiān)測與統(tǒng)計,從而制定出相應的對策。傳統(tǒng)的水文監(jiān)測普遍采用人工監(jiān)測和有線傳播等方式進行,這種方式存在很嚴重的弊端,人工監(jiān)測的成本高,信息采集的準確度不高,并且信息的實時性也不高,這樣給水文部門制定相應的策略的時候造成很大的干擾。隨著科學技術的不斷發(fā)展,現如今的水文監(jiān)測技術已經發(fā)展的比較成熟,其中主要涉及傳感器技術、嵌入式技術、通信技術、存儲技術、信息處理技術以及人工智能等多種高新信息技術[1]。 

  網絡編程的基本模型是Client/Server模型,也就是兩個進程之間進行相互通信,其中服務端提供位置信息(綁定的IP地址和監(jiān)聽端口),客戶端通過連接操作向服務端監(jiān)聽的地址發(fā)起連接請求,通過三次握手建立連接,如果連接成功,雙方就可以通過網絡套接字(Socket)進行通信[2]。 

  2 BIO與NIO的比較 

  傳統(tǒng)的網絡服務器使用BIO(阻塞IO)開發(fā),多采用一連接一線程(One thread per connection)的線程模型,即每接受一個連接請求則產生一個子線程處理該請求。如圖1所示,Acceptor負責監(jiān)聽各個Client的連接請求,當接收到Client的連接請求之后為每個Client創(chuàng)建一個新的線程進行處理,處理完成之后再通過輸出流返回應答消息給每個Client,之后線程銷毀。 

  該架構最大的問題就是不具備彈性伸縮能力,當客戶端數量增加后,服務端的線程個數和并發(fā)訪問數成線性正比,由于線程是JAVA虛擬機非常寶貴的系統(tǒng)資源,當線程數膨脹之后,系統(tǒng)的性能急劇下降,隨著并發(fā)量的繼續(xù)增加,可能會發(fā)生句柄溢出、線程堆棧溢出等問題,并導致服務器最終宕機[3]。 

  3 Netty原理及其概述 

  NIO類庫和API繁雜,使用麻煩工作量和難度都非常大,并且JDK NIO本身存在BUG,例如epoll bug,它會導致Selector空輪詢,最終導致CPU100%[2]。目前主流的NIO框架包括Mina、Netty、Grizzly等。Mina是Apache組織的一個開源的項目,Netty從某種程度上來說是Mina的延伸和擴展,對Mina的設計上進行了一些優(yōu)化。而Grizzly由sun公司開發(fā),專門解決多客戶端訪問服務器時產生的各種問題。本文通過調研,決定采用Netty作為底層源碼實現服務端。 

  Netty是一款NIO的客戶端服務器框架,能夠快速而簡單的開發(fā)出高性能的、可維護的網絡應用比如協議服務器和客戶端[6]。Netty 可以通過調整參數靈活配置成Reactor 單線程、多線程和主從多線程模型,用少量的線程即可以處理上萬條TCP 連接,同時Netty 中集成了主流的編解碼框架和靈活的自定義編解碼器實現,能輕松實現私有的協議棧[5]。   同時Netty具有以下優(yōu)點[2]: 

  1)對原生的NIO類庫進行封裝,并且預置了多種編解碼器,便于使用; 

  2)同時支持阻塞以及非阻塞的socket; 

  3)支持多種主流的協議,如TCP,UDP,HTTP,SMTP等; 

  4)可擴展性高,可以通過自定義的ChannelHandler對框架進行靈活的擴展; 

  5)與其他主流NIO框架相比,性能最優(yōu)。 

  4 設計與分析 

  4.1 總體設計 

  本文是以Netty[6]為底層框架,將Netty對私有協議編解碼的支持封裝,提供可以解析符合水利行業(yè)標準信息報文的通用型水文監(jiān)測服務端。其硬件部署圖如圖3所示,遙測站監(jiān)測數據,通過無線通信網絡發(fā)送到遠端中心站進行處理。 

  4.2 詳細設計 

  水文監(jiān)測服務端的主要任�帳譴�理與遙測站之間的數據交互,這里主要指的是各類水文信息報文。水文信息報文幀結構如表1所示,采用中華人民共和國水利部規(guī)定的水文監(jiān)測數據通信規(guī)約[7] ,報文正文主要由兩類報文構成,一是遙測站主動發(fā)送給服務端的報文,包括鏈路維持報,測試報,均勻時段水文信息報,遙測站定時報,遙測站小時報等;二是中心站查詢信息報文,包括中心站查詢遙測站實時數據,中心站查詢遙測站時段數據,中心站查詢遙測站指定要素實時數據,中心站修改遙測站基本配置數據,中心站查詢遙測站狀態(tài)和報警信息等。 

  4.2.1 TCP粘包/拆包 

  服務端從網絡中接收到的是一系列二進制的數據,這些數據在未處理之前,用戶是無法識別的,因此,對這些數據的解析就顯得尤為重要。 

  數據傳輸的過程中,TCP底層并不了解上層的業(yè)務,在包劃分時,只會根據自身緩沖區(qū)進行,因此,不能保證接收到的消息為整包消息。現假設客戶端向服務端發(fā)送兩個數據包B1,B2,則服務端在接收客戶端數據時可能存在以下幾種情況[2]: 

  1)服務端分兩次收到了兩個完整的獨立的數據包; 

  2)服務端一次收到了兩個粘在一起的包,即TCP粘包; 

  3)服務端分兩次收到兩個數據包,第一次的包是完整的B1和部分B2,第二次的包是余下的B2; 

  4)服務端分兩次收到兩個數據包,第一次是部分B1,第二次是余下的B1和完整的B2; 

  5)當數據包比較大而緩沖區(qū)比較小的時候,服務端分多次才能將B1,B2接收完全。 

  針對TCP粘包問題提出以下拆包策略: 

  6)根據特定的分隔符對報文進行分割 

  7)在包尾增加回車換行符進行分割; 

  8)消息長度固定; 

  9)根據消息中的長度字段解析報文。 

  4.2.2 業(yè)務數據編碼/解碼 

  ObjectEncoder和ObjectDecoder是Netty提供的一組用于POJO的序列化及反序列化即編碼和解碼的處理類。對象序列化是對象持久化的一種實現方法,它是將一個對象的屬性和方法轉化為一種序列化的格式以用于存儲和傳輸,反序列化就是根據這些保存的信息重建對象的過程[8]。 

  當服務端向客戶端發(fā)送消息時,需要對業(yè)務消息進行編碼,即將實體類對象序列化為ByteBuf,不需要與TCP層打交道,也就不存在粘包問題。此時只需考慮編碼問題,ObjectEncoder是Java序列化編碼器,它負責將實現Serializable接口的對象序列化為byte [],然后寫入到ByteBuf中用于消息的跨網絡傳輸。編碼過后的數據結構如圖7所示,經過ObjectEncoder編碼之后的數據包會默認在包頭添加length字段默認為4Byte,用來標識數據包正文部分的長度。 

  4.2.3 業(yè)務數據處理 

  經過ObjectDecoder解碼過后的數據包是一個完整的包,對應一個業(yè)務消息,需要做進一步的業(yè)務處理。MyServerHandler是繼承自ChannelHandlerAdapter的一個業(yè)務處理類,并覆蓋了其messageReceived、exceptionCaught等方法,用來對業(yè)務數據進行處理。MyServerHandler基于事件驅動機制,當接收到消息時,會激活messageReceived方法,獲取功能碼,由此來判斷消息類型,然后獲取校驗碼,做循環(huán)冗余校驗,經過校驗無誤之后,存儲到相應的數據庫表中供整個系統(tǒng)其他模塊的使用。 

  5 實現與測試 

  5.1 實現步驟 

  1)創(chuàng)建一組EventLoopGroup,分別用來負責接收客戶端和處理客戶端連接,并創(chuàng)建一個ServerBootstrap實例; 

  2)設置channel屬性:ChannelOption.SO_BACKLOG使消息立即發(fā)出去,不用等到一定的數據量才發(fā)出去,ChannelOption.SO_KEEPALIVE保持長連接; 

  3)定制自己的ChannelPipeline,加入相應的ChannelHandler; 

  4)綁定端口,并監(jiān)聽在該端口上的客戶端的請求并異步的處理; 

  5)開啟存數據庫與發(fā)送消息線程; 

  6)編寫自己的ChannelHandler。 

  5.2 測試結果 

  按照實現步驟開啟服務端,通過一臺PC機模擬客戶端,PC機的配置為:32位Win7操作系統(tǒng),2GB內存,Inter Core i3處理器,通過該PC機網服務端發(fā)送消息,服務端接收結果如圖9所示。 

  6 結束語 

  本文設計并實現了基于Netty的水文監(jiān)測服務端,借鑒了許多開源軟件的設計思想,始終保持高內聚低耦合的設計理念,為今后程序的擴展提供了方便。 

  雖然,本文所設計的服務端已經能基本滿足項目的需求,但是功能上也有待進一步完善。下一步所要完善的方向為如何在多客戶端連接的同時,服務端向指定客戶端的消息推送問題以及網頁客戶端如何查詢指定遙測站客戶端信息的問題,同時系統(tǒng)的性能還需進一步優(yōu)化以滿足更多客戶端的接入。 

  參考文獻: 

  [1] 陳威, 郭書普. 中國農業(yè)信息化技術發(fā)展現狀及存在的問題[J]. 農業(yè)工程學報, 2013(22): 196-204. 

  [2] 李林峰. Netty權威指南[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的面向移動終端的推送服務設計[J]. 軟件, 2015, 36(12): 01-04. 

  [6] JBoss. Netty project[CP/OL]. http://netty.io/,2012-4-1 

  [7] 中華人民共和國水利部. 水文監(jiān)測數據通信規(guī)約[M]. 北京: 中國水利水電出版社, 2014. 

  [8] 郭荷清, 王增勛. XML數據綁定及對象序列化的應用研究[J]. 計算機應用與軟件, 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.