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

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

盧政 2003/08/05

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

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

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

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

  我們可以注意到在UaBuilder::process中的 SendEvent提供了一種把系統收到的各種消息(在UaCallInfo中匯集的,無(wú)論是本地硬件消息或者是異地接收的消息)傳遞到狀態(tài)機(UaStateMachine)的方法, 并且分配調用當前狀態(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)機的作用主要是在運行UaCallInfo(由前面詳細敘述的UaBuilder::process來(lái)裝入各種事件(SipProxyEvent))容器中的各種操作,上面這句就是調用Sate(各種State的基類(lèi))中的process方法。

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

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

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

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

  一旦用戶(hù)進(jìn)入Trying狀態(tài),主叫將會(huì )等待被叫摘起話(huà)筒的過(guò)程,如果被叫摘機,那么將會(huì )發(fā)送一個(gè)200的消息給主叫,主叫端的OpFarEndAnswered操作將會(huì )處理這個(gè)消息,并且讓系統進(jìn)入InCall狀態(tài),主叫/被叫之間將打開(kāi)RTP/RTCP通道,開(kāi)始通訊,當用戶(hù)掛機以后,進(jìn)入OpTermiateCall狀態(tài),并且互相發(fā)送Bye消息,中斷通話(huà),系統最終返回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 )//這里是檢測是否開(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)始呼叫,那么我們轉入StateDialing狀態(tài)
}

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

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

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

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

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);//調用各個(gè)OPXXX的process處理機
//制
if( newState == PROXY_DONE_WITH_EVENT )
{//在Marshall server的時(shí)候有時(shí)侯server端會(huì )給狀態(tài)機賦于這種狀態(tài)
//一旦進(jìn)入這種狀態(tài),那么程序將處于退出當前狀態(tài)的執行階段,進(jìn)入下一個(gè)階段
break;
}
else if( newState != PROXY_CONTINUE )
{ assert( nextState == PROXY_CONTINUE );
nextState = newState;
}
}
if( nextState != PROXY_CONTINUE )
{
processExit(event);//退出當前狀態(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):
  下圖表示了從摘機到通話(huà)完畢的各個(gè)狀態(tài)之間程序各種類(lèi)之間的遷移過(guò)程,當然這個(gè)圖還僅僅是略圖,它只是表示了了一個(gè)終端簡(jiǎn)單的主動(dòng)呼叫,摘機,通話(huà),掛機的過(guò)程,如果協(xié)議棧軟件應用于Marshal或者是Redirection Server的話(huà),那么采用的協(xié)議流程和下面又有一些不一樣了,以后會(huì )對這些做詳細介紹。

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


(點(diǎn)擊放大)


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


(點(diǎn)擊放大)


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

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

  大家看一下,在deviceThread->run();(調用SoundcardDevice::hardwareMain(0))來(lái)檢檢測鍵盤(pán)事件,這個(gè)時(shí)候在下面程序中得到DeviceEventHookUp鍵盤(pán)事件,表示摘機,同時(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隊列的方法將操作符加入隊列中去,并且運行各個(gè)操作符的Process方法:
  OpStartCall為主動(dòng)開(kāi)始一個(gè)呼叫過(guò)程;
  OpRing為被動(dòng)得等待遠端的一個(gè)呼叫;

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

  這里是加入操作隊列準備開(kāi)始執行的操作符

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

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

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

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

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

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

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

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



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

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;
}

... ...
//根據DigitCollector中的內容創(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();
//如果對方的SIP地址不完整的話(huà),那么就要用被叫方的代理服務(wù)器
if ( toUrl->getHost().length() <= 0 && proxyServer.length() )
{
NetworkAddress na( proxyServer );
//形成一個(gè)具體的被叫的SIP地址,并設置端口號碼,例如//93831073@192.168.36.180:5060
toUrl->setHost( na.getIpName() );
if( na.getPort() > 0 )
{

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

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

//定義本地接收異地的SIP消息的接收端口;我們根據Cfg文件暫時(shí)定為5000
string sipPort = UaConfiguration::instance()->getLocalSipPort();
//根據目的SIP地址和接收端口構造一個(gè)SIP的Invite命令,同時(shí)通過(guò)函數:
//setInviteDetails構造一個(gè)Via頭的部分內容,以及From,to,Cseq, contact
InviteMsg msg( toUrl, atoi( sipPort.c_str() ) );
//設置Call-ID
msg.setCallId( *(UaDevice::instance()->getCallId()) );
//設置請求頭啟始行,例如: sip-req: INVITE sip:93831073@192.168.36.180 SIP/2.0 [192.168.6.20:50753->192.168.36.180:5060]
//如果存在代理服務(wù)器的話(huà),那么根據RFC3261中的7.1項目來(lái)構造請求頭部啟始//行,這里的可能只包括9383107的IP電話(huà)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 );
//設置被叫端的代理服務(wù)器SIp地址以及端口號
reqUrl->setHost( proxyUrl->getHost() );
reqUrl->setPort( proxyUrl->getPort() );

if(UaConfiguration::instance()->getSipTransport() == "TCP")
{
reqUrl->setTransportParam( Data("tcp"));
}
reqLine.setUrl( reqUrl );
//最后完整的設置一個(gè)被叫端的SIP地址到請求頭部。
msg.setRequestLine( reqLine );
}
//設置From頭部
SipFrom from = msg.getFrom();
//設置顯示的用戶(hù)名到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 );
//設置用戶(hù)名
fromUrl->setUserValue( Data( UaConfiguration::instance()->getUserName() ),
"phone" );
from.setUrl( fromUrl );
msg.setFrom( from );

//設置路由表,首先取出當前的INVITE消息中的路由表,設置傳輸協(xié)議路由表中//的其他信息在setInviteDetails已經(jīng)做了設定(主機名,端口名等)
SipVia via = msg.getVia();
msg.removeVia();
via.setTransport( UaConfiguration::instance()->getSipTransport() );
msg.setVia( via );
//設置Contact頭部,這里如果是發(fā)送一個(gè)Invite的消息(開(kāi)始呼叫一個(gè)遠端的主
//機)那么采用的頭部的Contact地址當然本主機的地址。
// 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 );
//設置SDP
addSdpToMsg(msg,
//設置每個(gè)毫秒的RTP包的個(gè)數;
UaConfiguration::instance()->getNetworkRtpRate(),
//設置RTP的端口
UaDevice::instance()->getRtpPort());

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

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

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

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

3.2.7 進(jìn)入Trying狀態(tài):
  我們知道在這個(gè)狀態(tài)機中,發(fā)送出一個(gè)INVITE消息以后,我們將讓系統進(jì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í)器,目的是設置后續每個(gè)事件的超時(shí)操作,喚起超時(shí)處理。

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

3.2.7.3 OpStartRingbackTone向被叫進(jìn)行鈴聲回放。
  addOperator( new OpStartRingbackTone );
震鈴回放的實(shí)現,也就是接收到對方回傳的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() );
//檢驗消息狀態(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消息得到的回應,正如在2.5所說(shuō)的,Invite消息發(fā)送//的時(shí)候保存Invite消息在UaCallInfo隊列中,以便在Ring Back狀態(tài)的時(shí)候可對
//狀態(tài)進(jìn)行檢測,看其是否為當前的INVITE,在出現兩個(gè)INVITE(例如呼叫轉移
//或者是SDP不適配)等情況那么,并且做到選定合適的SDP。
Sptr < SipSdp > localSdp;
//取得引起震鈴的INVITE消息
Sptr < InviteMsg > inviteMsg = call->getRingInvite();
//取出當前的Contact字段中所引發(fā)的INVITE消息,在OpInviteUrl::process中會(huì )定義這//個(gè)當前的SipCOntact字段。
InviteMsg invMsg = call->getContact()->getInviteMsg();
//兩者相比較,檢驗是否引起震鈴的INVITE消息和當前Contact字段中的INVITE消息
//是否相同
if ( *inviteMsg == invMsg )
{
//從UaCallInfo中取出相應的SDP
localSdp = call->getLocalSdp();
}
else
{
… … //從UaCallInfo中取出相應的SDP
localSdp = call->getLocal2Sdp();
}

int rtpPacketSize = UaConfiguration::instance()->getNetworkRtpRate();
//從被叫端回送的Ring消息中取得相應的SDP,如果沒(méi)有的話(huà),那么就不接收異地來(lái)的//振鈴。一般情況下,是沒(mé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)
{
//設置鈴聲回送的RTP Packet數值
setRtpPacketSize(*localSdp, rtpPacketSize);
call->setRemoteSdp( new SipSdp( *remoteSdp ) );
}
else
{
remoteRingback = false;
}
}

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

