在Vovida的基礎上實現自己的SIP協議棧(二)

 在Vovida的基礎上實現自己的SIP協議棧(二)

盧政 2003/08/04

 

盧政 2003/08/04

2. 5 HeartLessProxy Run方法的實現

HeartLessProxy::run()
{
myWorkerThread->run();
mySipThread->run();
}
通過上面可以看到有兩個Run方法的調用,第一個是WorkThread的Run方法,它的主要作用是處理UaBuilder的Process方法,主要用來處理Sptr < Fifo < Sptr < SipProxyEvent > > > myFifo中的各種事件,前面已經詳細的介紹了SipProxyEvent類的作用,這個類已經在前面介紹了,其實簡單的說,它就是一個本地的各種事件的集合。
現在我們來看一下兩個Run方法的實現:
2.5.1 WorkerThread的Run方法:
UaBuilder::process( const Sptr < SipProxyEvent > nextEvent )
{
//處理以下的四種事件
/// SipEvent
Sptr < SipEvent > sipEvent;
sipEvent.dynamicCast( nextEvent );
if ( sipEvent != 0 )
{
//處理本地的SIP的事件,包括對狀態機的設置和命令/狀態隊列返回的操作在下面將//對它做詳細的介紹
if( processSipEvent( sipEvent ) )
{
return;
}
//向消息隊列myCallContainer中插入相應的事件信息。
sendEvent( nextEvent );
return;
}

/// UaDeviceEvent
Sptr < UaDeviceEvent > uaDeviceEvent;
uaDeviceEvent.dynamicCast( nextEvent );
if ( uaDeviceEvent != 0 )
{
//處理本地的設備事件,最主要的就是處理摘機信號;
if( processUaDeviceEvent( uaDeviceEvent ) )
{
return;
}
sendEvent( nextEvent );
return;
}

/// UaDigitEvent
Sptr < UaDigitTimerEvent > uaDigitEvent;
uaDigitEvent.dynamicCast( nextEvent );
if ( uaDigitEvent != 0 )
{

//處理在規定的時間間隔(Kickstart)主動呼叫事件的觸發。
if( processUaDigitEvent( uaDigitEvent ) )
{
return;
}
sendEvent( nextEvent );
return;
}

/// UaTimerEvent
Sptr < UaTimerEvent > uaTimerEvent;
uaTimerEvent.dynamicCast( nextEvent );
if ( uaTimerEvent != 0 )
{
//在各種SIP命令的迴應產生了超時事件後,系統的事件觸發。例如:
//在StateTrying()中addEntryOperator( new OpStartTimer )在myEntryOperators隊列中加入
//該Operator(指一個操作,例如呼叫或者是進入等待),這裏我們這個Operator在時間到達
//以後戶會被OpTimeout::process的方法檢測到(isTimeout(event)進行檢測,對StateTrying
//整個狀態進行檢測,也就是Trying事件),最後如果UaTimerEvent事件被觸發,那麼,//就會調用:stateMachine->findState( "StateError" )這個狀態,進入錯誤狀態,實施錯誤的
//處理機制,同時向myEntryOperators隊列中加入一個新的Operator--OpStartErrorTone,
//從而被processUaTimerEvent過程撲捉到,最後通過SendEvent發送到執行隊列裏去。
if( processUaTimerEvent( uaTimerEvent ) )
{
return;
}
sendEvent( nextEvent );
return;
}
assert( 0 );

}

2.5.1.1 processSipEvent
  顧名思義,processSipEvent方法是對隊列中的SIP消息進行處理,我們來看下面的程序:
bool UaBuilder::processSipEvent( const Sptr < SipEvent > sipEvent )
{
Sptr < StatusMsg > statusMsg;
statusMsg.dynamicCast( sipEvent->getSipMsg() );
// 檢驗是否爲返回的狀態碼(主要是對Notify,Subscribe,Register三種狀態進行單獨處理)
//下面做詳細介紹
if ( statusMsg != 0 )
{
if( handleStatusMsg( sipEvent ) )
{
return true;
}
}
//在這裏表示接收到一個SIP的消息,
//檢驗是否爲一個SIP的消息而不是一個狀態(例如是否爲Invite命令)
/// Let's check the call info, now
callId = sipEvent->getSipCallLeg()->getCallId();
callInfo = calls->findCall( callId );
if ( callInfo == 0 )
{
//下面分成兩種狀況進行討論,一種是接受到Invite的消息,一種是接收到一個普通的
//命令,例如
Sptr < InviteMsg > inviteMsg;
inviteMsg.dynamicCast( sipEvent->getSipMsg() );
if ( inviteMsg == 0 )
{
//如果大家在這裏有什麼奇怪的話沒有必要,爲什麼除了inviteMsg以外的所有的消
//息都不處理呢?其實這些消息都在SipThread這個程序中處理了,在Ua這個大狀態
//機中所有的狀態都是以Invite這個消息作爲啓動的。每一個INVITE啓動一個系列的//消息和狀態。
return true;
}
else
{
//收到一個Invite消息,這個時候我們就要進入相應的處理機制中了;
callInfo = calls->newCall
( sipEvent->getSipCallLeg()->getCallId() );
assert( callInfo != 0 );
callInfo->setFeature( stateMachine );
//如果進入的狀態是自動呼叫(Auto Call)或者是自動應答(Auto Answer)狀態(這
//兩種狀態的確定要在CFG文件中體現)
if ( UaConfiguration::instance()->getLoadGenOn() )
{
/// Assume this is a new call...
/// Also assume that we are not in use.
callInfo->setState( stateMachine->findState( "StateAutoIdle" ) );

//StateAutoIdle這個狀態是一個自動應答和自動呼叫(按照呼叫列表)時候的狀態,這裏我
//們不做介紹,它本身和手動呼叫是非常相似的。
}
else // LoadGen is off
{
//下面這個程序會進入等待遠端SIP事件和本地呼叫事件的狀態StateIdle
if( handleCallWaiting( callInfo ) )
{
cpLog( LOG_ERR, "Returned from handleCallWaiting\n" );
return true;
}
}
} // lots of brackets!
}
return false;
} /// UaBuilder::processSipEvent

handleStatusMsg在做什麼?

  前面我們已經作了簡單的介紹,這個函數的主要目的是在處理Rgister,Notify,和Subscribe等幾個狀態,並且分別調用他們的處理機;
  Rgister調用它的處理機:
  handleRegistrationResponse他的主要作用是處理返回的各種Rgister狀態,例如200,4XX或者是100等狀態,另外它還負責在作爲Mashal Server的時候轉發各種狀態時候,重新設定Expire的值;另外要注意的是在Register中增加了一個新的返回--Trying這個是非常合理的,特別是大型網絡中,對服務器端的性能判定很有效,所以使用協議棧的同志能好好利用這個機制;另外如果發揮的值是401/407狀態(未授權),還需要調用authenticateMessage做相應的處理,以返回的(401/407)狀態中所帶的密鑰加密新的Rgister消息,發送給Register服務器重新進行授權判定;有興趣的可以看看BaseAuthentication中的addAuthorization函數。在介紹UaMarshal和Redirect Server的時候會着重討論這個問題。

  註明:Subscribe的處理機在Feature Server章節裏面在再詳細介紹)。

2.5.1.2 processUaDeviceEvent

  前面說了,processUaDeviceEvent主要是用來處理本地的設備事件,最主要就是處理摘機信號,在這裏程序的流程我就不詳細的列出,不過我們從主要的程序主體部分可以看出:

  在uaDeviceEvent->type == DeviceEventHookUp也就是檢測了摘機以後,程序會採取某些必要的方式取得CallID(主要是通過CFG文件),最後讓程序進入狀態機的StateIdle狀態,這個狀態是接收和發送消息的初始狀態,我們可以在後面將會重點介紹這個狀態;

2.5.1.3 processUaDigitEvent

  也是主要通過判定CFG文件中的LoadGen_On的參數是On或者是Off來決定是否進入StateAutoIdle狀態,或者是StateAutoRS狀態(自動通過Marshal Server進行中轉所有的SIP的消息和狀態,在Marshal Server的時候會做詳細的介紹)。

2.5.1.4 processUaTimerEvent

  這個的流程也實在沒有什麼好說的,前面也有了一定的介紹,如果大家對這些還有不明白的話,可以看一下SIP協議中Trying過程的走勢,主要是對超時處理部分的介紹,就會明白(按照前面所說的UaBuilder::Process中關於SIP命令消息超時的介紹部分)。

2.5.2 SipThread的Run方法:

Void SipThread::thread()
{
… …
while ( true )
{
try
{
//接收所發送的消息,並且準備置入相關的隊列中;
Sptr < SipMsgQueue > sipRcv( mySipStack->receive(1000) );

if ( sipRcv != 0 )
{
Sptr < SipMsg > sipMsg = sipRcv->back();

if ( sipMsg != 0 )
{
//根據本地的地址來檢查是否發生了路由環路
if ( discardMessage(sipMsg) )
{
continue;
}
// 在這裏的myOutputFifo就是 myCallProcessingQueue(異地輸入消息的隊
//列),在Workthread構建的時候會把這個隊列帶入作爲處理參量
Sptr < SipEvent > nextEvent = new SipEvent(myOutputFifo);
if ( nextEvent != 0 )
{
//以下就是把新收到的消息載入隊列當中。
nextEvent->setSipReceive(sipRcv);
nextEvent->setSipStack(mySipStack);
if(myCallLegHistory) nextEvent->setCallLeg();
myOutputFifo->add(nextEvent);
}
}
}
else
{
… …
}
}

catch ( VException& v)
{
… …
}
catch ( ... )
{
… …
}

if ( isShutdown() == true )
{
return;
}
}
}

2.5.2.1 SIP消息的接收/發送緩衝技術

a. 負責接收的主要程序體:
Sptr < SipMsgQueue > sipRcv( mySipStack->receive(1000) );這個方法就是利用SipTransceiver的receive方法接收SIP的消息;
Sptr < SipMsgQueue > SipTransceiver::receive(int timeOut)
{
Sptr < SipMsgQueue > msgQPtr = 0;
//以下是設立超時參數,如果發生超時,那麼就讓該命令無效;
timeval start, now;

if ( timeOut >= 0 )
{
gettimeofday(&start, 0);
}

while (msgQPtr == 0)
{
int timePassed = 0;
if ( timeOut >= 0 )
{
gettimeofday(&now, 0);

timePassed = ( now.tv_sec - start.tv_sec ) * 1000
+ ( now.tv_usec - start.tv_usec ) / 1000;

if (timePassed >= timeOut)
{
return 0;
}
}
recvdMsgsFifo.block(timeOut);

if ( !recvdMsgsFifo.messageAvailable() )
{
continue;
}

SipMsgContainer *msgPtr = recvdMsgsFifo.getNext();
if ( msgPtr == 0)
{
assert(0);
cpLog(LOG_CRIT, "received NULL");
continue;
}
#if 1
if ( natOn == true)
{
//這裏是一個非常有意思的地方,雖然再程序主體中將它設定爲False,也就是我們就
//不能採用NAT轉換了,不過我還是想介紹一下,它主要是用在如果UA是一個標準
//的網關,或者是路由器設備的情況之下,在這個時候,它主要做各個消息包的轉
//譯工作,把路由(Via List)改成下一跳的IP地址和端口地址;
SipVia natVia = msgPtr->msg.in->getVia(0);
LocalScopeAllocator lo;
string addr1 = natVia.getHost().getData(lo);
string addr2 = msgPtr->msg.in->getReceivedIPName().getData(lo);
NetworkAddress netaddr1(addr1);
NetworkAddress netaddr2(addr2);
if ( netaddr1.getHostName() != netaddr2.getHostName())
{
natVia.setReceivedhost(msgPtr->msg.in->getReceivedIPName());
natVia.setReceivedport(msgPtr->msg.in->getReceivedIPPort());
//remove the first item from the via list
msgPtr->msg.in->removeVia(0);
//insert natvia in the vector via list
msgPtr->msg.in->setVia(natVia, 0);
}
}
#endif
//---NAT
/* *********************************************************************/


SipMsgQueue *msgQ = 0;
Sptr sipPtr = msgPtr->msg.in;

if(msgPtr->msg.in->getType() == SIP_STATUS)
//這兩個是處理返回消息隊列的函數,下面將重點介紹
msgQ = sentRequestDB.processRecv(msgPtr);
else
msgQ = sentResponseDB.processRecv(msgPtr);
//更新SNMP命令隊列,並向SNMP網管中心發送接收的消息隊列;
if(msgQ)
{
msgQPtr = msgQ;

//need to have snmpDetails for this.
if (sipAgent != 0)
{
updateSnmpData(sipPtr, INS);
}
}
else if(msgPtr->msg.in != 0)
{
send(msgPtr);
}
else if(msgPtr->msg.out.length())
{
send(msgPtr);
}
else
… …
}
}

b.描述接收/發送SIP消息隊列的主要類:
  SipSentRequestDB:: processRecv和SipSentRequestDB::processSend是一對相互的方法,
