接收H248資料包流程
阿新 • • 發佈:2018-12-29
在看mgStartup流程時,H248的資料包接收機制就已經全部構建好了,只是分部在mgStartup流程的幾個位置,程式碼零散,不看完整個mgStartup流程是沒辦法理解收包機制的。這裡主要以關鍵點來分析對資料包接收處理的流程。 mgStartup rvMegacoStackConstruct rvMegacoTransportConstruct 這裡建立了一個傳輸層物件,並設定了一個回撥函式rvMegacoStackProcessPackets,這裡標記為關鍵點A 然後建立了一個執行緒物件,執行緒函式為rvMegacoTransportProcessPacketQueue,這裡該執行緒只是建立,沒有啟動,這裡標記為關鍵點B 這裡建立了一個SELECT物件,這裡標記為關鍵點C 這裡建立了一個執行緒物件,執行緒函式為rvMegacoSocketEngineThread,這個執行緒已經啟動,但是直接就阻塞了,一直等待觸發它執行的開關變數開啟,什麼時候開啟一會看下面。這裡標記為關鍵點D rvMegacoSocketEngineConstruct rvMegacoEntityConstructLocalEx 這裡首先使用系統SOCKET的API建立了H248需要的socket控制代碼,並使用系統函式bind進行埠繫結,然後封裝成RVSocket物件,這裡定為關鍵點E 然後構造了一個SocketData物件,這個物件主要是用來儲存接收資料包的,同時將剛才構建的RVSocket物件包含進去,將關鍵點A中的傳輸層物件包含進去,對於SocketData物件有段很巧妙的程式碼,因為該物件指標分配完記憶體後這個指標在函式棧中就丟棄了,那怎麼來找到該指標分配的SocketData物件這塊記憶體呢,其實在別的程式碼中想引用SocketData這塊記憶體是通過這個物件中的RVSocket找到的,就是根據RVSocket物件在SocketData結構中的成員偏移找到SocketData的,這裡定為關鍵點F 然後將RVScoket物件封裝為一個RvFd檔案描述符,並將該描述符加入到關鍵點C的SELECT物件RedSet集合中,並給RvFd設定了一個回撥函式rvMegacoTransportRecvFromUdp,這裡定為關鍵點G rvMegacoTransportAddUdpSocketEx rvMegacoSystemRegisterStack rvMegacoStackStart rvMegacoTransportStart //這裡執行關鍵點B的rvMegacoTransportProcessPacketQueue執行緒,該執行緒函式檢測關鍵點A的傳輸層物件待處理包佇列中是否有資料(在哪裡會向這個佇列中加資料在下面的程式碼分析中可以看到),如果有的話,則呼叫關鍵點A的rvMegacoStackProcessPackets回撥進行處理,該函式最終會根據分析出的不同訊息型別觸發rvMegacoStackProcessParserError、rvMegacoStackProcessAuthHeader、rvMegacoStackProcessRequest等回撥 RvThreadStart 這裡就是把關鍵點D的開關變數進行開啟,使得rvMegacoSocketEngineThread執行緒函式繼續向下執行,該函式執行後,使用關鍵點C的SELECT物件呼叫系統的select函式監聽關鍵點E的socket是否有訊息,如果監聽到了,則呼叫關鍵點G中rvMegacoTransportRecvFromUdp函式,該函式根據關鍵點E的RVSocket物件使用結構體內成員偏移方法找到關鍵點F的SocketData物件,然後從SocketData物件中得到關鍵點A的傳輸層物件,呼叫系統收包函式接收UDP包,將收到訊息封裝為packetData物件,加入到傳層物件待處理包佇列中 rvMegacoSocketEngineStart 接收到請求訊息後,如果本地沒有應答,協議棧自動回覆Pending訊息及最終請求事務超時的處理流程如下: 首先從上面接收資料包流程中,如果收到MGC請求訊息則觸發rvMegacoStackProcessRequest //根據事務ID,查詢遠端例項的TCBS佇列中是否有該事務,如果沒有則建立一個新的TCB物件,加入遠端例項的TCBS佇列中,新的TCB物件建立了一個定時器物件並設定了回撥函式rvMegacoTcbProcessTimer,後面需要用到這個函式,這裡暫標記為關鍵點A tcb = rvMegacoEntityCheckOutTcb(remoteEntity, rvMegacoTransactionGetId(request), RV_TRUE); //進入到請求處理流程函式 rvMegacoTcbOnRecvRequest(tcb, request); //判斷如果TCB的狀態為IDLE(新時為該狀態)則執行如下 //啟動TCB物件的定時器,用於自動回覆Pending訊息的超時處理,該定時器就是上面關鍵點A的定時器,超時後會調上那個回撥 rvMegacoTcbResetTimer(tcbPtr, RV_MEGACOTCB_AUTOPENDING_TIMEOUT); //將TCB狀態遷為等待應答 tcbPtr->state = RV_MEGACOTCBSTATE_WAITING_FOR_REPLY; //執行請求的處理響應,我們想了解本地沒有應答後會發生什麼,所以這裡我們就當程式碼沒有執行 localEntity->u.local.processRequest(tcbPtr, request, localEntity->u.local.processRequestData); //收到新的事務請求時,該標記都記為TRUE,為了是將請求加入到需要應答的處理中 addResponse = RV_TRUE; //如果上面標記為TRUE,則將該TCB新增到待監視的應答處理中 if(addResponse) rvMegacoEntityAddResponse(remoteEntity, rvMegacoTcbGetId(tcbPtr)); //首先得到請求事務超時的預設時間 i = entity->stack->tHist / RV_MEGACOENTITY_RESPONSEPROCESSINGINTERVAL + 1; //判斷監視應答佇列中是否為空,如果為空則標記後面要啟動監視定時器,不為空的話表明該定時器已經觸發過了,該定時器觸發在內部函式中會判斷如果這個佇列不空則一直自啟用,只有佇列空才停止啟用,等待下一個請求過來觸發。 startTimer = rvDequeSize(&entity->u.remote.responses) == 0; //將引數傳入的事務ID放入特監視應答佇列中 rvDequePushBack(RvMegacoTransactionId)(&entity->u.remote.responses, &transId); //下面這段程式碼很經典,其中numToDelete佇列就像一個時間軸,該佇列與上面//responses佇列緊密聯絡在一起。見下圖舉例: //--------------------------------------------------------------- // numToDelete佇列 0 0 0 0 0 0 1 0 0 1 // responses佇列 A B //--------------------------------------------------------------- //numToDelete佇列有10個成員,成員值為整形數字,responses佇列有兩個成員,//成員值為事務ID,在numToDelete佇列中0表示該時間點沒有超時事務,1表示該//時間點responses佇列前1個事務已經超時,同理如果是2表明該時間點//responses佇列前2個事務已經超時,上圖的意思就是事務ID為A的事務會在7 //秒後超時,事務ID為B的事務會在10秒後超時 //如果當前時間軸已經有很長的等待佇列,可是使用者中途改變了請求事務超時時 //間,並且比當前等待佇列的最長時間短,則忽略使用者設定,把當前監視事務加到//當前最長的時間點上 if(i + 1 < rvDequeSize(&entity->u.remote.numToDelete)) i = rvDequeSize(&entity->u.remote.numToDelete) - 1; else //如果請求事務超時時間比當前時間軸長,則用0來填充時間軸至需要的超時時間 while(i >= rvDequeSize(&entity->u.remote.numToDelete)) rvDequePushBack(RvUint)(&entity->u.remote.numToDelete, &zero); //步增當前時間點所觸發的事務個數 ++(*rvDequeAt(&entity->u.remote.numToDelete, i)); //如果前面加了啟動定時器標記則啟動監視請求事務的定時器,該定時器的回撥函//數為rvMegacoEntityProcessResponseTimer,這裡記為關鍵點B if(startTimer) rvMegacoTimerReset(&entity->u.remote.responseTimer, RV_MEGACOENTITY_RESPONSEPROCESSINGINTERVAL); 此時已經有兩個定時器待觸發,協議棧自動回覆Pending訊息及最終請求事務超時就是分別用這兩個定時器實現的,先看一下自動回覆Pending訊息是怎樣實現的。關鍵點A的定時器超時後執行回撥rvMegacoTcbProcessTimer 首先判斷當前TCB狀態,如果為RV_MEGACOTCBSTATE_WAITING_FOR_REPLY則執行如下: rvMegacoTcbSendPending(tcbPtr, NULL, NULL); //停止該定時器 rvMegacoTcbStopTimer(tcbPtr); //將encodedTransaction清空 rvStrStreamSeekPos(&tcbPtr->encodedTransaction, 0); //構建要傳送的資料包內容,存入encodedTransaction rvMegacoTransactionPendingEncode(tcbPtr->tId, &tcbPtr->encodedTransaction, rvMegacoTcbGetStack(tcbPtr)->encodeCompact, &remoteEntity->stack->logSource); //將TCB遷態為Pending訊息已經放入佇列 tcbPtr->state = RV_MEGACOTCBSTATE_PENDING_QUEUED; //這裡將TCB加入的傳送佇列併發送,這段程式碼不分析了,在上次寫的傳送資料包流程中有這塊程式碼的分析 rvMegacoEntitySendListInsert(remoteEntity, tcbPtr); 最終請求事務超時的實現,關鍵點B的定時器觸發後,呼叫rvMegacoEntityProcessResponseTimer回撥函式 //判斷時間軸是否有時間點 if(rvDequeSize(&entity->u.remote.numToDelete)) //如果有時間點,獲取頂部時間點的數值 n = rvDequeFront(&entity->u.remote.numToDelete); //當時間點的陣列大於零時,表明該時間點有超時事務發生,用迴圈是為了處理該時間點如果同時有多個事務超時,則依次處理 while((*n) > 0) //獲取第一個超時事務的ID id = *rvDequeFront(&entity->u.remote.responses); //從事務列表中找到指定ID的事務 tcbPtr = rvMegacoEntityCheckOutTcb(entity, id, RV_FALSE); //這裡將超時的TCB的狀態變遷為RV_MEGACOTCBSTATE_REPLY_COMPLETE,然後啟動關鍵點A的定時器,超時時間為10ms,超時後觸發關鍵點A的定時器回撥函式rvMegacoTcbProcessTimer,該回調函式主要就是將TCB從關鍵點A中的TCBS佇列中刪除 rvMegacoTcbOnHistoryTimerExpire(tcbPtr); //將事務ID從監視事務列表中刪除 rvDequePopFront(RvMegacoTransactionId)(&entity->u.remote.responses); //用於遍歷當前時間點的下一個超時事務的觸發條件 (*n)--; //將當前時間點從時間軸上刪除 rvDequePopFront(RvUint)(&entity->u.remote.numToDelete); //如果時間軸上還有時間點,則自觸發定時器 if(rvDequeSize(&entity->u.remote.responses)) rvMegacoTimerReset(timer, RV_MEGACOENTITY_RESPONSEPROCESSINGINTERVAL);