signal->type = HardwareAudioType;
struct HardwareAudioRequest* request
= &(signal->signalOrRequest.request);
request->type = AudioStart//打開(kāi)RTP會(huì )話(huà)開(kāi)始回放遠端鈴聲;稍后在OpACK中做詳細介紹
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;
//這里調用 ResGwDevice::processSessionMsg-->
// ResGwDevice::provideSignal-->
// case DeviceSignalLocalRingbackStart:
// provideLocalRingbackStart()
// -->SoundCardDevice::provideLocalRingbackStart()
// provideTone( RingbackToneEmulation )來(lái)實(shí)現本地振鈴
signal->signalOrRequest.signal = DeviceSignalLocalRingbackStart;
}
UaDevice::getDeviceQueue()->add( signal );
return 0;
}

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

a. 一個(gè)接收重定向的基本過(guò)程:
  在這里我們只著(zhù)重闡述一下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 );
//根據From/To/Call ID在已經(jīng)已經(jīng)接收到的隊列中來(lái)尋找和302消息相匹配的INVITE消息
Sptr < Contact > origContact = call->findContact( *msg );
assert( origContact != 0 ); Sptr < Contact > origContact = call->findContact( *msg );

//按照上面狀態(tài)圖所指示的,在這里先創(chuàng )建一個(gè)回應消息的ACK(僅僅是對于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
//這里根據從origContact中得到的URL值Request URL設置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)的話(huà)(有可能被叫在多個(gè)marshal上進(jìn)行了注冊,需要進(jìn)行呼叫查詢(xún))
for ( int i = 0; i < msg->getNumContact(); i++ )
{
SipContact sipContact = msg->getContact( i );
//以下是根據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ù)器的名稱(chēng)
string proxyServer = UaConfiguration::instance()->getProxyServer();
string::size_type colonPos = proxyServer.find( ":" );
//設置新的INVITE的代理服務(wù)器名稱(chēng)(具體可以參看UA1001.cfg)
newUrl->setHost(proxyServer.substr( 0, colonPos ));
if ( colonPos < string::npos )
{//設置端口號碼
newUrl->setPort(
proxyServer.substr( proxyServer.rfind( ":" ) + 1 ));
}
else
{
newUrl->setPort("5060");
}
}

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

