首頁(yè) >> 新聞
在Vovida的基礎(chǔ)上實(shí)現(xiàn)自己的SIP協(xié)議棧(二)

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

盧政 2003/08/05

3.開(kāi)始一個(gè)呼叫和等待對(duì)方呼叫:

3.1 系統(tǒng)創(chuàng)建StateIdle狀態(tài):

StateIdle::StateIdle()
{
addOperator( new OpStartCall );
addOperator( new OpRing );
addOperator( new OpOnHook ); // bizarre case
}

  注意:所有的狀態(tài)StateIdle,以及下面要介紹的StateInCall(大概有幾十個(gè)左右)等等都是State的子類,所以他們統(tǒng)統(tǒng)都繼承了State的所有方法,在State中通過(guò)addOperator的方法來(lái)增加"操作"例如:開(kāi)始呼叫是OpStartCall,掛機(jī)是OpOnHook等等,在這個(gè)狀態(tài)"容器"里通收集所需要的操作,并且通過(guò)State::Process方法調(diào)用執(zhí)行他們;

  我們可以注意到在UaBuilder::process中的 SendEvent提供了一種把系統(tǒng)收到的各種消息(在UaCallInfo中匯集的,無(wú)論是本地硬件消息或者是異地接收的消息)傳遞到狀態(tài)機(jī)(UaStateMachine)的方法, 并且分配調(diào)用當(dāng)前狀態(tài)的處理過(guò)程。

… …
UaBuilder::sendEvent( const Sptr < SipProxyEvent > nextEvent )
{
nextEvent->setCallInfo ( callInfo, calls );
stateMachine->process( nextEvent );//Here we use state machine to run elementin //operator queue
return;
}

  UaStateMachine狀態(tài)機(jī)的作用主要是在運(yùn)行UaCallInfo(由前面詳細(xì)敘述的UaBuilder::process來(lái)裝入各種事件(SipProxyEvent))容器中的各種操作,上面這句就是調(diào)用Sate(各種State的基類)中的process方法。

  我們下面來(lái)歸納的看一下如何讓設(shè)備的狀態(tài)進(jìn)入等待命令的idle狀態(tài)所遍列的類和方法:

UaBuilder->process() -UaBuilder->handleCallWaiting--> addOperator(new OpXXX)-->UaBuilder->sendEvent()-->UaStateMachine->process-->Sate->process()-->-->OpXXX->process()

3.2 開(kāi)始一個(gè)呼叫:
  從上面的介紹我們可以知道,系統(tǒng)在初始化以后會(huì)進(jìn)入Idle狀態(tài),如果用戶使電話進(jìn)入了摘機(jī)(Offhook),那么這個(gè)事件會(huì)發(fā)送到本地處理隊(duì)列,這樣OpstartDialing會(huì)檢測(cè)到這個(gè)時(shí)間并且把系統(tǒng)放置在Dialing 狀態(tài)中;這時(shí)用戶輸入被叫的電話號(hào)碼,或者是Url,在Dialing狀態(tài)中所有的事件被OpAddDigit操作處理,但是這個(gè)操作并不會(huì)讓當(dāng)前的狀態(tài)從Dialing離開(kāi),而是維持到撥號(hào)完畢。

  當(dāng)用戶撥號(hào)完畢以后,這個(gè)時(shí)候會(huì)產(chǎn)生一個(gè)Dialing Complete事件,我們知道這個(gè)事件是由OpInviteURL產(chǎn)生,這個(gè)操作負(fù)責(zé)把INVITE消息發(fā)送到被叫端,并且讓系統(tǒng)陷入相應(yīng)的狀態(tài)機(jī)中,這個(gè)時(shí)刻系統(tǒng)進(jìn)入了Trying狀態(tài)

  一旦用戶進(jìn)入Trying狀態(tài),主叫將會(huì)等待被叫摘起話筒的過(guò)程,如果被叫摘機(jī),那么將會(huì)發(fā)送一個(gè)200的消息給主叫,主叫端的OpFarEndAnswered操作將會(huì)處理這個(gè)消息,并且讓系統(tǒng)進(jìn)入InCall狀態(tài),主叫/被叫之間將打開(kāi)RTP/RTCP通道,開(kāi)始通訊,當(dāng)用戶掛機(jī)以后,進(jìn)入OpTermiateCall狀態(tài),并且互相發(fā)送Bye消息,中斷通話,系統(tǒng)最終返回Idle狀態(tài)。

3.2.1 OpStartCall主程序部分:
  這里我們可以看出在idle狀態(tài)中首先經(jīng)歷的是OpStartCall操作,這個(gè)操作目的是開(kāi)始呼叫
OpStartCall::process( const Sptr < SipProxyEvent > event )

