李艷紅
(中南民族大學(xué)計(jì)算機(jī)科學(xué)學(xué)院,武漢430074)
在很多網(wǎng)絡(luò)應(yīng)用程序中,需要使用UDP來(lái)傳送消息和數(shù)據(jù),UDP傳輸是不可靠的,數(shù)據(jù)報(bào)可能會(huì)在傳送途中被丟棄.但是它也有很多優(yōu)點(diǎn),比如無(wú)連接、效率高、系統(tǒng)資源開(kāi)銷(xiāo)小、適用于防火墻穿透等,在互聯(lián)網(wǎng)實(shí)時(shí)通訊以及本機(jī)進(jìn)程間通訊中得以廣泛使用.設(shè)計(jì)UDP網(wǎng)絡(luò)通訊程序,需要解決丟包、重發(fā)、異步、并發(fā)等問(wèn)題.TCP和UDP在網(wǎng)絡(luò)通訊中都必不可少,他們有不同的特點(diǎn),有各自的適用范圍,均不可互相替代.UDP能夠在不建立連接的情況下收發(fā)數(shù)據(jù),也就是說(shuō)在防火墻允許的情況下,可以發(fā)送數(shù)據(jù)到任何地址,也可以接收任何地址發(fā)來(lái)的數(shù)據(jù).很多場(chǎng)合非常需要這種數(shù)據(jù)收發(fā)的方式.但是正是因?yàn)檫@個(gè)特點(diǎn),丟包的現(xiàn)象也是不可避免的.研究者們關(guān)注UDP傳輸?shù)目煽啃詥?wèn)題,并提出了改進(jìn)的算法[1,2],但是可靠性問(wèn)題從原理上講不可能完全解決.
在音視頻數(shù)據(jù)傳送時(shí),一定程度丟包率是允許的,不會(huì)對(duì)會(huì)話質(zhì)量造成很大的影響.但是傳送控制信息時(shí),需要用重發(fā)的方法來(lái)解決丟包問(wèn)題.重發(fā)涉及到重發(fā)的最大次數(shù)、每次重發(fā)的時(shí)間間隔,以及狀態(tài)機(jī)改變狀態(tài)后才收到的(遲來(lái)的)應(yīng)答包的處理方式等,這是異步操作必然要碰到的問(wèn)題.一般的網(wǎng)絡(luò)程序,往往需要同時(shí)處理一些并發(fā)的網(wǎng)絡(luò)通訊任務(wù),比如A終端與B終端進(jìn)行視頻會(huì)話,同時(shí)A終端還可能發(fā)送文件給C終端.
定時(shí)器在過(guò)程控制中廣泛使用,針對(duì)一些特定的應(yīng)用環(huán)境,研究者們提出了相應(yīng)的方案和研究成果[3-8],本文重點(diǎn)研究智能定時(shí)器的軟件實(shí)現(xiàn).大多數(shù)情況下,程序中使用固定間隔值的定時(shí)器即可滿足定時(shí)的目的.VC、QT、JAVA等程序設(shè)計(jì)語(yǔ)言,無(wú)一例外實(shí)現(xiàn)了定時(shí)器類(lèi),提供了啟動(dòng)、停止、設(shè)置定時(shí)間隔值等方法,一般有一次性定時(shí)和重復(fù)定時(shí)兩種定時(shí)模式.但是,這些定時(shí)器不能滿足復(fù)雜的定時(shí)需求.例如運(yùn)用于退避算法,定時(shí)間隔需要變化、然后定時(shí)間隔再趨于穩(wěn)定的定時(shí)需求.而且,在不同場(chǎng)合間隔的變化方式也是不一樣的.為此,本文設(shè)計(jì)了一種稱(chēng)之為智能定時(shí)器的C++類(lèi),來(lái)解決這個(gè)問(wèn)題.
該定時(shí)器類(lèi)的組成并不復(fù)雜,具體包括:一個(gè)數(shù)組、一個(gè)用于存放該數(shù)組元素個(gè)數(shù)的整數(shù)、一個(gè)時(shí)間戳、一個(gè)計(jì)數(shù)器、一個(gè)最大重試次數(shù)、判斷是否超時(shí)的函數(shù),以及判斷是否重試的函數(shù).
數(shù)組用來(lái)存放定時(shí)的間隔(超時(shí)值),也即每一次重試等待的時(shí)間.數(shù)組長(zhǎng)度可長(zhǎng)可短,由重試的頻度變化策略決定,比如發(fā)送一個(gè)UDP包后,如果沒(méi)收到應(yīng)答,則需要按 2s、4s、8s、16s、16s 這樣的間隔重發(fā),那么該數(shù)組就是[0,2,4,8,16,16].由于數(shù)組是由構(gòu)造函數(shù)的參數(shù)傳入的,構(gòu)造函數(shù)得到的是該數(shù)組的地址,在函數(shù)中無(wú)法求得其元素個(gè)數(shù),所以需要將元素個(gè)數(shù)也作為參數(shù)傳入構(gòu)造函數(shù).在構(gòu)造函數(shù)中,將數(shù)組地址及其元素個(gè)數(shù)保存到成員變量中.
時(shí)間戳用來(lái)存放上次重試(重發(fā))的系統(tǒng)時(shí)間.計(jì)數(shù)器用來(lái)統(tǒng)計(jì)重試的次數(shù).最大重試次數(shù)用來(lái)判斷是否超時(shí),這里的超時(shí)表示重試結(jié)束.如果最大重試次數(shù)為0,則表示需要無(wú)限次重試.
如果計(jì)數(shù)器大于最大重試次數(shù),并且最大重試次數(shù)大于0,則表示已經(jīng)超時(shí).判斷是否需要重試的方法是,取出當(dāng)前計(jì)數(shù)器對(duì)應(yīng)數(shù)組元素的值,然后用當(dāng)前系統(tǒng)時(shí)間減去時(shí)間戳的值,如果差值大于等于數(shù)組元素值,則表示需要重試.此時(shí),將當(dāng)前系統(tǒng)時(shí)間賦值給時(shí)間戳,作為下次判斷的依據(jù).
定時(shí)器類(lèi)的設(shè)計(jì)具體如下:
上文所述的定時(shí)器是一個(gè)被動(dòng)對(duì)象,也即沒(méi)有自己的線程,需要在定時(shí)器線程以及狀態(tài)機(jī)的配合下才能發(fā)揮作用.沒(méi)有設(shè)計(jì)自己的線程,是為了節(jié)省系統(tǒng)的開(kāi)銷(xiāo).因?yàn)橐粋€(gè)稍微復(fù)雜的系統(tǒng),可能需要多個(gè)定時(shí)器,如果每個(gè)定時(shí)器都有一個(gè)線程在運(yùn)行,總體上會(huì)增加CPU空轉(zhuǎn)的時(shí)間.
狀態(tài)機(jī)一般是指有限狀態(tài)機(jī),由一系列的狀態(tài)、事件、條件、動(dòng)作組成.在某一時(shí)刻,狀態(tài)機(jī)只能處于某個(gè)狀態(tài),稱(chēng)為當(dāng)前狀態(tài).當(dāng)發(fā)生某個(gè)事件時(shí),如果條件滿足,則狀態(tài)機(jī)的狀態(tài)將發(fā)生改變,這個(gè)改變稱(chēng)為狀態(tài)轉(zhuǎn)移.狀態(tài)轉(zhuǎn)移往往伴隨著一些動(dòng)作(任務(wù))的執(zhí)行.
線程是利用CPU時(shí)間片輪轉(zhuǎn)來(lái)獲得執(zhí)行權(quán)的代碼片段.在多數(shù)線程的使用中,這個(gè)代碼片段會(huì)設(shè)計(jì)成循環(huán)體,只有在特殊條件下才退出循環(huán)來(lái)結(jié)束線程.每一次循環(huán)內(nèi),調(diào)用狀態(tài)機(jī)處理函數(shù),處理一次狀態(tài)機(jī).如果系統(tǒng)需要多個(gè)狀態(tài)機(jī),則每個(gè)狀態(tài)機(jī)都處理一次,調(diào)用完所有的處理函數(shù)后是短暫的休眠.休眠的時(shí)間與定時(shí)器的精度相關(guān),比如精度為20ms則休眠20ms,也即預(yù)定1s的定時(shí),那么實(shí)際的定時(shí)間隔為1000~1020ms.除非是實(shí)時(shí)系統(tǒng)需要精確到微秒,一般的應(yīng)用,精度為20~100ms就可以滿足要求.
狀態(tài)機(jī)處理函數(shù)因?yàn)橐幚砗芏酄顟B(tài),適合用switch…case構(gòu)建,該函數(shù)被調(diào)用時(shí),執(zhí)行的代碼是狀態(tài)機(jī)當(dāng)前狀態(tài)的case,在此狀態(tài)中,如果有重傳,或者超時(shí)狀態(tài)轉(zhuǎn)移的需求,則可分別調(diào)用isNextStep()和isTimeOut()來(lái)判斷.當(dāng)isTimeOut返回true時(shí),表示狀態(tài)機(jī)需要轉(zhuǎn)移到新的狀態(tài),而isNextStep返回true時(shí),表示在當(dāng)前狀態(tài)下需要重復(fù)執(zhí)行某些任務(wù).在isNextStep被調(diào)用時(shí),計(jì)數(shù)器count和上次訪問(wèn)last time才會(huì)被更新.這表明,增加智能定時(shí)器只是增加少量?jī)?nèi)存的使用,不會(huì)增加系統(tǒng)的CPU資源消耗.
狀態(tài)機(jī)的狀態(tài)轉(zhuǎn)移,除了超時(shí)的原因外,更多的是人機(jī)交互事件和外部系統(tǒng)事件觸發(fā)的,比如用戶點(diǎn)擊“上線”按鈕,或者是收到服務(wù)器的應(yīng)答信息.
本文以音視頻會(huì)話終端軟件的登錄過(guò)程作為實(shí)例,來(lái)說(shuō)明智能定時(shí)器的功能和運(yùn)用方法.程序分終端程序和服務(wù)器端程序,均在Linux平臺(tái)開(kāi)發(fā)和運(yùn)行,采用C++語(yǔ)言編程.
終端登錄的過(guò)程用狀態(tài)機(jī)表示,如圖1所示.
圖1 終端登錄狀態(tài)機(jī)Fig.1 Terminal login state machine
該狀態(tài)機(jī)有“離線”、“請(qǐng)求登錄服務(wù)器地址”、“登錄請(qǐng)求”、“檢測(cè)防火墻”、“檢測(cè) NAT”、“心跳”6個(gè)狀態(tài).除了離線狀態(tài),其余狀態(tài)都使用了定時(shí)器.這些定時(shí)器中,只有在心跳狀態(tài)下終端才會(huì)以固定的時(shí)間間隔發(fā)送心跳包,其余狀態(tài)的終端都要按照一定的退避算法來(lái)重發(fā)數(shù)據(jù).按退避算法的方式來(lái)重發(fā)的原因是,如果服務(wù)器忙于處理其他任務(wù),不能及時(shí)給終端返回應(yīng)答,終端應(yīng)當(dāng)適當(dāng)放慢重試的頻率;又由于終端向服務(wù)器發(fā)送的請(qǐng)求數(shù)據(jù)可能在網(wǎng)絡(luò)上丟失,那么終端也不能無(wú)限制等待,希望服務(wù)器最終會(huì)返回應(yīng)答.
根據(jù)登錄過(guò)程的狀態(tài)機(jī),制定定時(shí)器策略.終端登錄過(guò)程共需要3種定時(shí)器:1)登錄請(qǐng)求定時(shí)器,分時(shí)復(fù)用于3個(gè)狀態(tài):獲取登錄服務(wù)器地址、登錄請(qǐng)求、檢測(cè)NAT;2)防火墻檢測(cè)定時(shí)器,用于檢測(cè)防火墻;3)心跳維持定時(shí)器,用于心跳包定時(shí)發(fā)送.如表1所示.
表1 定時(shí)策略表Tab.1 Timing strategy table
根據(jù)定時(shí)策略表,定義3個(gè)數(shù)組:
unsigned int tmLogin[5]={0,2000,4000,4000,8000};
unsigned int tmFwDetect[6]={0,500,1000,2000,4000,8000};
unsigned int tmHeartBeat[2]={0,16000};
然后,聲明智能定時(shí)器實(shí)例,構(gòu)造函數(shù)中傳入數(shù)組及其元素個(gè)數(shù):
startTimer stLogin(tmLogin,sizeof(tmLogin)/sizeof(tmLogin[0]));
startTimer stFwDetect (tmFwDetect,sizeof(tmFwDetect)/sizeof(tmFwDetect[0]));
startTimer stHeartBeat(tmHeartBeat,sizeof(tmHeartBeat)/sizeof(tmHeartBeat[0]));
登錄過(guò)程涉及到3個(gè)算法,分別是:定時(shí)器線程、登錄狀態(tài)機(jī)處理函數(shù)、服務(wù)器應(yīng)答處理.
(1)定時(shí)器線程算法.
Function TimerThread
pre-condition:sm is initialized to offline
1 While program not quitting do{
2 smLoginProcess();
3 sleep 100 ms;
4}
(2)登錄狀態(tài)機(jī)處理函數(shù)算法.
Function smLoginProcess
1switch(sm){
2 case offline:
3 break;
4 case login_server_req:
5 if(stLogin.isNextStep()){
6 send CMD_LB_LOGINSERVERREQ packet to load-balance server;
7 }
8 break;
9 case login_req:
10 if(stLogin.isTimeOut()){//如果超時(shí)則返回到login_server_req狀態(tài)
11 sm=login_server_req;
12 stLogin.reset(0);
13 break;
14 }
15 if(stLogin.isNextStep()){//否則如果時(shí)間間隔已滿則重發(fā)登錄請(qǐng)求到登錄服務(wù)器
16 send CMD_NAT_LOGINREQ packet to login server;
17 }
18 break;
19 case firewall_detecting:
20 如果stLogin超時(shí)(5次重試無(wú)應(yīng)答)則說(shuō)明有防火墻,記錄防火墻特性,復(fù)位stFwDetect,設(shè)置狀態(tài)為 nat_detecting.否則如果時(shí)間間隔已滿則重發(fā)防火墻檢測(cè)請(qǐng)求.
21 break;
22 case nat_detecting:
23 如果stFwDetect超時(shí)則返回login_server_req狀態(tài).否則如果時(shí)間間隔已滿則重發(fā)NAT檢測(cè)請(qǐng)求.
24 break;
25 case heat_beating:
26 如果超時(shí)則返回login_server_req狀態(tài),否則如果時(shí)間間隔已滿則重發(fā)心跳包.
27 break;
28 }
(3)服務(wù)器應(yīng)答處理算法片段.
Function onServerAck(char*pkt)
1 switch(cmd in pkt){//所有服務(wù)器返回的應(yīng)答包中均含有cmd(命令字)
2case CMD_LB_LOGINSERVERACK:
3 if(login_server_req){//該應(yīng)答包只有在login_server_req狀態(tài)下才有效
4 sm=login_req;//更新?tīng)顟B(tài)機(jī)的當(dāng)前狀態(tài)為login_req
5 stLogin.reset(10);//重置定時(shí)器 stLogin,表示在login_req狀態(tài)最多重發(fā)10次.
6 get ip,port of login server from pkt;//取得應(yīng)答包中所含登錄服務(wù)器的地址
7 }
8 break;
9case CMD_NAT_LOGINACK:
10 …(略)
由于篇幅所限,不能完整描述這3個(gè)算法對(duì)所有狀態(tài)和所有服務(wù)器應(yīng)答包的處理情況,以下重點(diǎn)介紹終端由offline(離線)狀態(tài)和login_server_req(獲取登錄服務(wù)器地址)狀態(tài)相關(guān)的處理.
首先需要注意的是定時(shí)器線程算法TimerThread,該線程每隔100ms會(huì)執(zhí)行一次狀態(tài)機(jī)處理函數(shù)smLoginProcess.
終端程序初始化后,狀態(tài)機(jī)處于offline(離線).在offline狀態(tài),smLoginProcess處理函數(shù)不做任何操作,立即返回(第3行代碼).只有人機(jī)交互才能改變offline的狀態(tài),比如通過(guò)按鈕發(fā)出“上線”請(qǐng)求,相應(yīng)的代碼會(huì)將狀態(tài)機(jī)的狀態(tài)由offline轉(zhuǎn)移到login_server_req,并調(diào)用 stLogin.reset(0)復(fù)位定時(shí)器,也即在該狀態(tài)下將無(wú)限次地重試,直到從負(fù)載均衡服務(wù)器取到登錄服務(wù)器地址為止.
狀態(tài)機(jī)處理函數(shù)smLoginProcess對(duì)login_server_req狀態(tài)的處理見(jiàn) 4~8行.在此調(diào)用 stLogin.isNextStep來(lái)判斷是否該發(fā)送CMD_LB_LOGINSERVERREQ到負(fù)載均衡服務(wù)器.在smartTimer的isNextStep代碼中可以看到,如果當(dāng)前時(shí)間減去上次重試的時(shí)間大于當(dāng)前重試間隔值,會(huì)返回 true.在 login_server_req 狀態(tài)下,stLogin.isNextStep返回true時(shí),程序?qū)?zhí)行第6行所示的動(dòng)作,發(fā)送CMD_LB_LOGINSERVERREQ到負(fù)載均衡服務(wù)器.
如果一直沒(méi)有收到負(fù)載均衡服務(wù)器的應(yīng)答,則狀態(tài)機(jī)處理函數(shù)將永遠(yuǎn)執(zhí)行4~8行的代碼.負(fù)載均衡服務(wù)器的應(yīng)答在算法onServerAck的2~8行中處理,負(fù)載均衡服務(wù)器返回的應(yīng)答包中包含了登錄服務(wù)器的地址,這個(gè)應(yīng)答包只有在終端的狀態(tài)為login_server_req時(shí)才被處理,在其他狀態(tài)則被忽略.處理方法是:1)狀態(tài)機(jī)變?yōu)閘ogin_req;2)將定時(shí)器stLogin復(fù)位為10 次超時(shí),這10 次的間隔值分別為0、2、4、4、8、8、8、8、8、8s;3)從應(yīng)答包中取出登錄服務(wù)器的地址.
狀態(tài)機(jī)處理函數(shù)smLoginProcess對(duì)login_req狀態(tài)的處理見(jiàn)9~18行.先調(diào)用stLogin.isTimeOut,如果超時(shí)則狀態(tài)回轉(zhuǎn)到login_server_req重新取登錄服務(wù)器地址.如果沒(méi)有超時(shí),則調(diào)用stLogin.isNextStep判斷是否需要重試發(fā)送登錄請(qǐng)求.
以上3個(gè)算法組成的代碼框架,除了實(shí)現(xiàn)數(shù)據(jù)重發(fā),也體現(xiàn)了并發(fā)、同步操作的理念.終端發(fā)出請(qǐng)求包,服務(wù)器返回應(yīng)答包是異步操作,不能設(shè)計(jì)成函數(shù)調(diào)用得到返回值的方式.smLoginProcess中發(fā)送請(qǐng)求,而在onServerAck中處理應(yīng)答,使用了異步處理的模式.終端完成登錄后進(jìn)入心跳狀態(tài),在心跳狀態(tài)終端每16s向服務(wù)器發(fā)送一次心跳包.如果用戶發(fā)起音視頻會(huì)話,那么心跳包和會(huì)話信令包以及音視頻數(shù)據(jù)包就需要并發(fā)處理.
本文介紹了一種能夠處理復(fù)雜狀態(tài)機(jī)和程序邏輯的智能定時(shí)器,該智能定時(shí)器以一個(gè)類(lèi)的形式進(jìn)行封裝.智能定時(shí)器實(shí)例化時(shí),從構(gòu)造函數(shù)傳入預(yù)設(shè)的定時(shí)數(shù)組,數(shù)組的元素個(gè)數(shù)可根據(jù)需要進(jìn)行設(shè)定,每個(gè)元素的值代表某個(gè)操作重復(fù)執(zhí)行的等待時(shí)間.該智能定時(shí)器屬于被動(dòng)對(duì)象(沒(méi)有自己的線程),因此需要在線程中去查詢是否重復(fù)執(zhí)行某操作,或者去查詢是否超時(shí).本文所用的定時(shí)器線程和狀態(tài)機(jī)處理函數(shù),是支撐智能定時(shí)器的線程、狀態(tài)機(jī)處理的典型代碼框架.千變?nèi)f化的狀態(tài)機(jī)模型,均可按照此框架來(lái)構(gòu)建程序.最后實(shí)現(xiàn)了將智能定時(shí)器應(yīng)用于實(shí)時(shí)通訊軟件系統(tǒng)的登錄過(guò)程,實(shí)驗(yàn)結(jié)果表明所設(shè)計(jì)的智能定時(shí)器能精確控制UDP包的重發(fā).
[1]李 國(guó),鞏光志,王冬冬.一種提高UDP可靠性的數(shù)據(jù)傳輸方法研究[J].中國(guó)民航大學(xué)學(xué)報(bào),2012(01):41-45.
[2]王艷芳,戴 永,劉東華,等.基于UDP的數(shù)據(jù)可靠傳輸技術(shù)研究與應(yīng)用[J].計(jì)算機(jī)工程與應(yīng)用,2010(03):105-108.
[3]孫宏旭,邢 薇,陶 林.基于有限狀態(tài)機(jī)的模型轉(zhuǎn)換方法的研究[J].計(jì)算機(jī)技術(shù)與發(fā)展,2012(02):10-13.
[4]李慶華,陳志剛,鄧曉衡.基于線性均方誤差的無(wú)線自組網(wǎng)TCP定時(shí)器改進(jìn)[J].中南大學(xué)學(xué)報(bào):自然科學(xué)版,2012(05):1780-1786.
[5]朱正發(fā),陳琳.一種嵌入式基帶系統(tǒng)定時(shí)器裝置的研究[J].單片機(jī)與嵌入式系統(tǒng)應(yīng)用,2012(11):12-14.
[6]朱旭光.單片機(jī)定時(shí)器應(yīng)用探討[J].自動(dòng)化技術(shù)與應(yīng)用,2012(01):99-103.
[7]王秀霞.基于555定時(shí)器的開(kāi)關(guān)電源的設(shè)計(jì)[J].微計(jì)算機(jī)信息,2012(01):76-78.
[8]任君玉,黎國(guó)文.網(wǎng)絡(luò)中的定時(shí)器技術(shù)[J].電腦知識(shí)與技術(shù),2011(21):5094-5095.