1. 程式人生 > >接收H248資料包流程

接收H248資料包流程

在看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);