{
Sptr < UaDeviceEvent > deviceEvent;
deviceEvent.dynamicCast( event );
if ( deviceEvent == 0 )
{
return 0;
}
if ( deviceEvent->type != DeviceEventHookUp )//這里是檢測(cè)是否開(kāi)始進(jìn)行呼叫
{
return 0;
}
//如果該過(guò)程接收到一個(gè)DeviceEventHookUp事件,那么開(kāi)始這個(gè)事件的處理過(guò)程
Sptr < UaStateMachine > stateMachine;
stateMachine.dynamicCast( event->getCallInfo()->getFeature() );
assert( stateMachine != 0 );
return stateMachine->findState( "StateDialing" );
//如果開(kāi)始呼叫,那么我們轉(zhuǎn)入StateDialing狀態(tài)
}

3.2.2 取得鍵盤的事件
  調(diào)用deviceThread->run()(SoundCardDevice::process())來(lái)實(shí)現(xiàn)對(duì)鍵盤事件的檢測(cè),判斷是什么事件--摘機(jī)(OffHook)掛機(jī)(OnHook),是否進(jìn)行了撥號(hào)等等,在這個(gè)程序中所有的鍵盤全部解釋為一個(gè)事件,這個(gè)程序很簡(jiǎn)單,我就不在這里做介紹了。

3.2.3 狀態(tài)機(jī)(State)對(duì)各個(gè)操作(Operator)的處理過(guò)程
  我們回頭再來(lái)看一下這個(gè)state::process()的處理核心機(jī)制,這個(gè)是所有的狀態(tài)的處理機(jī),各種狀態(tài)將各自的操作序列(各個(gè)OPXXX)裝入處理機(jī)中調(diào)用各自的process方法

首先我先解釋三種操作(OPXXX)隊(duì)列
  1. MyEntryOperators State的入口隊(duì)列,例如OpStartDialTone,OpStartCall將會(huì)位于這個(gè)處理隊(duì)列中
  2. MyOperators State中各種執(zhí)行狀態(tài)的集合
  3. MyExitOperators 退出狀態(tài)的集合,例如OpStopBusyTone,OpStartRinging將要位于這個(gè)隊(duì)列中

  這三個(gè)隊(duì)列中包含了幾乎全部的OPXXX系列操作符,用于管理和維護(hù)這些操作符,并且調(diào)動(dòng)他們執(zhí)行。

State::process(const Sptr < SipProxyEvent > event)
{
… …
Sptr < State > currentState = event->getCallInfo()->getState();
if ( currentState != PROXY_CONTINUE
&& currentState != this )
… …
Sptr < State > nextState = PROXY_CONTINUE;
for ( OperatorIter iter = myOperators.begin();
iter != myOperators.end();
iter++
)
{
Sptr < State > newState = (*iter)->process(event);//調(diào)用各個(gè)OPXXX的process處理機(jī)
//制
if( newState == PROXY_DONE_WITH_EVENT )
{//在Marshall server的時(shí)候有時(shí)侯server端會(huì)給狀態(tài)機(jī)賦于這種狀態(tài)
//一旦進(jìn)入這種狀態(tài),那么程序?qū)⑻幱谕顺霎?dāng)前狀態(tài)的執(zhí)行階段,進(jìn)入下一個(gè)階段
break;
}
else if( newState != PROXY_CONTINUE )
{ assert( nextState == PROXY_CONTINUE );
nextState = newState;
}
}
if( nextState != PROXY_CONTINUE )
{
processExit(event);//退出當(dāng)前狀態(tài),進(jìn)入下一個(gè)狀態(tài)中
event->getCallInfo()->setState(nextState);
nextState->processEntry(event);//進(jìn)入下一個(gè)狀態(tài)。
}
return ( nextState );
}

3.2.4 開(kāi)始一個(gè)呼叫所經(jīng)歷的各種操作(Operator):
  下圖表示了從摘機(jī)到通話完畢的各個(gè)狀態(tài)之間程序各種類之間的遷移過(guò)程,當(dāng)然這個(gè)圖還僅僅是略圖,它只是表示了了一個(gè)終端簡(jiǎn)單的主動(dòng)呼叫,摘機(jī),通話,掛機(jī)的過(guò)程,如果協(xié)議棧軟件應(yīng)用于Marshal或者是Redirection Server的話,那么采用的協(xié)議流程和下面又有一些不一樣了,以后會(huì)對(duì)這些做詳細(xì)介紹。

  在本圖中粗體的部分表示加入MyEntryOperator隊(duì)列中的操作符
  正體的部分表示加入MyOperator隊(duì)列中的操作符
  斜體的部分表示加入MyExitOperator隊(duì)列中的操作符


(點(diǎn)擊放大)


  我們根據(jù)SIP協(xié)議來(lái)定義一下一個(gè)發(fā)起呼叫或者是等待接收一個(gè)呼叫的狀態(tài)遷移過(guò)程:
(根據(jù)Vocal中提供的Ua Simple State Transfer狀態(tài)圖)


(點(diǎn)擊放大)


