run(); mySipThre"> 首頁 >> 新聞

在Vovida的基礎(chǔ)上實(shí)現(xiàn)自己的SIP協(xié)議棧(一)

在Vovida的基礎(chǔ)上實(shí)現(xiàn)自己的SIP協(xié)議棧(二)

盧政 2003/08/04

2. 5 HeartLessProxy Run方法的實(shí)現(xiàn)

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

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

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

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

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

}

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

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

handleStatusMsg在做什么?

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

  注明:Subscribe的處理機(jī)在Feature Server章節(jié)里面在再詳細(xì)介紹)。

2.5.1.2 processUaDeviceEvent

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

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

2.5.1.3 processUaDigitEvent

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

2.5.1.4 processUaTimerEvent

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

2.5.2 SipThread的Run方法:

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

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

if ( sipMsg != 0 )
{
//根據(jù)本地的地址來檢查是否發(fā)生了路由環(huán)路
if ( discardMessage(sipMsg) )
{
continue;
}
// 在這里的myOutputFifo就是 myCallProcessingQueue(異地輸入消息的隊
//列),在Workthread構(gòu)建的時候會把這個隊列帶入作為處理參量
Sptr < SipEvent > nextEvent = new SipEvent(myOutputFifo);
if ( nextEvent != 0 )
{
//以下就是把新收到的消息載入隊列當(dāng)中。
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消息的接收/發(fā)送緩沖技術(shù)

a. 負(fù)責(zé)接收的主要程序體:
Sptr < SipMsgQueue > sipRcv( mySipStack->receive(1000) );這個方法就是利用SipTransceiver的receive方法接收SIP的消息;
Sptr < SipMsgQueue > SipTransceiver::receive(int timeOut)
{
Sptr < SipMsgQueue > msgQPtr = 0;
//以下是設(shè)立超時參數(shù),如果發(fā)生超時,那么就讓該命令無效;
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)
{
//這里是一個非常有意思的地方,雖然再程序主體中將它設(shè)定為False,也就是我們就
//不能采用NAT轉(zhuǎn)換了,不過我還是想介紹一下,它主要是用在如果UA是一個標(biāo)準(zhǔn)
//的網(wǎng)關(guān),或者是路由器設(shè)備的情況之下,在這個時候,它主要做各個消息包的轉(zhuǎn)
//譯工作,把路由(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)
//這兩個是處理返回消息隊列的函數(shù),下面將重點(diǎn)介紹
msgQ = sentRequestDB.processRecv(msgPtr);
else
msgQ = sentResponseDB.processRecv(msgPtr);
//更新SNMP命令隊列,并向SNMP網(wǎng)管中心發(fā)送接收的消息隊列;
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.描述接收/發(fā)送SIP消息隊列的主要類:
  SipSentRequestDB:: processRecv和SipSentRequestDB::processSend是一對相互的方法,
另外還有SipSentResponseDB:: processRecv和SipSentResponseDB::processSend是用來記憶狀態(tài)/消息的發(fā)送和接受的,在這里和Request的結(jié)構(gòu)基本相同,就不做累述了;前者處理發(fā)送的SIP消息隊列,后者處理接收的SIP消息隊列,為了實(shí)現(xiàn)高效率的處理SIP的隊列,在程序中大量采用了HASH表的方法,由于這個部分的程序非常的多,我不想一一把他們羅列出來,在這里就做一下簡單的一個瀏覽:

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

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

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

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


(點(diǎn)擊放大)

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


(點(diǎn)擊放大)

A. 接收到一個Invite Message/發(fā)送一個180狀態(tài)的情況的情況:
  1. 在UDP通道收到一個INVITE消息
  2. 創(chuàng)建了一個InvMsg,同時發(fā)送到SipSentResponseDB中做備份,我們要檢查在這里有沒有重復(fù)的副本;
  3.如果沒有重復(fù),那么InvMsg就放入RecvFifo中,準(zhǔn)備讓應(yīng)用層進(jìn)行處理;
  4.應(yīng)用層通過SipTransciever接收到了InvMsg并且做出相應(yīng)的處理;
  5.應(yīng)用層產(chǎn)生了180回應(yīng)到SipSentResponseDB中備份,
  6.180在SndFifo中排隊,并且調(diào)用SipTransceiver中的SendReply方法回送消息

B.從對方接收到一個100(Trying)狀態(tài)的作為向?qū)Ψ桨l(fā)送Invite消息回應(yīng)的情況:
  1. 在UDP通道收到一個INVITE的狀態(tài);
  2. 創(chuàng)建了一個StatusMsg,同時發(fā)送到SipSentResquestDB中做備份,我們要檢查在這里有沒有重復(fù)的副本;
  3.如果沒有重復(fù),那么StatusMsg就放入RecvFifo中,準(zhǔn)備讓應(yīng)用層進(jìn)行處理;
  4.應(yīng)用層通過SipTransciever接收到了StatusMsg并且做出相應(yīng)的處理;
  5.應(yīng)用層產(chǎn)生了ACK回應(yīng)到SipSentResquestDB中備份,
  6.180在SndFifo中排隊,并且調(diào)用SipTransceiver中的SendAsync方法回送ACK消息,

c.在存在一個呼叫重新定向的情況:
  *我們下面來看一個更加復(fù)雜一點(diǎn)的情況:


(點(diǎn)擊放大)

1>SipSendtRequestDB::processSend方法:

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

  topNode->findOrInsert(id)->val->findOrInsert(id)
  最后放在SipTransLevel1Node,SipTransLevel2Node,SipTransLevel3Node形成一個新的節(jié)點(diǎn)。

2>SipSentRequestDB:: processRecv方法:
  例如我們接收了一個回應(yīng)100 Trying這個回應(yīng)的處理自然落在下面的這個部分:
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)

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

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

答案在這里:
else if(response->getStatusLine().getStatusCode() >= 200)
{
if(level3Node->val->msgs.response)//這里是檢驗在消息隊列中是否有應(yīng)答
//產(chǎn)生,也就是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來復(fù)制第二個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);
}

  很明顯復(fù)制一個ACK消息準(zhǔn)備進(jìn)行下一個新的Invite的發(fā)送,當(dāng)然這個是要在有ACK發(fā)送以后才可以進(jìn)行,如果沒有那么我們可以假定ACK正處在Processing狀態(tài);

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

  在這個else下面所表示的處理機(jī)制是在第一個Message發(fā)送出去以后回應(yīng)大于200的情況,也就是在Diagram 1中First Phase的情況,也就是發(fā)出第一個302的情況,在下面有一行語句:

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

  它的主要目的是用于形成ACK應(yīng)答,

  另外后面介紹Marshal Server的時候向異地發(fā)送Invite的時候返回4XX的回應(yīng),一般都是4XX等惡名招著的Response不會有其他的,本地一般采取的處理就是向應(yīng)用層匯報,并且消除Hash隊列里的所有駐留的消息。

  大家可以根據(jù)上面介紹的方法實(shí)驗一下其他的情況,基本上都是合適的.
目前來說這個處理機(jī)制并不使最優(yōu)的,特別是在服務(wù)器的狀態(tài),某些情況事實(shí)上并沒有
一個具體的處理方法:例如4XX的回應(yīng),可能會造成超時等待過長。

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

2.6.1 媒體設(shè)備啟動
  DeviceThread->run(); //調(diào)用SoundcardDevice::hardwareMain(0)

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

2.6.2 啟動RTP線程,用于對RTP/RTCP包的接收和發(fā)送管理;
  rtpThread->run //調(diào)用SoundCardDevice::processRTP()

  參看RtpThread實(shí)例化的過程可以看出,實(shí)際上就是調(diào)用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,在這里它是在構(gòu)建這個聲音設(shè)備的時候,就創(chuàng)建它了。
//這里表示從一個創(chuàng)建好的RTP會話中接收一幀數(shù)據(jù),
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");
}
//將數(shù)據(jù)輸出到聲卡,
writeToSoundCard( (unsigned char*) inRtpPkt->getPayloadLoc(),
inRtpPkt->getPayloadUsage() );
bNothingDo = false;

… …
}
}

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

return;
}

2.6.3 合法用戶列表的獲。≧edirection Server專用)

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



(點(diǎn)擊放大)


我們來看Diagram.7

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

2.6.4 監(jiān)測線程:
  一個調(diào)用的RUN方法loadGenThread->run是一個監(jiān)測線程,檢查各種回應(yīng)和請求消息,并記錄在LOG文件中。

2.6.5 自動呼叫
  在loadGenThread->run后面的程序?qū)崿F(xiàn)了一個自動在預(yù)定時間內(nèi)發(fā)送INVITE消息的過程,大家有興趣可以參看OpAutoCall類,當(dāng)在UserAgent::Run()中通過檢測Cfg文件,通過setLoadGenSignalType(LoadGenStartCall)設(shè)定了一個公共變量以后,我們可以發(fā)現(xiàn)系統(tǒng)將自動進(jìn)入OpAutoCall操作,并且啟動INVITE開始呼叫。


(點(diǎn)擊放大)

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

(未完待續(xù))

在Vovida的基礎(chǔ)上實(shí)現(xiàn)自己的SIP協(xié)議棧(三)

作者供稿 CTI論壇編輯

作者聯(lián)系方法:lu_zheng@21cn.com


分類信息:     文摘