薛元慶,孟鳳嬌,張海峰
(1.天津市醫(yī)藥集團(tuán)有限公司,天津 300410;2.南開大學(xué)計算機(jī)與控制工程學(xué)院,天津 300071)
對軟件系統(tǒng)的操作正確與否直接影響工作的質(zhì)量,有時錄入信息的簡單失誤會造成重大事故和經(jīng)濟(jì)損失。因此,許多企業(yè)需要對第三方軟件錄入信息和操作留痕自動監(jiān)控和追溯,但對第三方軟件系統(tǒng)錄入信息的監(jiān)控追溯的研究和實現(xiàn)一直是空白,而其又是重要的值得研究和實現(xiàn)的課題。同時,企業(yè)的業(yè)務(wù)是由操作多種不同軟件系統(tǒng)協(xié)同完成的,這些軟件之間沒有接口,但需要這些軟件的業(yè)務(wù)信息共享或者集成使用以形成業(yè)務(wù)信息流,這種需求可以借助本文的信息留痕研究工作實現(xiàn)信息集成,例如電子碼監(jiān)管系統(tǒng)要求完成對客戶端用戶軟件操作的信息留痕[1],自動抓取操作界面上的關(guān)注信息,以及完成這些信息在服務(wù)端和客戶端的關(guān)聯(lián)和集成。本文研究對用戶端的軟件操作進(jìn)行監(jiān)控,有信息錄入問題時提醒,以及事后追溯問題操作人、填寫信息內(nèi)容等,為此,設(shè)計了C/S架構(gòu),服務(wù)器端負(fù)責(zé)匯集所有客戶端自動捕獲客戶端軟件系統(tǒng)中填寫的數(shù)據(jù)信息,將散落在不同地點的系統(tǒng)錄入操作的離散數(shù)據(jù)匯集之后再進(jìn)行序列化處理[2],較為完整地展示出電子監(jiān)管碼從申請到成品出庫的完整過程,從而可達(dá)到對藥品的生產(chǎn)過程中與電子碼操作相關(guān)的所有信息的追溯,還可以從系統(tǒng)采集藥品質(zhì)量相關(guān)的各種基礎(chǔ)數(shù)據(jù),從而可以進(jìn)行藥品生產(chǎn)、質(zhì)量分析等工作??蛻舳素?fù)責(zé)抓取從相應(yīng)電子碼軟件錄入的相關(guān)數(shù)據(jù)[3],這些大量的留痕數(shù)據(jù)(數(shù)據(jù)記錄、文件)還要實時地通過網(wǎng)絡(luò)自動同步到服務(wù)端;然后與服務(wù)器端的電子碼信息數(shù)據(jù)進(jìn)行比對,比對失敗時給出錯誤預(yù)警??梢姺?wù)端和客戶端之間通信量相當(dāng)大,這會造成猝發(fā)式的網(wǎng)絡(luò)通信而產(chǎn)生數(shù)據(jù)包丟失的現(xiàn)象。為此,本文為保障基于C/S實現(xiàn)的電子碼監(jiān)管系統(tǒng)中的客戶和服務(wù)器之間的有效通信,設(shè)計研究了網(wǎng)絡(luò)通信組件;為解決數(shù)據(jù)包猝發(fā)等問題[4],在所設(shè)計的組件中引進(jìn)了時間驅(qū)動機(jī)制和超時重發(fā)機(jī)制[5],解決了數(shù)據(jù)包猝發(fā)帶來的UDP丟包問題,使客戶和服務(wù)器能相互有效地通信,實現(xiàn)兩端透明傳輸。
這些網(wǎng)絡(luò)通信組件主要的功能有:命令的封裝和解析、發(fā)送和接收數(shù)據(jù)、發(fā)送和接收狀態(tài)的反饋[6],從而使得在網(wǎng)絡(luò)通信組件基礎(chǔ)上實現(xiàn)的應(yīng)用程序可以很方便地實現(xiàn)身份驗證(登錄、注銷、修改用戶名/密碼)、文件上傳/下載、消息發(fā)送/接收等一系列的功能。但在真實網(wǎng)絡(luò)環(huán)境中,往往存在很多猝發(fā)式的請求,會造成大量的UDP包延遲發(fā)送甚至丟失,致使服務(wù)端和客戶端不能正常交互工作,影響通信效果和質(zhì)量,難以實現(xiàn)電子碼操作的留痕和監(jiān)控[7]。網(wǎng)絡(luò)通信組件工作原理如圖1所示。
圖1 網(wǎng)絡(luò)通信組件的工作原理
本系統(tǒng)中網(wǎng)絡(luò)通信組件采用TCP和UDP兩種連接方式來為上層軟件提供基礎(chǔ)的網(wǎng)絡(luò)通信服務(wù)。由于系統(tǒng)中有很多廣播性的通知,比如版本升級通知,服務(wù)器只需要發(fā)一個廣播數(shù)據(jù)包即可。UDP協(xié)議自身的優(yōu)點決定其在網(wǎng)絡(luò)通信中被大量地應(yīng)用。在本文設(shè)計的系統(tǒng)中也一樣,只在客戶端登錄、傳輸文件時才使用TCP協(xié)議,其他的地方全部使用UDP協(xié)議。
網(wǎng)絡(luò)通信組件是為服務(wù)端和客戶端提供透明傳輸服務(wù),并確保兩端信息準(zhǔn)確無誤傳遞的,因此設(shè)計原理也要與服務(wù)端和客戶端實現(xiàn)的功能相適應(yīng)。
當(dāng)任何一端發(fā)送數(shù)據(jù)時,該端的通訊軟件把將要發(fā)送的信息內(nèi)容和目的地址信息傳遞給網(wǎng)絡(luò)通信組件,由網(wǎng)絡(luò)通信組件對其進(jìn)行命令封裝、格式轉(zhuǎn)換,然后發(fā)送到另一端。同時,發(fā)送的時機(jī)、發(fā)送的質(zhì)量(是否即時、成功)、發(fā)送請求時的優(yōu)先調(diào)度、發(fā)送結(jié)果處理等事情,全部由網(wǎng)絡(luò)通信組件自己處理。
命令的封裝與解析如圖2所示。
圖2 命令的封裝與解析過程示意圖
某端需要發(fā)送網(wǎng)絡(luò)數(shù)據(jù)時,先把要發(fā)送的數(shù)據(jù)和地址信息傳遞給網(wǎng)絡(luò)通信組件,由網(wǎng)絡(luò)通信組件進(jìn)行命令的封裝和發(fā)送。命令封裝時,需要將同步字(SYN)、接收用戶編號、命令類型和數(shù)據(jù)項信息都組裝在一起[8];然后將封裝好的數(shù)據(jù)信息傳遞給傳輸層,再添加協(xié)議頭部,最終發(fā)送到接收用戶所在的終端上[9]。
網(wǎng)絡(luò)通信組件接收到數(shù)據(jù)后,首先需要進(jìn)行逆向解析,將發(fā)送端封裝的數(shù)據(jù)信息準(zhǔn)確地解析出來;解析的過程與封裝的過程相反,只要按照命令封裝采用的命令格式,就能準(zhǔn)確無誤地解析出接收數(shù)據(jù)中的各個字段。命令解析完成后,由網(wǎng)絡(luò)通信組件或者解析端的上層軟件根據(jù)命令來處理相應(yīng)的數(shù)據(jù)信息。
因為網(wǎng)絡(luò)通信組件大量使用了UDP協(xié)議,而UDP通信是無連接的、不可靠的[10],故在設(shè)計網(wǎng)絡(luò)通信組件時加入了SYN字段以保證通信質(zhì)量。命令中的SYN是同步字段,它是一個正整數(shù),其值由發(fā)送方定義,且每發(fā)出一條命令,SYN的值就加1;接收方可以根據(jù)SYN值來判斷哪些數(shù)據(jù)包是重復(fù)發(fā)送的,從而可以丟棄重復(fù)的數(shù)據(jù)包。此外,接收方還可直接使用SYN來封裝應(yīng)答數(shù)據(jù)包,使得發(fā)送方能根據(jù)收到的應(yīng)答包判斷哪些數(shù)據(jù)包發(fā)送成功。
在封裝的命令中還包含一個非常關(guān)鍵的字段就是命令類型字段,接收方就是根據(jù)這個字段來判斷應(yīng)對解析數(shù)據(jù)作何處理。為方便上層軟件的處理工作,網(wǎng)絡(luò)通信組件定義了一系列命令類型,表1列出了其中的一部分命令。
表1 網(wǎng)絡(luò)通信組件命令列表
表 1中,CLIENT_LOGIN、CLIENT_UPLOADFILE和CLIENT_UPLOADFILE使用了TCP協(xié)議,其他命令采用的是UDP協(xié)議。例如CLIENT_BR_SEARCHSERVER,其中CLIENT代表該命令是從客戶端發(fā)出去的,BR代表廣播,SEARCHSERVER代表請求服務(wù)器的IP地址。這種廣播形式的命令發(fā)出去后所有的客戶端都能收到,使用起來比較方便。但這種廣播包并不是在所有環(huán)境下都能使用,例如有些路由器不允許廣播包從一個網(wǎng)段傳播到另一個網(wǎng)段,在這種網(wǎng)絡(luò)環(huán)境下,會導(dǎo)致部署在不同網(wǎng)段的服務(wù)器和客戶端彼此無法收到對方發(fā)出的廣播包。為避免這類問題,確保本系統(tǒng)設(shè)計的網(wǎng)絡(luò)通信組件可以適應(yīng)比較復(fù)雜的網(wǎng)絡(luò)架構(gòu),就要改變通訊方式,例如處理客戶端注銷時,原本在簡單網(wǎng)絡(luò)環(huán)境下可以使用一條CLIENT_BR_LOGOFF通知服務(wù)器和所有在線用戶某個客戶端下線了,但如果不使用廣播方式的話,就要先發(fā)送一條CLIENT_LOGOFF將下線信息發(fā)給服務(wù)器,然后由服務(wù)器給每個在線用戶逐一發(fā)送SERVER_LOGOFF命令。
封裝的命令中的最后一個字段是附加字段,代表要發(fā)送的數(shù)據(jù),這個字段比較復(fù)雜,它按照收發(fā)雙方均可理解的組合規(guī)則由若干個字段自由組合而成。但使用UDP發(fā)送的數(shù)據(jù)包不大于64 kB,所以除去數(shù)據(jù)報首部、SYN、發(fā)送用戶信息等,網(wǎng)絡(luò)通信組件通過UDP協(xié)議所發(fā)送的最大附加字段要小于64 kB;因此,上層軟件傳遞過來的附加字段如果過長的話,網(wǎng)絡(luò)通信組件在命令封裝時會自動進(jìn)行截斷。所以,本文采用 UDP協(xié)議發(fā)送比較短的數(shù)據(jù)包(小于64 kB),如控制命令、狀態(tài)信息、即時消息等。如果發(fā)送文件之類的信息,則本文通信協(xié)議采用TCP。
服務(wù)端和客戶端的網(wǎng)絡(luò)通信組件的功能雖略有不同但基本功能是一致的,因此,本文僅以服務(wù)端的通信組件設(shè)計為例介紹其工作過程,圖3是服務(wù)器端的網(wǎng)絡(luò)通信組件的工作過程的描述。
啟動服務(wù)器軟件后會自動調(diào)用網(wǎng)絡(luò)通信組件的接口 StartServer[11],再由 StartServer創(chuàng)建并啟動 2 個監(jiān)聽線程:TcpThread和UdpThread,它們分別用來監(jiān)聽特定的TCP端口和UDP端口,這些端口是把用戶設(shè)置的信息寫在配置文件中的。當(dāng)網(wǎng)絡(luò)通信組件初始化時,先從配置文件中讀取端口信息,當(dāng)啟動監(jiān)聽線程后就會自動監(jiān)聽這些端口信息。如果沒有設(shè)置監(jiān)聽端口,服務(wù)端軟件和客戶端軟件會使用自己的默認(rèn)端口號,當(dāng)開啟監(jiān)聽線程后會自動監(jiān)聽其默認(rèn)端口。當(dāng)然用戶可以修改默認(rèn)的端口號,指定的端口號只需要在1024~49151的范圍內(nèi)并且沒有被別的程序占用即可。另外,UDP線程和TCP線程的開啟也非常自由,并不會相互限制,例如想監(jiān)聽到UDP端口,那么只要開啟UdpThread即可,如圖3所示的客戶端;當(dāng)然也可以同時使用這2個線程,那么這就需要同時開啟UdpThread和TcpThread,如圖3所示的服務(wù)器端。所有工作完成,應(yīng)用程序退出時,調(diào)用網(wǎng)絡(luò)通信組件提供的接口 StopServer,它會終止TcpThread和UdpThread線程。
當(dāng)開啟TcpThread后,TcpThread線程首先會創(chuàng)建一個TcpListener,并將其綁定到由本機(jī)的IP地址和監(jiān)聽的端口號所組成的套接字上;TcpListener啟動之后就會循環(huán)等待連接請求,一旦監(jiān)聽到新的請求就會創(chuàng)建一個TcpClient專門處理終端的請求連接,而通信的具體過程則交給線程池中的線程進(jìn)行處理,同時監(jiān)聽線程會等待下一個連接請求的到來。UdpThread比TcpThread要稍微簡單一點,它是無連接的,只需要接收數(shù)據(jù)包并處理即可。UdpThread線程啟動后會創(chuàng)建一個UdpClient,并命令UdpClient監(jiān)聽所要監(jiān)聽的UDP端口。UdpClient會循環(huán)讀取該端口上的數(shù)據(jù)包,一旦接收到數(shù)據(jù)包后,立即解析,將解析出的命令交給線程池中的線程處理,同時Udp-Client繼續(xù)監(jiān)聽端口。無論是 TcpThread還是UdpThread,它們接收到的數(shù)據(jù)經(jīng)過線程池處理后,必要時都要交給上層軟件做后續(xù)處理。
由于服務(wù)端主動發(fā)送數(shù)據(jù)時采用的全部是UDP協(xié)議,所以服務(wù)端主動發(fā)送數(shù)據(jù)的過程比較簡單,只需要調(diào)用網(wǎng)絡(luò)通信組件提供的OnMessageAdd接口即可。當(dāng)發(fā)送條件允許時就發(fā)送出去。否則,暫時緩存該消息。
服務(wù)端軟件服務(wù)停止時,會調(diào)用網(wǎng)絡(luò)通信組件提供的StopServer接口。StopServer負(fù)責(zé)終止TcpThread和UdpThread。
TcpListener監(jiān)聽到連接請求和UdpClient接收到數(shù)據(jù)包后,會后續(xù)地處理線程池中緩存的線程。這樣做的目的是
1)不耽誤TcpListener和UdpClient的監(jiān)聽工作;TcpListener和UdpClient專門負(fù)責(zé)監(jiān)聽工作,而監(jiān)聽到連接或數(shù)據(jù)包之后的具體處理工作交給線程池來解決。
2)線程池可以緩存使用過的線程并循環(huán)利用,能大大提高處理響應(yīng)速度;尤其是當(dāng)大量客戶端同時訪問時,采用線程池的處理方法獲得的性能提高更加突出。
初始時,線程池中并沒有線程。當(dāng)TcpListener監(jiān)聽到第一個連接請求時,會由線程池創(chuàng)建第一個線程來處理該連接請求,這個線程的初始化與普通線程的初始化并沒有區(qū)別,不同的地方在于這個線程執(zhí)行完成后并不會自行銷毀,而是以掛起狀態(tài)保存在線程池中。當(dāng)TcpListener有新的請求時,線程池會將掛起的線程激活,而不會創(chuàng)建新的線程。只有當(dāng)監(jiān)聽到大量的連接請求,同時線程池中沒有可以使用的掛起線程時,才會創(chuàng)建新的線程。通過反復(fù)使用已經(jīng)創(chuàng)建的線程,會節(jié)約不少開銷,縮短響應(yīng)時間。在網(wǎng)絡(luò)通信中,使用線程池使得網(wǎng)絡(luò)程序有很好的伸縮性,即使并發(fā)請求較多也不至于時延而連接失效。
圖4給出了線程池的工作原理。線程池中的線程在解析出足夠的信息之后(用戶、命令類型、附加數(shù)據(jù))會以消息事件的方式通知應(yīng)用程序進(jìn)行處理。應(yīng)用程序?qū)δ男┦录信d趣的話,只需要在啟動時登記相應(yīng)的消息即可。
圖4 線程池原理
例如,服務(wù)端對用戶登錄、注銷以及數(shù)據(jù)發(fā)送的狀態(tài)比較感興趣,這樣的話,在服務(wù)端軟件啟動時就注冊這些事件:
Server.UserLogon + = new Server.UserEventHandler(OnUserLogon);
Server.UserLogoff + = new Server.UserEventHandler(OnUserLogoff);
Server.SendSucceed += new erver.SendSucceedEventHandler(OnSendSucceed);
Server.SendFailed + = new Server.SendFailedEventHandler(OnSendFailed);
每當(dāng)網(wǎng)絡(luò)通信組件拋出這些事件時,服務(wù)端軟件就可以處理了。
由于本系統(tǒng)運(yùn)行的實際網(wǎng)絡(luò)環(huán)境中,會存在突發(fā)式的大量請求,而大量發(fā)送的UDP包會延遲甚至丟失。比如客戶端同時提交數(shù)十個訂單,這些訂單的提交數(shù)據(jù)包幾乎并發(fā)地涌向服務(wù)端,服務(wù)端接收之后的狀態(tài)數(shù)據(jù)包又會涌向該客戶端。服務(wù)端除了將狀態(tài)數(shù)據(jù)包發(fā)給訂單提交者之外,還會將這些訂單數(shù)據(jù)包轉(zhuǎn)發(fā)給有審核權(quán)限的用戶。在這個過程中,某個提交數(shù)據(jù)包或狀態(tài)數(shù)據(jù)包的丟失都會導(dǎo)致客戶端訂單提交失敗(或誤以為失敗)。
本文實現(xiàn)的網(wǎng)絡(luò)通信組件雖能進(jìn)行基本的網(wǎng)絡(luò)收發(fā),但還不能很好地應(yīng)對上述問題,為此,本文提出了基于時間驅(qū)動機(jī)制和超時重發(fā)機(jī)制的網(wǎng)絡(luò)通信組件,使之適應(yīng)復(fù)雜網(wǎng)絡(luò)環(huán)境。
為解決數(shù)據(jù)包猝發(fā)問題,網(wǎng)絡(luò)通信組件收到上層應(yīng)用程序的發(fā)送請求時,不再立即將命令封裝并發(fā)送出去,而是按照其輕重緩急程度插入到一個命令隊列中。這個命令隊列是按照時間排序的,距離當(dāng)前時間越近的排得越靠前。應(yīng)用程序請求發(fā)送時,要附帶一個期望時間,網(wǎng)絡(luò)通信組件就根據(jù)這個期望時間來判斷該請求在命令隊列中的位置[12]。
基于上述實現(xiàn)的網(wǎng)絡(luò)通信組件添加的時間驅(qū)動機(jī)制和超時重發(fā)機(jī)制,它們的聯(lián)系比較緊密,如圖5所示。
圖5 基于時間驅(qū)動和超時重發(fā)機(jī)制的網(wǎng)絡(luò)通信組件
圖5中所示的網(wǎng)絡(luò)通信組件被虛線分成了2部分:發(fā)送數(shù)據(jù)包之前的時間驅(qū)動機(jī)制和發(fā)送數(shù)據(jù)包之后的超時重發(fā)機(jī)制。
時間驅(qū)動機(jī)制部分除了新增加一個命令隊列之外,網(wǎng)絡(luò)通信組件還增加了一個驅(qū)動定時器。每當(dāng)命令隊列發(fā)生改變時(插入一個命令、刪除一個命令)都會重置驅(qū)動定時器——取出命令隊列頭部的命令,將其期望時間設(shè)置為驅(qū)動定時器的下一次響應(yīng)時間,一旦到了設(shè)置時間,驅(qū)動定時器的Elapsed事件會自動觸發(fā)。在Elapsed Handler中,從命令隊列頭部取出一個命令,判斷其期望時間,如果到了設(shè)定時間則對其封裝并被發(fā)送,然后將其從隊列中刪除。當(dāng)命令隊列中的所有命令都處理完了后,驅(qū)動定時器就停止工作,等待下一個發(fā)送請求的到來。
在網(wǎng)絡(luò)通信組件設(shè)計中引入時間驅(qū)動機(jī)制的好處:
1)發(fā)送請求有了優(yōu)先級,從而可以根據(jù)其優(yōu)先級來決定先處理哪些請求;
2)猝發(fā)產(chǎn)生的大量請求會緩存到隊列里逐一處理,減少同時發(fā)送大量數(shù)據(jù)時數(shù)據(jù)包丟失的可能;
3)利用時間驅(qū)動機(jī)制能很簡單地實現(xiàn)定時發(fā)送功能,上層軟件定時發(fā)送通知、同步數(shù)據(jù)變得輕而易舉。
不過,引入時間驅(qū)動機(jī)制也會產(chǎn)生一些額外的開銷:
1)需要立即發(fā)送的請求因為要插入到隊列中(即使隊列是空的),設(shè)置驅(qū)動定時器,會有毫秒級的時間延遲;
2)命令隊列的存在增加了內(nèi)存開銷;
3)某些非緊急數(shù)據(jù)包(比如版本升級),會有意地延時一段時間,以避免突發(fā)請求。
舉一個例子來說明時間驅(qū)動機(jī)制的利與弊。假如,當(dāng)一段時間內(nèi)有很多發(fā)送請求時,這樣會使命令隊列變得比較長,但在驅(qū)動定時器的驅(qū)動下,命令隊列中的命令會一個一個地被處理并刪除,內(nèi)存的開銷也不會太大,只不過是因為發(fā)送的請求需要要插入到隊列中,設(shè)置驅(qū)動定時器,會有毫秒級的時間延遲,但是網(wǎng)絡(luò)通信的主要開銷是在網(wǎng)絡(luò)傳輸環(huán)節(jié),毫秒級的時間延遲對于網(wǎng)絡(luò)通信來講微不足道,而這樣卻降低了丟包概率,是非常值得的。
時間驅(qū)動機(jī)制雖可以降低網(wǎng)絡(luò)通信中的丟包概率,但是仍然不能保證不會丟失數(shù)據(jù)包、網(wǎng)絡(luò)擁塞、接收端來不及處理等情況,這些情況也是現(xiàn)實網(wǎng)絡(luò)通信環(huán)境中經(jīng)常存在的。為了避免由于丟包而導(dǎo)致系統(tǒng)運(yùn)行異常,所以引入了超時重發(fā)機(jī)制。
增加超時重發(fā)機(jī)制后,發(fā)送出去的UDP數(shù)據(jù)包,在從命令隊列中刪除后會根據(jù)其類型決定是否將其追加到ACK List(確認(rèn)隊列)中[13]。廣播性質(zhì)的數(shù)據(jù)包以及其他不需要確認(rèn)的數(shù)據(jù)包(比如客戶端下線時發(fā)給服務(wù)端的下線通知,服務(wù)端不需要應(yīng)答)從命令隊列中刪除后,不需要做任何處理;其它類型的數(shù)據(jù)包需要追加到確認(rèn)隊列中去[14]。
需要添加到確認(rèn)隊列的數(shù)據(jù)包要先進(jìn)行一次簡單的封裝,在已有信息(Message、Command、IP、Port)的基礎(chǔ)上額外增加SYN和SendTimes字段,封裝成ACK Info結(jié)構(gòu)。另外,確認(rèn)隊列是一個順序隊列,它采用尾插法插入ACK Info結(jié)構(gòu)[15]。
當(dāng)確認(rèn)隊列中有待確認(rèn)數(shù)據(jù)包時,在ACK Timer的驅(qū)動下,每隔1000毫秒就掃描確認(rèn)隊列一次。掃描的過程如下:
1)檢查ACK Info的 SendTimes是否超出閾值(系統(tǒng)中默認(rèn)為3);
2)如果SendTimes沒有超出閾值的話,將其加1;同時將ACK Info結(jié)構(gòu)中封裝的數(shù)據(jù)包(Message和Command)重新發(fā)送到 ACK Info中的 IPAddress和Port;
3)如果SendTimes超出閾值的話,將ACK Info從確認(rèn)隊列中移除,拋出SendFailed消息,通知上層應(yīng)用軟件處理;
如果在某個ACK Info的SendTimes達(dá)到閾值之前,網(wǎng)絡(luò)通信組件收到終端發(fā)來的帶有相同SYN的數(shù)據(jù)包時,就說明這個數(shù)據(jù)包發(fā)送成功了。判斷是否發(fā)送成功的詳細(xì)過程如下:
1)接收到終端發(fā)來的UDP數(shù)據(jù)包后加以解析;
2)解析出來的數(shù)據(jù)包不是ACK數(shù)據(jù)包時,交給上層軟件處理;
3)解析處理的數(shù)據(jù)包是ACK數(shù)據(jù)包時,遍歷確認(rèn)隊列,查找?guī)в邢嗤琒YN的ACK Info。找到之后將其從確認(rèn)隊列移除,接著拋出SendSucceed消息,通知應(yīng)用軟件處理。上層軟件可以不對SendSucceed消息做任何處理,但網(wǎng)絡(luò)通信組件作為底層組件還是會在數(shù)據(jù)包發(fā)送成功時就拋出此消息。
采用超時重發(fā)機(jī)制后,上層軟件發(fā)送的關(guān)鍵UDP數(shù)據(jù)包都會由網(wǎng)絡(luò)通信組件發(fā)送出去,發(fā)送的狀態(tài)(成功、失敗)也會及時反饋給上層軟件。
在C/S結(jié)構(gòu)的系統(tǒng)中,客戶端軟件正常退出時,會發(fā)送一個UDP數(shù)據(jù)包來通知服務(wù)端軟件進(jìn)行一些必要的處理。但由于網(wǎng)絡(luò)故障、軟件崩潰或機(jī)器重啟等意外原因,導(dǎo)致正在運(yùn)行的客戶端軟件“非正常斷開”——在退出之前來不及通知服務(wù)器[16]。而服務(wù)端軟件無法判斷客戶端是長時間空閑還是已經(jīng)掉線。如果服務(wù)端軟件不能及時地探測到這類問題,也會影響系統(tǒng)的正常運(yùn)行。比如,系統(tǒng)中具有訂單審核權(quán)限的客戶端意外掉線了,服務(wù)端并不知道該客戶端已掉線,所以有新的訂單需要審核時,服務(wù)端軟件仍然會把訂單數(shù)據(jù)包發(fā)給該客戶端,這樣的數(shù)據(jù)包永遠(yuǎn)無法發(fā)送成功。
這類問題是無法僅僅依靠網(wǎng)絡(luò)通信組件來解決。網(wǎng)絡(luò)通信組件只是負(fù)責(zé)監(jiān)聽并將接收的數(shù)據(jù)包解析上傳,同時將上層軟件下達(dá)的發(fā)送請求封裝并發(fā)送。雖然網(wǎng)絡(luò)通信組件采用了一系列的機(jī)制來保證收發(fā)的可靠性,但它所處的層次決定它無法解決上面提到的這些問題。
為此,本文在應(yīng)用層采用一種稱為心跳包的技術(shù)來解決客戶端掉線的探測問題[17],心跳包的工作過程:
1)服務(wù)端啟動后,將每個用戶的基本信息封裝成UserInfo,逐一添加到用戶隊列UserList中。
2)服務(wù)器端收到客戶端的登錄請求后,在UserL-ist中找到該用戶的UserInfo,將此UserInfo的在線狀態(tài)(IsOnLine)設(shè)置為 true,連續(xù)丟失心跳包數(shù)(LostLinkNum)設(shè)置為0,同時將IPAddress和 Port設(shè)置為該客戶端實際使用的IP地址和網(wǎng)絡(luò)端口,還要將該UserInfo在用戶隊列中的位置調(diào)整到所有離線用戶之前;如果服務(wù)端的Heartbeat Timer處于停止?fàn)顟B(tài)(說明當(dāng)前登錄的客戶端是系統(tǒng)中第一個在線客戶端),將其啟動;與此同時,客戶端登錄成功后,也啟動自身的Heartbeat Timer。
3)服務(wù)端Heartbeat Timer啟動之后,每隔一定時間間隔,就從頭掃描UserList,從中取出一個個UserInfo,如果UserInfo是離線狀態(tài)的,不對此UserInfo做任何處理,說明所有的在線用戶的UserInfo都已經(jīng)處理完了,停止本次掃描;否則檢查其LostLinkNum,如果小于3時,將其加1,否則說明已經(jīng)至少連續(xù)3次沒有收到此客戶端的心跳包了,就認(rèn)定該客戶端已經(jīng)斷開,要對其做一些后續(xù)處理——將其在線狀態(tài)設(shè)置為離線,重置其 LostLinkNum、IPAddress和 Port,將這個消息通知給別的客戶端等;如果掃描UserList時,遇到第一個UserInfo就是離線狀態(tài)的,說明系統(tǒng)中的所有用戶都已下線,停止Heartbeat Timer。
4)客戶端運(yùn)行期間,會由客戶端的 Heartbeat Timer每隔一定間隔發(fā)送一個心跳包命令CLIENT_TICKREQ(網(wǎng)絡(luò)通信組件定義的眾多網(wǎng)絡(luò)通信命令之一)。
5)服務(wù)端收到客戶端的心跳包之后,在UserList中找到該用戶的UserInfo,正常情況下,其在線狀態(tài)應(yīng)該為true,此時只需要重置其LostLinkNum。如果其在線狀態(tài)為false,說明該用戶曾經(jīng)有連續(xù)丟失多個心跳包被服務(wù)器誤判為斷開,而現(xiàn)在可以斷定該用戶仍然在線,這種情況按用戶登錄處理,處理方法見2)。
需要說明的是,服務(wù)端和客戶端各有自己的Heartbeat Timer,但它們的功能是完全不同的:客戶端在Heartbeat Timer的驅(qū)動下,每隔固定時間間隔發(fā)送一個心跳包,而服務(wù)器在Heartbeat Timer的驅(qū)動下,每隔固定時間間隔處理檢查一遍這些用戶。
服務(wù)端和客戶端Heartbeat Timer的時間間隔必須一致,否則會由于“心率不齊”而導(dǎo)致“心跳紊亂”。假如服務(wù)端Heartbeat Timer每1分鐘處理一次,而客戶單的Heartbeat Timer每3分鐘才發(fā)一個心跳包,這時可能會出現(xiàn)客戶端頻繁的“被上下線”的情況。
本文研究給出了基本網(wǎng)絡(luò)通信組件,并針對電子碼監(jiān)管系統(tǒng)通信中特定要求,以及網(wǎng)絡(luò)中經(jīng)常出現(xiàn)的問題,引入時間驅(qū)動機(jī)制和超時重發(fā)機(jī)制,很好地解決了系統(tǒng)中猝發(fā)式數(shù)據(jù)包丟失的問題,有效地完成了自動對第三方軟件的監(jiān)控、操作界面留痕和從中抓取關(guān)鍵的數(shù)據(jù),實現(xiàn)了服務(wù)端和客戶端之間的透明傳輸,以及信息與業(yè)務(wù)過程的集成。
[1]周敏.UDP協(xié)議下可靠信息傳輸?shù)难芯颗c實現(xiàn)[J].電腦開發(fā)與應(yīng)用,2011(12):15-16.
[2]徐世波.新型可靠數(shù)據(jù)報傳輸協(xié)議[J].科技資訊,2011(19):14-15.
[3]富元,呂建新.基于UDP的可靠傳輸協(xié)議的研究與實現(xiàn)[J].光通信研究,2009(5):21-23.
[4]胡志坤,何多昌,桂衛(wèi)華,等.基于改進(jìn)心跳包機(jī)制的整流遠(yuǎn)程監(jiān)控系統(tǒng)[J].計算機(jī)應(yīng)用,2008,28(2):363-366.
[5]謝希仁.計算機(jī)網(wǎng)絡(luò)[M].第5版.北京:電子工業(yè)出版社.
[6]唐明董,雷電.一種用于局域網(wǎng)通信的組件程序的開發(fā)與使用[J].微計算機(jī)應(yīng)用,2003,24(3):137-140.
[7]魏宇紅,姜建國.基于TCP/IP異構(gòu)網(wǎng)絡(luò)下重傳定時機(jī)制的改進(jìn)[J].微計算機(jī)信息,2009(33):118-119,168.
[8]歐軍,吳清秀,裴云,等.基于Socket的網(wǎng)絡(luò)通信技術(shù)研究[J].網(wǎng)絡(luò)安全技術(shù)與應(yīng)用,2011(7):19-21.
[9]于敏,盛向治.輕量級組件間通訊中間件Mbus的設(shè)計和實現(xiàn)[J].計算機(jī)工程與應(yīng)用,2004,40(29):94-96.
[10]梁茂盛.可靠通信協(xié)議中多定時器的實現(xiàn)方案[J].計算機(jī)與現(xiàn)代化,2009(12):121-123.
[11]趙爾敦,鄒玲.寬帶計算機(jī)網(wǎng)絡(luò)的快速時間驅(qū)動仿真算法[J].小型微型計算機(jī)系統(tǒng),2003,24(2):318-320.
[12]申普兵,行明順,王兆祥,等.計算機(jī)網(wǎng)絡(luò)與通信[M].北京:人民郵電出版社,2006:9-11.
[13]王豐錦,邵新宇.基于Socket和多線程的應(yīng)用程序間通信技術(shù)的研究[J].計算機(jī)應(yīng)用,2000,20(6):65-67.
[14]楊濤,謝劍英.一種基于自適應(yīng)MR_ARQ協(xié)議性能的研究[J].計算機(jī)工程與應(yīng)用,2003,39(26):179-181,214.
[15]趙秀英.Delphi網(wǎng)絡(luò)高級編程[M].北京:人民郵電出版社,2001:83-85.
[16]楊繼家,張麗靜,張曉蕾.面向C/S模式下的客戶端軟件自動升級的實現(xiàn)[J].微計算機(jī)應(yīng)用,2005,26(3):290-293.