我們下面來(lái)對(duì)照這些狀態(tài)一個(gè)個(gè)的進(jìn)行介紹:

  從上面的程序中可以看到,在系統(tǒng)所有的狀態(tài)初始化完畢以后,執(zhí)行了內(nèi)部方法handleCallWaiting系統(tǒng)進(jìn)入一個(gè)StateIdle狀態(tài)這個(gè)時(shí)候系統(tǒng)在等待本地呼叫和遠(yuǎn)端異地的呼叫:
我們前面已經(jīng)知道了在UaBuilder::process中通過(guò)Send的方法向消息隊(duì)列myCallContainer中發(fā)送相應(yīng)的事件信息這里然后在stateMachine->process( nextEvent )通過(guò)狀態(tài)機(jī)中的Process方法調(diào)用State::Process方法對(duì)這個(gè)隊(duì)列進(jìn)行中的SipProxyEvent處理,換句話來(lái)說(shuō),也就是我們把所有的從Uabuilder::Process提取的狀態(tài)通過(guò)Send的方法發(fā)送到狀態(tài)機(jī)中,由狀態(tài)機(jī)調(diào)用各個(gè)狀態(tài)的process方法,對(duì)各個(gè)狀態(tài)(StateXXX)進(jìn)行處理,這里我們可以參看一下下面的Idle狀態(tài)的調(diào)用方法
3.2.5 如何進(jìn)入待機(jī)狀態(tài)(Idle狀態(tài))

  大家看一下,在deviceThread->run();(調(diào)用SoundcardDevice::hardwareMain(0))來(lái)檢檢測(cè)鍵盤事件,這個(gè)時(shí)候在下面程序中得到DeviceEventHookUp鍵盤事件,表示摘機(jī),同時(shí)由在Uabuilder::Process()過(guò)程中的ProcessUaDeviceEvent中解釋該事件,并且進(jìn)入StateIdle狀態(tài)。
… …
case 'a': // offhook
hookStateOffhook = true;
playDialTone = true;
event->type = DeviceEventHookUp;
break;
… …
這樣創(chuàng)建了StateIdle:
StateIdle::StateIdle()
{
addOperator( new OpStartCall );
addOperator( new OpRing );
addOperator( new OpOnHook ); // bizarre case
}

  按照上面的加入MyOperator隊(duì)列的方法將操作符加入隊(duì)列中去,并且運(yùn)行各個(gè)操作符的Process方法:
  OpStartCall為主動(dòng)開(kāi)始一個(gè)呼叫過(guò)程;
  OpRing為被動(dòng)得等待遠(yuǎn)端的一個(gè)呼叫;

3.2.6 如何開(kāi)始撥號(hào)并且開(kāi)始一個(gè)呼叫:
  在OpStartCall中主要讓用戶的狀態(tài)機(jī)陷入下一個(gè)狀態(tài)中StateDialing這個(gè)操作是非常簡(jiǎn)單的直接進(jìn)入撥號(hào)狀態(tài):

stateMachine->findState( "StateDialing" )
StateDialing::StateDialing()
{
addEntryOperator( new OpStartDialTone );
addOperator( new OpAddDigit );
addOperator( new OpStopDialTone );
addOperator( new OpInviteUrl );
addOperator( new OpDialError );
addOperator( new OpOnHook );
}

  這里是加入操作隊(duì)列準(zhǔn)備開(kāi)始執(zhí)行的操作符

3.2.6.1 OpStartDialTone:
  第一個(gè)操作符是OpStartDialTone顧名思義這個(gè)操作符是為本地提供撥號(hào)音的操作,不過(guò)在這里出現(xiàn)了一個(gè)本地設(shè)備事件的處理隊(duì)列:

… …
Sptr < UaHardwareEvent > signal = new UaHardwareEvent( UaDevice::getDeviceQueue() );
signal->type = HardwareSignalType;
signal->signalOrRequest.signal = DeviceSignalDialToneStart;//設(shè)置本次操作的操作類型
UaDevice::getDeviceQueue()->add( signal );//在處理隊(duì)列中加入本次需要處理的操作。
… …

處理本次事件的程序部分在聲卡描述這個(gè)類里:處理順序如下:

SoundCardDevice::hardwareMain-->ResGwDevice::processSessionMsg( myQ->getNext() )
--> … …case HardwareSignalType:
if( msg->signalOrRequest.signal == DeviceSignalFwding )
… …
provideSignal((msg->signalOrRequest).signal)
--> …… case DeviceSignalDialToneStart:
……. provideDialToneStart();

  到了這個(gè)程序就非常簡(jiǎn)單了provideDialToneStart主要提供了處理本地放送撥號(hào)音的過(guò)程,打開(kāi)設(shè)備發(fā)送聲音。在程序中大量使用了HardwareMsg MyQ這個(gè)隊(duì)列,目的主要上建立各個(gè)線程(例如上面所說(shuō)介紹的OpStartDialTone操作),和本地設(shè)備之間的處理消息傳遞的通道