//根據新的INVITE命令的Request line設置一個(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設置成和上一個(gè)INVITE相同。
inviteMsg.setCallId( sipMsg->getCallId() );
// CSeq要累加一
SipCSeq newCSeq = inviteMsg.getCSeq();
int cseq = sipMsg->getCSeq().getCSeqData().convertInt();
//設置新的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列表設置當前連接,以便為有可能的下一個(gè)302消息創(chuàng )造當前的INVITE
call->setContact( contact );

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

(未完待續)

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

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

作者供稿 CTI論壇編輯


分類(lèi)信息:     文摘
亚洲精品网站在线观看不卡无广告,国产a不卡片精品免费观看,欧美亚洲一区二区三区在线,国产一区二区三区日韩 宁南县| 丰镇市| 昌图县| 北辰区| 江都市| 巨鹿县| 淅川县| 岢岚县| 城步| 洱源县| 佛山市| 屯门区| 大洼县| 治县。| 天水市| 邹平县| 利川市| 华宁县| 百色市| 巴中市| 昭觉县| 武隆县| 紫金县| 郎溪县| 灵丘县| 林甸县| 应用必备| 阳曲县| 沾益县| 宁乡县| 浦江县| 雷波县| 鄂伦春自治旗| 宁南县| 东宁县| 中西区| 南平市| 中宁县| 益阳市| 布尔津县| 林甸县| http://444 http://444 http://444 http://444 http://444 http://444