另外還有SipSentResponseDB:: processRecv和SipSentResponseDB::processSend是用來記憶狀態/消息的發送和接受的,在這裏和Request的結構基本相同,就不做累述了;前者處理髮送的SIP消息隊列,後者處理接收的SIP消息隊列,爲了實現高效率的處理SIP的隊列,在程序中大量採用了HASH表的方法,由於這個部分的程序非常的多,我不想一一把他們羅列出來,在這裏就做一下簡單的一個瀏覽:

  HASH隊列的抽象:在這裏有三個用於表示HASH表的類:
  SipTransLevel1Node,SipTransLevel2Node,SipTransLevel3Node;

  第一個是表的入口,它的組成由:目的地址 NameAddress源地址 From以及CallID三個部分疊加而成;

  第二個是表的索引,包括CSeq和Via 路由表

  第三個就是具體的消息對了,也就是一個呼叫命令組的列表;詳見下圖:


(點擊放大)

  我們下面一一個簡單的例子來描述一下一個INVITE消息的處理過程:


(點擊放大)

A. 接收到一個Invite Message/發送一個180狀態的情況的情況:
  1. 在UDP通道收到一個INVITE消息
  2. 創建了一個InvMsg,同時發送到SipSentResponseDB中做備份,我們要檢查在這裏有沒有重複的副本;
  3.如果沒有重複,那麼InvMsg就放入RecvFifo中,準備讓應用層進行處理;
  4.應用層通過SipTransciever接收到了InvMsg並且做出相應的處理;
  5.應用層產生了180迴應到SipSentResponseDB中備份,
  6.180在SndFifo中排隊,並且調用SipTransceiver中的SendReply方法回送消息

B.從對方接收到一個100(Trying)狀態的作爲向對方發送Invite消息迴應的情況:
  1. 在UDP通道收到一個INVITE的狀態;
  2. 創建了一個StatusMsg,同時發送到SipSentResquestDB中做備份,我們要檢查在這裏有沒有重複的副本;
  3.如果沒有重複,那麼StatusMsg就放入RecvFifo中,準備讓應用層進行處理;
  4.應用層通過SipTransciever接收到了StatusMsg並且做出相應的處理;
  5.應用層產生了ACK迴應到SipSentResquestDB中備份,
  6.180在SndFifo中排隊,並且調用SipTransceiver中的SendAsync方法回送ACK消息,

c.在存在一個呼叫重新定向的情況:
  *我們下面來看一個更加複雜一點的情況:


(點擊放大)

1>SipSendtRequestDB::processSend方法:

  我們可以做一個很簡單的舉例,大家就對這兩個方法有比較深入的瞭解了,可以以上面的Diagram1來做一個很好的例子比如,Marshal Server開始發送一個Invite的消息,由SipSendtRequestDB::processSend來進行處理,同時並且把這個消息裝入SipMsgContainer中,然後消息被插入到SipTransactionList隊列中:

  topNode->findOrInsert(id)->val->findOrInsert(id)
  最後放在SipTransLevel1Node,SipTransLevel2Node,SipTransLevel3Node形成一個新的節點。

2>SipSentRequestDB:: processRecv方法:
  例如我們接收了一個迴應100 Trying這個迴應的處理自然落在下面的這個部分:
int statusCode = response->getStatusLine().getStatusCode();
if((statusCode < 200) ||
((SipTransceiver::myAppContext == APP_CONTEXT_PROXY) &&
(statusCode == 200) &&
(response->getCSeq().getMethod() == INVITE_METHOD) ) )
… …
retVal = new SipMsgQueue;
retVal->push_back(msgContainer->msg.in)

  單純的把消息隊列返回上面的應用層;
  後續的180(Ringing)也是如此直接返回應用層;
  但是到了接受到200(OK),那麼處理的方式就大不一樣了因爲OK以後命令交互階段已經告一段落,那麼我們通過SipTransactionGC::instance()-> collect的後臺方法處理(Thread線程),根據Delay的時間的變化:如invCleanupDelay等等,刪除當前的一些隊列中消息所佔用的內存(垃圾處理),(具體處理機制可以參看SipTransactionGC::thread()這個後臺處理掉一些孤獨的消息,例如有Request沒有Response的等等,並且根據各個消息所佔用的Delay時間來釋放他們);

  但是如果沒有收到200呢?假設我們收到了302(呼叫轉移)呢?(例如在上面Diagram 1中所表現的那樣)