3.2.6.2 輸入電話號(hào)碼開(kāi)始撥號(hào):
  OpAddDigit 的主要作用是把在摘機(jī)以后輸入的十進(jìn)制的IP電話號(hào)碼或者是URL放在DigitCollector隊(duì)列中以便后續(xù)的撥號(hào)程序使用,這兩種方式在程序中都適用,程序中會(huì)相應(yīng)的Url 改變成十進(jìn)制的IP電話號(hào)碼放入Dial這個(gè)字符串中;請(qǐng)注意這里模擬了一個(gè)撥號(hào)等待超時(shí)的方法(調(diào)動(dòng)UaDigitTimerEvent類)。

3.2.6.3 OpStopDialTone主要是用于關(guān)掉撥號(hào)音。

3.2.6.4 OpInviteUrl目的是用在建立一個(gè)INVITE命令并且向被叫發(fā)送;
  我們現(xiàn)在來(lái)看一下他的程序結(jié)構(gòu),而在此之前我們來(lái)看一下一個(gè)普通的SIP呼叫/應(yīng)答/的過(guò)程



  這里我們先來(lái)看第一個(gè)Invite命令的發(fā)送:
  我們知道一個(gè)SIP的基本命令包括以下幾個(gè)基本字段:
  From:請(qǐng)求的發(fā)起方,to:請(qǐng)求的接收方,Call-ID請(qǐng)求標(biāo)識(shí),
  Cseq:命令的序列號(hào),Via:路由列表,Contact:后續(xù)通訊地址;
  一個(gè)基本的INVITE命令如下所示,注意這個(gè)INVITE的構(gòu)造方式也適用于Marshal, Feature等其他需要發(fā)送/轉(zhuǎn)發(fā)INVITE的設(shè)備上:

SIP Headers
-----------------------------------------------------------------
sip-req: INVITE sip:93831073@192.168.36.180 SIP/2.0 [192.168.6.20:50753-
>192.168.36.180:5060]
Header: Via: SIP/2.0/UDP 192.168.6.20:5060
Header: From: sip:5120@192.168.6.20
Header: To: [sip:93831073@192.168.36.180]
Header: Call-ID: c2943000-23e062-2e278-2e323931@192.168.6.20
Header: CSeq: 100 INVITE
Header: Expires: 180
Header: User-Agent: Cisco IP Phone/ Rev. 1/ SIP enabled
Header: Accept: application/sdp
Header: Contact: sip:5120@192.168.6.20:5060
Header: Content-Type: application/sdp
Header: Content-Length: 218
-----------------------------------------------------------------
SDP Headers
-----------------------------------------------------------------
Header: v=0
Header: o=CiscoSystemsSIP-IPPhone-UserAgent 21012 9466 IN IP4 192.168.6.20
Header: s=SIP Call
Header: c=IN IP4 192.168.6.20
Header: t=0 0
Header: m=audio 25776 RTP/AVP 0 101
Header: a=rtpmap:0 pcmu/8000
Header: a=rtpmap:101 telephone-event/8000
Header: a=fmtp:101 0-11

const Sptr < State >
OpInviteUrl::process( const Sptr < SipProxyEvent > event )
{

Sptr < UaDigitTimerEvent > timerEvent;
timerEvent.dynamicCast( event );
if ( timerEvent == 0 )
{
return 0;
}

... ...
//根據(jù)DigitCollector中的內(nèi)容創(chuàng)建一個(gè)合法的URL
toUrl = new SipUrl( digitCollector->getUrl() );

if ( dial_phone == digitCollector->getDialMethod() )
{
… …
toUrl->setUserValue( toUrl->getUserValue(), dial_phone );
}
... ...
//Proxy_Server表示的是被叫方代理服務(wù)器的地址
string proxyServer = UaConfiguration::instance()->getProxyServer();
//如果對(duì)方的SIP地址不完整的話,那么就要用被叫方的代理服務(wù)器
if ( toUrl->getHost().length() <= 0 && proxyServer.length() )
{
NetworkAddress na( proxyServer );
//形成一個(gè)具體的被叫的SIP地址,并設(shè)置端口號(hào)碼,例如//93831073@192.168.36.180:5060
toUrl->setHost( na.getIpName() );
if( na.getPort() > 0 )
{

toUrl->setPort( na.getPort() );
}
}

... ...
proxyUrl = new SipUrl( "sip:" + proxyServer );
}
... ...

//定義本地接收異地的SIP消息的接收端口;我們根據(jù)Cfg文件暫時(shí)定為5000
string sipPort = UaConfiguration::instance()->getLocalSipPort();
//根據(jù)目的SIP地址和接收端口構(gòu)造一個(gè)SIP的Invite命令,同時(shí)通過(guò)函數(shù):
//setInviteDetails構(gòu)造一個(gè)Via頭的部分內(nèi)容,以及From,to,Cseq, contact
InviteMsg msg( toUrl, atoi( sipPort.c_str() ) );
//設(shè)置Call-ID
msg.setCallId( *(UaDevice::instance()->getCallId()) );
//設(shè)置請(qǐng)求頭啟始行,例如: sip-req: INVITE sip:93831073@192.168.36.180 SIP/2.0 [192.168.6.20:50753->192.168.36.180:5060]
//如果存在代理服務(wù)器的話,那么根據(jù)RFC3261中的7.1項(xiàng)目來(lái)構(gòu)造請(qǐng)求頭部啟始//行,這里的可能只包括9383107的IP電話NUM而沒(méi)有代理服務(wù)器部分。
if ( proxyServer.length() > 0 )
{
SipRequestLine reqLine = msg.getRequestLine();
Sptr< BaseUrl > baseUrl = reqLine.getUrl();
assert( baseUrl != 0 );
if( baseUrl->getType() == TEL_URL )
{
... ...
}
// Assume we have a SIP_URL
Sptr< SipUrl > reqUrl;
reqUrl.dynamicCast( baseUrl );
assert( reqUrl != 0 );
//設(shè)置被叫端的代理服務(wù)器SIp地址以及端口號(hào)
reqUrl->setHost( proxyUrl->getHost() );
reqUrl->setPort( proxyUrl->getPort() );

if(UaConfiguration::instance()->getSipTransport() == "TCP")
{
reqUrl->setTransportParam( Data("tcp"));
}
reqLine.setUrl( reqUrl );
//最后完整的設(shè)置一個(gè)被叫端的SIP地址到請(qǐng)求頭部。
msg.setRequestLine( reqLine );
}
//設(shè)置From頭部
SipFrom from = msg.getFrom();
//設(shè)置顯示的用戶名到From條目中去
from.setDisplayName( Data( UaConfiguration::instance()->getDisplayName()));
Sptr< BaseUrl > baseUrl = from.getUrl();
assert( baseUrl != 0 );
... ...}
// Assume we have a SIP_URL
Sptr< SipUrl > fromUrl;
fromUrl.dynamicCast( baseUrl );
assert( fromUrl != 0 );
//設(shè)置用戶名
fromUrl->setUserValue( Data( UaConfiguration::instance()->getUserName() ),
"phone" );
from.setUrl( fromUrl );
msg.setFrom( from );

//設(shè)置路由表,首先取出當(dāng)前的INVITE消息中的路由表,設(shè)置傳輸協(xié)議路由表中//的其他信息在setInviteDetails已經(jīng)做了設(shè)定(主機(jī)名,端口名等)
SipVia via = msg.getVia();
msg.removeVia();
via.setTransport( UaConfiguration::instance()->getSipTransport() );
msg.setVia( via );
//設(shè)置Contact頭部,這里如果是發(fā)送一個(gè)Invite的消息(開(kāi)始呼叫一個(gè)遠(yuǎn)端的主
//機(jī))那么采用的頭部的Contact地址當(dāng)然本主機(jī)的地址。
// Set Contact: header
Sptr< SipUrl > myUrl = new SipUrl;
myUrl->setUserValue( UaConfiguration::instance()->getUserName(), "phone" );
myUrl->setHost( Data( theSystem.gethostAddress() ) );
myUrl->setPort( atoi( UaConfiguration::instance()
->getLocalSipPort().c_str() ) );
if(UaConfiguration::instance()->getSipTransport() == "TCP")
{
myUrl->setTransportParam( Data("tcp"));
}
SipContact me;
me.setUrl( myUrl );
msg.setNumContact( 0 ); // Clear
msg.setContact( me );

Sptr < UaCallInfo > call;
call.dynamicCast( event->getCallInfo() );
assert( call != 0 );
//設(shè)置SDP
addSdpToMsg(msg,
//設(shè)置每個(gè)毫秒的RTP包的個(gè)數(shù);
UaConfiguration::instance()->getNetworkRtpRate(),
//設(shè)置RTP的端口
UaDevice::instance()->getRtpPort());

Sptr sipSdp;
sipSdp.dynamicCast ( msg.getContentData( 0 ) );

if ( sipSdp != 0 )
{
call->setLocalSdp( new SipSdp( *sipSdp ) );
int tmp;
}
//保存Invite消息在UaCallInfo隊(duì)列中,以便在Ring Back狀態(tài)的時(shí)候可對(duì)狀態(tài)進(jìn)
//行檢測(cè),看其是否為當(dāng)前的INVITE詳見(jiàn)后續(xù)的OPStartRingBackTone中對(duì)
//getRingInvite的方法調(diào)用,在出現(xiàn)兩個(gè)INVITE(例如呼叫轉(zhuǎn)移或者是SDP不適配)
//等情況那么,如何做到選定合適的SDP。
call->setRingInvite( new InviteMsg( msg ) );