答案在這裏:
else if(response->getStatusLine().getStatusCode() >= 200)
{
if(level3Node->val->msgs.response)//這裏是檢驗在消息隊列中是否有應答
//產生,也就是Diagram 1中的Second Phase的情況,(第二個Invite消息)
{
SipTransactionList::SipTransListNode *
curr = 0;
if(level3Node->val->myKey == INVITE_METHOD)
{
curr = level2Node->val->level3.getLast();
while(curr)
{
// look for the ACK message
if(curr->val->myKey == ACK_METHOD &&
curr->val->msgs.request)
{
cpLog(DEBUG_NEW_STACK,"duplicate message: %s",
msgContainer->msg.out.logData());
//通過第一個ACK來複制第二個ACK,使用上二者完全相同,
msgContainer->msg.in = 0;
msgContainer->msg.out =
curr->val->msgs.request->msg.out;
msgContainer->msg.type
= curr->val->msgs.request->msg.type;
msgContainer->msg.transport =
curr->val->msgs.request->msg.transport;
msgContainer->msg.netAddr =
curr->val->msgs.request->msg.netAddr;

msgContainer->retransCount = FILTER_RETRANS_COUNT;

break;
}
curr = level2Node->val->level3.getPrev(curr);
}

  很明顯複製一個ACK消息準備進行下一個新的Invite的發送,當然這個是要在有ACK發送以後纔可以進行,如果沒有那麼我們可以假定ACK正處在Processing狀態;

if(!curr)
{
msgContainer->msg.in = 0;
msgContainer->msg.out = "";
msgContainer->retransCount = 0;
}
}
else
… …

  在這個else下面所表示的處理機制是在第一個Message發送出去以後迴應大於200的情況,也就是在Diagram 1中First Phase的情況,也就是發出第一個302的情況,在下面有一行語句:

  msgContainer->msg.out=msgContainer->msg.in->encode()

  它的主要目的是用於形成ACK應答,

  另外後面介紹Marshal Server的時候向異地發送Invite的時候返回4XX的迴應,一般都是4XX等惡名招著的Response不會有其他的,本地一般採取的處理就是嚮應用層彙報,並且消除Hash隊列裏的所有駐留的消息。

  大家可以根據上面介紹的方法實驗一下其他的情況,基本上都是合適的.
目前來說這個處理機制並不使最優的,特別是在服務器的狀態,某些情況事實上並沒有
一個具體的處理方法:例如4XX的迴應,可能會造成超時等待過長。

2.6 在User Agent中的四個重要實例的Run方法:
  HeartLessProxy的兩個Run方法都介紹完畢了,現在我們來看下面將要啓動的四個Run過程:

2.6.1 媒體設備啓動
  DeviceThread->run(); //調用SoundcardDevice::hardwareMain(0)

  第一個是調用Sound Card的處理進程,它最主要的用處是返回各種按鍵的處理信息,他的具體的作用可以參看程序,和具體的操作手冊,非常的簡單易懂,不用詳細介紹,不過要注意的一點是,在程序中,啓動按鍵事件的檢測是通過RTP/RTCP的事件觸發的,(很明顯,例如在通話的時候按下z表示掛機,必須是在有RTP/RTCP事件),說簡單了,沒有設備,鍵盤事件無法觸發。

2.6.2 啓動RTP線程,用於對RTP/RTCP包的接收和發送管理;
  rtpThread->run //調用SoundCardDevice::processRTP()

  參看RtpThread實例化的過程可以看出,實際上就是調用SoundCardDevice的processRTP過程。

SoundCardDevice::processRTP ()
{
… …
if (audioStack == 0)
{
… …
return;
}

bool bNothingDo = true;

RtpSessionState sessionState = audioStack->getSessionState();

if ( sessionState == rtp_session_undefined )
{
deviceMutex.unlock();
… …
return;
}
if( sessionState == rtp_session_recvonly ||
sessionState == rtp_session_sendrecv )
{
// audioStack就是RtpSession,在這裏它是在構建這個聲音設備的時候,就創建它了。
//這裏表示從一個創建好的RTP會話中接收一幀數據,
inRtpPkt = audioStack->receive();
if( inRtpPkt )
{
//這裏的聲卡目前只能接受一種壓縮方式PCM,所以只能解析這一種最常用的,
if( inRtpPkt->getPayloadType() != rtpPayloadPCMU ||
//RTP的採樣頻率是否爲要求的頻率,例如爲20ms
inRtpPkt->getPayloadUsage() != NETWORK_RTP_RATE )
{
cpLog(LOG_ERR,"Received from RTP stack incorrect payload type");
}
//將數據輸出到聲卡,
writeToSoundCard( (unsigned char*) inRtpPkt->getPayloadLoc(),
inRtpPkt->getPayloadUsage() );
bNothingDo = false;

… …
}
}

// 這裏是發送一幀數據;
if( sessionState == rtp_session_sendonly ||
sessionState == rtp_session_sendrecv )
{
int cc;
if( audioStack->getRtcpTran() )
{ //如果有發送零聲的情況,例如零聲回送被叫端,這裏在OpRing裏通過//sendRemoteRingback過程來實現向遠端回送零聲(sendRingback=True)
if( sendRingback )
{
cc = getRingbackTone( dataBuffer, RESID_RTP_RATE );
#ifdef WIN32
Sleep(15);
#endif
}
else
{//從聲卡中讀入一幀數據,按照cfg文件中規定的採樣標準
cc = readFromSoundCard( dataBuffer, RESID_RTP_RATE );
}
if ((cc > 0) && audioStack)
{//將這幀數據(毛數據,未壓縮的作成RTP包發送出去);
audioStack->transmitRaw( (char*)dataBuffer, cc );
bNothingDo = false;
}
… …
}
}
… …
deviceMutex.unlock();

return;
}

2.6.3 合法用戶列表的獲取(Redirection Server專用)

  第三個過程是featureThread->run,,這個過程主要是用在向重定向服務器(Redirection Server)和Provisioning Server中的Feature線程,它實質上是調用 subscribe -Manager->subscribeMain,主體程序部分是向Provisioning Server發送Subscribe消息,在這個循環中會反覆的發送SubScribe消息到Provision Server中去,稍後我們要介紹的UaBuilder::handleStatusMsg(UaBuilder::processSipEvent中)過程會將會處理從Provision Server 返回的Notify消息,關於Subscribe/Notify消息對的介紹我們可以參看在Vocal中的相關介紹,它的作用範圍是在一個普通的UA向Marshal Server進行註冊或者是證實的時候,Marshal Server同時向Redirection Server發出Register消息,並且由Redirection Server向Provisioning Server發送Subscribe消息,對用戶列表進行檢測;我們可以舉一個例子來說明這個過程:



(點擊放大)


我們來看Diagram.7

1> 在A階段當啓動Redirection Server(RS)的時候,RS向Provisioning Server(PS)發送SubScribe消息,取得合法的用戶列表;
2> 在B階段,UA端向Marshal Server發送Register消息,以確認自己是否在合法用戶列表內;
3> 在C階段,RS將通過Subscribe/Notify命令對把該用戶的呼叫特性列表(呼叫等待,呼叫轉接,語音郵件,呼叫前轉,禁止呼叫等信息)得到該用戶的呼叫特性;
我們在Redirection Server這一章內將詳細介紹Subscribe/Notify命令對。

2.6.4 監測線程:
  一個調用的RUN方法loadGenThread->run是一個監測線程,檢查各種迴應和請求消息,並記錄在LOG文件中。

2.6.5 自動呼叫
  在loadGenThread->run後面的程序實現了一個自動在預定時間內發送INVITE消息的過程,大家有興趣可以參看OpAutoCall類,當在UserAgent::Run()中通過檢測Cfg文件,通過setLoadGenSignalType(LoadGenStartCall)設定了一個公共變量以後,我們可以發現系統將自動進入OpAutoCall操作,並且啓動INVITE開始呼叫。


(點擊放大)

  好了,通過上面的介紹後我們需要知道如何讓系統進入Idle狀態,在這個狀態中系統處於一種"等待"的狀態,接收本地的命令輸入,和遠端的消息;這個狀態是所有後續狀態的一個初始階段,在上述程序中我們可以在processSipEvent過程中找到handleCallWaiting子程序,就在該過程中讓系統進入Idle狀態;見下面的程序:
… …
if ( UaConfiguration::instance()->getLoadGenOn() )
{
callInfo->setState
( stateMachine->findState( "StateAutoIdle" ) );
}
else // LoadGen is off
{
if( handleCallWaiting( callInfo ) )
{
return true;
}
… …

(未完待續)

發佈了14 篇原創文章 · 獲贊 1 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章