//發(fā)送INVITE,timerEvent在這里是用來(lái)做發(fā)送超時(shí)的檢測(cè),調(diào)用UaOperator::
//StarTimer開(kāi)始當(dāng)前的記時(shí),并且在時(shí)間TimeOut以前發(fā)送當(dāng)前的INVITE命令。

timerEvent->getSipStack()->sendAsync( msg );
call->setContactMsg(msg);
Sptr < UaStateMachine > stateMachine;
stateMachine.dynamicCast( event->getCallInfo()->getFeature() );
assert( stateMachine != 0 );
//轉(zhuǎn)入StateTrying狀態(tài)。
return stateMachine->findState( "StateTrying" );
}

3.2.7 進(jìn)入Trying狀態(tài):
  我們知道在這個(gè)狀態(tài)機(jī)中,發(fā)送出一個(gè)INVITE消息以后,我們將讓系統(tǒng)進(jìn)入等待遠(yuǎn)端發(fā)送Trying命令,首先我們來(lái)看進(jìn)入的操作狀態(tài):

3.2.7.1 OpStartTimer啟動(dòng)每個(gè)事件的定時(shí)器:
  addEntryOperator( new OpStartTimer );
  這里通過(guò)UaOperator::setTimer的方式來(lái)啟動(dòng)UaTimerEvent這個(gè)事件定時(shí)器,目的是設(shè)置后續(xù)每個(gè)事件的超時(shí)操作,喚起超時(shí)處理。

3.2.7.2 掛機(jī)事件的檢測(cè)機(jī)制
  addOperator( new OpOnHook );
  調(diào)動(dòng)一個(gè)線程來(lái)檢測(cè)掛機(jī)事件,這里不做累述;

3.2.7.3 OpStartRingbackTone向被叫進(jìn)行鈴聲回放。
  addOperator( new OpStartRingbackTone );
震鈴回放的實(shí)現(xiàn),也就是接收到對(duì)方回傳的180/183振鈴消息

OpStartRingbackTone::process( const Sptr < SipProxyEvent > event )
{
Sptr < SipMsg > sipMsg = sipEvent->getSipMsg();
Sptr < StatusMsg > msg;
msg.dynamicCast( sipMsg );
… …
Sptr < UaCallInfo > call;
call.dynamicCast( event->getCallInfo() );
//檢驗(yàn)消息狀態(tài)是否為180/183
if ( msg->getStatusLine().getStatusCode() != 180 &&
msg->getStatusLine().getStatusCode() != 183 )
{
… …
};
//消除定時(shí)
if ( cancelTimer(event) )
Sptr < Contact > contact = call->findContact( *msg );
… …
int status = contact->getStatus();
if ( status == 180 || status == 183 )
{
… …
}
bool remoteRingback = true;
contact->update( *msg );
//這里是確定是哪一個(gè)Invite消息得到的回應(yīng),正如在2.5所說(shuō)的,Invite消息發(fā)送//的時(shí)候保存Invite消息在UaCallInfo隊(duì)列中,以便在Ring Back狀態(tài)的時(shí)候可對(duì)
//狀態(tài)進(jìn)行檢測(cè),看其是否為當(dāng)前的INVITE,在出現(xiàn)兩個(gè)INVITE(例如呼叫轉(zhuǎn)移
//或者是SDP不適配)等情況那么,并且做到選定合適的SDP。
Sptr < SipSdp > localSdp;
//取得引起震鈴的INVITE消息
Sptr < InviteMsg > inviteMsg = call->getRingInvite();
//取出當(dāng)前的Contact字段中所引發(fā)的INVITE消息,在OpInviteUrl::process中會(huì)定義這//個(gè)當(dāng)前的SipCOntact字段。
InviteMsg invMsg = call->getContact()->getInviteMsg();
//兩者相比較,檢驗(yàn)是否引起震鈴的INVITE消息和當(dāng)前Contact字段中的INVITE消息
//是否相同
if ( *inviteMsg == invMsg )
{
//從UaCallInfo中取出相應(yīng)的SDP
localSdp = call->getLocalSdp();
}
else
{
… … //從UaCallInfo中取出相應(yīng)的SDP
localSdp = call->getLocal2Sdp();
}

int rtpPacketSize = UaConfiguration::instance()->getNetworkRtpRate();
//從被叫端回送的Ring消息中取得相應(yīng)的SDP,如果沒(méi)有的話,那么就不接收異地來(lái)的//振鈴。一般情況下,是沒(méi)有振鈴回送的,太花費(fèi)網(wǎng)絡(luò)資源,而且價(jià)值也不是很大。
Sptr remoteSdp;
remoteSdp.dynamicCast( sipMsg->getContentData(0) );
//remoteSdp=0的情況表示沒(méi)有給本地,所以
if( remoteSdp == 0 )
{
remoteRingback = false;
}
else
{
int rtpPacketSize = getRtpPacketSize(*remoteSdp);
if(rtpPacketSize > 0)
{
//設(shè)置鈴聲回送的RTP Packet數(shù)值
setRtpPacketSize(*localSdp, rtpPacketSize);
call->setRemoteSdp( new SipSdp( *remoteSdp ) );
}
else
{
remoteRingback = false;
}
}

Sptr < UaHardwareEvent > signal =
new UaHardwareEvent( UaDevice::getDeviceQueue() );
//如果需要鈴聲回送的話,那么在下面的部分就打開(kāi)RTP/RTCP通道,準(zhǔn)備接收遠(yuǎn)端的振鈴
if ( remoteRingback )
{
call->getContact()->setRemoteRingback( true );

signal->type = HardwareAudioType;
struct HardwareAudioRequest* request
= &(signal->signalOrRequest.request);
request->type = AudioStart//打開(kāi)RTP會(huì)話開(kāi)始回放遠(yuǎn)端鈴聲;稍后在OpACK中做詳細(xì)介紹
strcpy( request->remoteHost, "\0" );
request->remotePort = 0;
LocalScopeAllocator lo;
strcpy( request->localHost, localSdp->getConnAddress().getData(lo) );
request->localPort = localSdp->getRtpPort();
request->rtpPacketSize = rtpPacketSize;
}
else
{//本地響鈴
call->getContact()->setRemoteRingback( false );
signal->type = HardwareSignalType;
//這里調(diào)用 ResGwDevice::processSessionMsg-->
// ResGwDevice::provideSignal-->
// case DeviceSignalLocalRingbackStart:
// provideLocalRingbackStart()
// -->SoundCardDevice::provideLocalRingbackStart()
// provideTone( RingbackToneEmulation )來(lái)實(shí)現(xiàn)本地振鈴
signal->signalOrRequest.signal = DeviceSignalLocalRingbackStart;
}
UaDevice::getDeviceQueue()->add( signal );
return 0;
}

3.2.7.4 OpReDirect進(jìn)行重定向服務(wù)的操作
  addOperator( new OpReDirect );
  當(dāng)接收到重定向命令以后

a. 一個(gè)接收重定向的基本過(guò)程:
  在這里我們只著重闡述一下302在UA端的處理過(guò)程,至于其他的3XX系列的處理,和302基本上大同小異。


(點(diǎn)擊放大)


b.一個(gè)攜帶302狀態(tài)的消息:
302狀態(tài)消息:
sip-res: SIP/2.0 302 Moved Temporarily [192.168.26.180:5060->192.168.26.10:5060]
Header: Via: SIP/2.0/UDP 192.168.26.10:5060
Header: From: [sip:6711@192.168.26.10:5060]
Header: To: [sip:6715@192.168.26.180:5060]
Header: Call-ID: c2943000-ce262-1b5c2-2e323931@192.168.26.10
Header: CSeq: 100 INVITE
Header: Contact: [sip:6716@192.168.26.180:5060]
Header: Content-Length: 0
Header: CC-Redirect: [sip:6716@192.168.26.180:5060];redirreason=
unconditional;redir-counter=0;redir-limit=99
ACK消息:
sip-req: ACK sip:6715@192.168.26.180 SIP/2.0 [192.168.26.10:50373-
>192.168.26.180:5060]
Header: Via: SIP/2.0/UDP 192.168.26.10:5060
Header: From: sip:6711@192.168.26.10
Header: To: [sip:6715@192.168.26.180]
Header: Call-ID: c2943000-ce262-1b5c2-2e323931@192.168.26.10
Header: CSeq: 100 ACK
Header: Content-Length: 0
下一個(gè)INVITE消息:
sip-req: INVITE sip:6716@192.168.26.180:5060 SIP/2.0 [192.168.26.10:50373-
>192.168.26.180:5060]
Header: Via: SIP/2.0/UDP 192.168.26.10:5060
Header: From: sip:6711@192.168.26.10
Header: To: sip:6716@192.168.26.180:5060
Header: Call-ID: c2943000-de262-1b626-2e323931@192.168.26.10
Header: CSeq: 101 INVITE
Header: Expires: 180
Header: User-Agent: Cisco IP Phone/ Rev. 1/ SIP enabled
Header: Accept: application/sdp
Header: Contact: sip:6711@192.168.26.10:5060
Header: Content-Type: application/sdp
Header: Content-Length: 221

c.原代碼部分:
const Sptr < State >
OpReDirect::process( const Sptr < SipProxyEvent > event )
{
Sptr < SipEvent > sipEvent;
sipEvent.dynamicCast( event );
Sptr < SipMsg > sipMsg = sipEvent->getSipMsg();
assert( sipMsg != 0 );
Sptr < StatusMsg > msg;
msg.dynamicCast( sipMsg );
switch ( msg->getStatusLine().getStatusCode() )
{ //以下是幾種3XX系列的狀態(tài)碼:
case 300: // Multiple Choices
case 301: // Moved Premanently
case 302: // Moved Temporary
case 305: // Use Proxy
break;
default:
}

Sptr < UaCallInfo > call;
call.dynamicCast( event->getCallInfo() );
assert( call != 0 );
//根據(jù)From/To/Call ID在已經(jīng)已經(jīng)接收到的隊(duì)列中來(lái)尋找和302消息相匹配的INVITE消息
Sptr < Contact > origContact = call->findContact( *msg );
assert( origContact != 0 ); Sptr < Contact > origContact = call->findContact( *msg );

//按照上面狀態(tài)圖所指示的,在這里先創(chuàng)建一個(gè)回應(yīng)消息的ACK(僅僅是對(duì)于UA端)
//如何創(chuàng)建可以參看AckMsg::setAckDetails(const StatusMsg& statusMsg)
AckMsg ack( *msg );
Sptr< BaseUrl > baseUrl =
origContact->getInviteMsg().getRequestLine().getUrl();
assert( baseUrl != 0 );
if( baseUrl->getType() == TEL_URL )
… …
// Assume we have a SIP_URL
//這里根據(jù)從origContact中得到的URL值Request URL設(shè)置ACK并且發(fā)送;
Sptr< SipUrl > reqUrl;
reqUrl.dynamicCast( baseUrl );
assert( reqUrl != 0 );
SipRequestLine reqLine = ack.getRequestLine();
reqLine.setUrl( reqUrl );
ack.setRequestLine( reqLine );
sipEvent->getSipStack()->sendAsync( ack );
//我們知道在302消息的返回的Contact字段中包含了被叫移動(dòng)后的新地點(diǎn),如果發(fā)生多個(gè)
//移動(dòng)的話(有可能被叫在多個(gè)marshal上進(jìn)行了注冊(cè),需要進(jìn)行呼叫查詢)
for ( int i = 0; i < msg->getNumContact(); i++ )
{
SipContact sipContact = msg->getContact( i );
//以下是根據(jù)Contact返回的地址來(lái)創(chuàng)建新的INVITE消息
baseUrl = sipContact.getUrl();
assert( baseUrl != 0 );
Sptr< SipUrl > newUrl;
//從Contact取得被叫端的URL
newUrl.dynamicCast( baseUrl );
//從Contact取得被叫端的傳輸方式(TCP或者UDP)
Data tprt = newUrl->getTransportParam();
assert( newUrl != 0 );
if (newUrl->getUserParam() == "phone")
{ //從Cfg文件中取得代理服務(wù)器的名稱
string proxyServer = UaConfiguration::instance()->getProxyServer();
string::size_type colonPos = proxyServer.find( ":" );
//設(shè)置新的INVITE的代理服務(wù)器名稱(具體可以參看UA1001.cfg)
newUrl->setHost(proxyServer.substr( 0, colonPos ));
if ( colonPos < string::npos )
{//設(shè)置端口號(hào)碼
newUrl->setPort(
proxyServer.substr( proxyServer.rfind( ":" ) + 1 ));
}
else
{
newUrl->setPort("5060");
}
}

//根據(jù)上一個(gè)INVITE(也就是得到302消息的前面一個(gè)INVITE)消息,創(chuàng)建一個(gè)//新的INVITE。
InviteMsg inviteMsg( origContact->getInviteMsg(), newUrl );

//根據(jù)新的INVITE命令的Request line設(shè)置一個(gè)新的VIA
SipVia via = inviteMsg.getVia(0);

if(tprt == "tcp")
{
via.setTransport("TCP");
}
else
{
via.setTransport("UDP");
}
inviteMsg.removeVia(0);
inviteMsg.setVia(via, 0);

//check it is not a loop
bool isLoop = false;
//TODO Fix this
if ( !isLoop )
{
//把Call ID設(shè)置成和上一個(gè)INVITE相同。
inviteMsg.setCallId( sipMsg->getCallId() );
// CSeq要累加一
SipCSeq newCSeq = inviteMsg.getCSeq();
int cseq = sipMsg->getCSeq().getCSeqData().convertInt();
//設(shè)置新的Cseq
newCSeq.setCSeq( ++cseq );
inviteMsg.setCSeq( newCSeq );
… …
sipEvent->getSipStack()->sendAsync( inviteMsg );

// Create the new contact
發(fā)送
Sptr < Contact > contact = new Contact( inviteMsg );
//在UaCallInfo中增加Contact列表
// Add this to the contact list
call->addContact( contact );
//在UaCallInfo中的Contact列表設(shè)置當(dāng)前連接,以便為有可能的下一個(gè)302消息創(chuàng)造當(dāng)前的INVITE
call->setContact( contact );

// 更新UaCallInfo中的Ring-Invite消息
call->setRingInvite( new InviteMsg( inviteMsg ) );
}
}
return 0;
}

(未完待續(xù))

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

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

作者供稿 CTI論壇編輯


分類信息:     文摘