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

 

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

盧政 2003/08/05

3.開始一個呼叫和等待對方呼叫:

3.1 系統創建StateIdle狀態:

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

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

  我們可以注意到在UaBuilder::process中的 SendEvent提供了一種把系統收到的各種消息(在UaCallInfo中彙集的,無論是本地硬件消息或者是異地接收的消息)傳遞到狀態機(UaStateMachine)的方法, 並且分配調用當前狀態的處理過程。

… …
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狀態機的作用主要是在運行UaCallInfo(由前面詳細敘述的UaBuilder::process來裝入各種事件(SipProxyEvent))容器中的各種操作,上面這句就是調用Sate(各種State的基類)中的process方法。

  我們下面來歸納的看一下如何讓設備的狀態進入等待命令的idle狀態所遍列的類和方法:

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

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

  當用戶撥號完畢以後,這個時候會產生一個Dialing Complete事件,我們知道這個事件是由OpInviteURL產生,這個操作負責把INVITE消息發送到被叫端,並且讓系統陷入相應的狀態機中,這個時刻系統進入了Trying狀態

  一旦用戶進入Trying狀態,主叫將會等待被叫摘起話筒的過程,如果被叫摘機,那麼將會發送一個200的消息給主叫,主叫端的OpFarEndAnswered操作將會處理這個消息,並且讓系統進入InCall狀態,主叫/被叫之間將打開RTP/RTCP通道,開始通訊,當用戶掛機以後,進入OpTermiateCall狀態,並且互相發送Bye消息,中斷通話,系統最終返回Idle狀態。

3.2.1 OpStartCall主程序部分:
  這裏我們可以看出在idle狀態中首先經歷的是OpStartCall操作,這個操作目的是開始呼叫
OpStartCall::process( const Sptr < SipProxyEvent > event )

{
Sptr < UaDeviceEvent > deviceEvent;
deviceEvent.dynamicCast( event );
if ( deviceEvent == 0 )
{
return 0;
}
if ( deviceEvent->type != DeviceEventHookUp )//這裏是檢測是否開始進行呼叫
{
return 0;
}
//如果該過程接收到一個DeviceEventHookUp事件,那麼開始這個事件的處理過程
Sptr < UaStateMachine > stateMachine;
stateMachine.dynamicCast( event->getCallInfo()->getFeature() );
assert( stateMachine != 0 );
return stateMachine->findState( "StateDialing" );
//如果開始呼叫,那麼我們轉入StateDialing狀態
}

3.2.2 取得鍵盤的事件
  調用deviceThread->run()(SoundCardDevice::process())來實現對鍵盤事件的檢測,判斷是什麼事件--摘機(OffHook)掛機(OnHook),是否進行了撥號等等,在這個程序中所有的鍵盤全部解釋爲一個事件,這個程序很簡單,我就不在這裏做介紹了。

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

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

  這三個隊列中包含了幾乎全部的OPXXX系列操作符,用於管理和維護這些操作符,並且調動他們執行。

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);//調用各個OPXXX的process處理機
//制
if( newState == PROXY_DONE_WITH_EVENT )
{//在Marshall server的時候有時侯server端會給狀態機賦於這種狀態
//一旦進入這種狀態,那麼程序將處於退出當前狀態的執行階段,進入下一個階段
break;
}
else if( newState != PROXY_CONTINUE )
{ assert( nextState == PROXY_CONTINUE );
nextState = newState;
}
}
if( nextState != PROXY_CONTINUE )
{
processExit(event);//退出當前狀態,進入下一個狀態中
event->getCallInfo()->setState(nextState);
nextState->processEntry(event);//進入下一個狀態。
}
return ( nextState );
}

3.2.4 開始一個呼叫所經歷的各種操作(Operator):
  下圖表示了從摘機到通話完畢的各個狀態之間程序各種類之間的遷移過程,當然這個圖還僅僅是略圖,它只是表示了了一個終端簡單的主動呼叫,摘機,通話,掛機的過程,如果協議棧軟件應用於Marshal或者是Redirection Server的話,那麼採用的協議流程和下面又有一些不一樣了,以後會對這些做詳細介紹。

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


(點擊放大)


  我們根據SIP協議來定義一下一個發起呼叫或者是等待接收一個呼叫的狀態遷移過程:
(根據Vocal中提供的Ua Simple State Transfer狀態圖)


(點擊放大)


我們下面來對照這些狀態一個個的進行介紹:

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

  大家看一下,在deviceThread->run();(調用SoundcardDevice::hardwareMain(0))來檢檢測鍵盤事件,這個時候在下面程序中得到DeviceEventHookUp鍵盤事件,表示摘機,同時由在Uabuilder::Process()過程中的ProcessUaDeviceEvent中解釋該事件,並且進入StateIdle狀態。
… …
case 'a': // offhook
hookStateOffhook = true;
playDialTone = true;
event->type = DeviceEventHookUp;
break;
… …
這樣創建了StateIdle:
StateIdle::StateIdle()
{
addOperator( new OpStartCall );
addOperator( new OpRing );
addOperator( new OpOnHook ); // bizarre case
}

  按照上面的加入MyOperator隊列的方法將操作符加入隊列中去,並且運行各個操作符的Process方法:
  OpStartCall爲主動開始一個呼叫過程;
  OpRing爲被動得等待遠端的一個呼叫;

3.2.6 如何開始撥號並且開始一個呼叫:
  在OpStartCall中主要讓用戶的狀態機陷入下一個狀態中StateDialing這個操作是非常簡單的直接進入撥號狀態:

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

  這裏是加入操作隊列準備開始執行的操作符

3.2.6.1 OpStartDialTone:
  第一個操作符是OpStartDialTone顧名思義這個操作符是爲本地提供撥號音的操作,不過在這裏出現了一個本地設備事件的處理隊列:

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

處理本次事件的程序部分在聲卡描述這個類裏:處理順序如下:

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

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

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

3.2.6.3 OpStopDialTone主要是用於關掉撥號音。

3.2.6.4 OpInviteUrl目的是用在建立一個INVITE命令並且向被叫發送;
  我們現在來看一下他的程序結構,而在此之前我們來看一下一個普通的SIP呼叫/應答/的過程



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

SIP Headers
-----------------------------------------------------------------
sip-req: INVITE sip:[email protected] 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:[email protected]
Header: To: [sip:[email protected]]
Header: Call-ID: [email protected]
Header: CSeq: 100 INVITE
Header: Expires: 180
Header: User-Agent: Cisco IP Phone/ Rev. 1/ SIP enabled
Header: Accept: application/sdp
Header: Contact: sip:[email protected]: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中的內容創建一個合法的URL
toUrl = new SipUrl( digitCollector->getUrl() );

if ( dial_phone == digitCollector->getDialMethod() )
{
… …
toUrl->setUserValue( toUrl->getUserValue(), dial_phone );
}
... ...
//Proxy_Server表示的是被叫方代理服務器的地址
string proxyServer = UaConfiguration::instance()->getProxyServer();
//如果對方的SIP地址不完整的話,那麼就要用被叫方的代理服務器
if ( toUrl->getHost().length() <= 0 && proxyServer.length() )
{
NetworkAddress na( proxyServer );
//形成一個具體的被叫的SIP地址,並設置端口號碼,例如//[email protected]:5060
toUrl->setHost( na.getIpName() );
if( na.getPort() > 0 )
{

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

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

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

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

//設置路由表,首先取出當前的INVITE消息中的路由表,設置傳輸協議路由表中//的其他信息在setInviteDetails已經做了設定(主機名,端口名等)
SipVia via = msg.getVia();
msg.removeVia();
via.setTransport( UaConfiguration::instance()->getSipTransport() );
msg.setVia( via );
//設置Contact頭部,這裏如果是發送一個Invite的消息(開始呼叫一個遠端的主
//機)那麼採用的頭部的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,
//設置每個毫秒的RTP包的個數;
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狀態的時候可對狀態進
//行檢測,看其是否爲當前的INVITE詳見後續的OPStartRingBackTone中對
//getRingInvite的方法調用,在出現兩個INVITE(例如呼叫轉移或者是SDP不適配)
//等情況那麼,如何做到選定合適的SDP。
call->setRingInvite( new InviteMsg( msg ) );

//發送INVITE,timerEvent在這裏是用來做發送超時的檢測,調用UaOperator::
//StarTimer開始當前的記時,並且在時間TimeOut以前發送當前的INVITE命令。

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

3.2.7 進入Trying狀態:
  我們知道在這個狀態機中,發送出一個INVITE消息以後,我們將讓系統進入等待遠端發送Trying命令,首先我們來看進入的操作狀態:

3.2.7.1 OpStartTimer啓動每個事件的定時器:
  addEntryOperator( new OpStartTimer );
  這裏通過UaOperator::setTimer的方式來啓動UaTimerEvent這個事件定時器,目的是設置後續每個事件的超時操作,喚起超時處理。

3.2.7.2 掛機事件的檢測機制
  addOperator( new OpOnHook );
  調動一個線程來檢測掛機事件,這裏不做累述;

3.2.7.3 OpStartRingbackTone向被叫進行鈴聲回放。
  addOperator( new OpStartRingbackTone );
震鈴回放的實現,也就是接收到對方回傳的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() );
//檢驗消息狀態是否爲180/183
if ( msg->getStatusLine().getStatusCode() != 180 &&
msg->getStatusLine().getStatusCode() != 183 )
{
… …
};
//消除定時
if ( cancelTimer(event) )
Sptr < Contact > contact = call->findContact( *msg );
… …
int status = contact->getStatus();
if ( status == 180 || status == 183 )
{
… …
}
bool remoteRingback = true;
contact->update( *msg );
//這裏是確定是哪一個Invite消息得到的迴應,正如在2.5所說的,Invite消息發送//的時候保存Invite消息在UaCallInfo隊列中,以便在Ring Back狀態的時候可對
//狀態進行檢測,看其是否爲當前的INVITE,在出現兩個INVITE(例如呼叫轉移
//或者是SDP不適配)等情況那麼,並且做到選定合適的SDP。
Sptr < SipSdp > localSdp;
//取得引起震鈴的INVITE消息
Sptr < InviteMsg > inviteMsg = call->getRingInvite();
//取出當前的Contact字段中所引發的INVITE消息,在OpInviteUrl::process中會定義這//個當前的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,如果沒有的話,那麼就不接收異地來的//振鈴。一般情況下,是沒有振鈴回送的,太花費網絡資源,而且價值也不是很大。
Sptr remoteSdp;
remoteSdp.dynamicCast( sipMsg->getContentData(0) );
//remoteSdp=0的情況表示沒有給本地,所以
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() );
//如果需要鈴聲回送的話,那麼在下面的部分就打開RTP/RTCP通道,準備接收遠端的振鈴
if ( remoteRingback )
{
call->getContact()->setRemoteRingback( true );

signal->type = HardwareAudioType;
struct HardwareAudioRequest* request
= &(signal->signalOrRequest.request);
request->type = AudioStart//打開RTP會話開始回放遠端鈴聲;稍後在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 )來實現本地振鈴
signal->signalOrRequest.signal = DeviceSignalLocalRingbackStart;
}
UaDevice::getDeviceQueue()->add( signal );
return 0;
}

3.2.7.4 OpReDirect進行重定向服務的操作
  addOperator( new OpReDirect );
  當接收到重定向命令以後

a. 一個接收重定向的基本過程:
  在這裏我們只着重闡述一下302在UA端的處理過程,至於其他的3XX系列的處理,和302基本上大同小異。


(點擊放大)


b.一個攜帶302狀態的消息:
302狀態消息:
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:[email protected]:5060]
Header: To: [sip:[email protected]:5060]
Header: Call-ID: [email protected]
Header: CSeq: 100 INVITE
Header: Contact: [sip:[email protected]:5060]
Header: Content-Length: 0
Header: CC-Redirect: [sip:[email protected]:5060];redirreason=
unconditional;redir-counter=0;redir-limit=99
ACK消息:
sip-req: ACK sip:[email protected] 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:[email protected]
Header: To: [sip:[email protected]]
Header: Call-ID: [email protected]
Header: CSeq: 100 ACK
Header: Content-Length: 0
下一個INVITE消息:
sip-req: INVITE sip:[email protected]: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:[email protected]
Header: To: sip:[email protected]:5060
Header: Call-ID: [email protected]
Header: CSeq: 101 INVITE
Header: Expires: 180
Header: User-Agent: Cisco IP Phone/ Rev. 1/ SIP enabled
Header: Accept: application/sdp
Header: Contact: sip:[email protected]: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系列的狀態碼:
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在已經已經接收到的隊列中來尋找和302消息相匹配的INVITE消息
Sptr < Contact > origContact = call->findContact( *msg );
assert( origContact != 0 ); Sptr < Contact > origContact = call->findContact( *msg );

//按照上面狀態圖所指示的,在這裏先創建一個迴應消息的ACK(僅僅是對於UA端)
//如何創建可以參看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並且發送;
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字段中包含了被叫移動後的新地點,如果發生多個
//移動的話(有可能被叫在多個marshal上進行了註冊,需要進行呼叫查詢)
for ( int i = 0; i < msg->getNumContact(); i++ )
{
SipContact sipContact = msg->getContact( i );
//以下是根據Contact返回的地址來創建新的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文件中取得代理服務器的名稱
string proxyServer = UaConfiguration::instance()->getProxyServer();
string::size_type colonPos = proxyServer.find( ":" );
//設置新的INVITE的代理服務器名稱(具體可以參看UA1001.cfg)
newUrl->setHost(proxyServer.substr( 0, colonPos ));
if ( colonPos < string::npos )
{//設置端口號碼
newUrl->setPort(
proxyServer.substr( proxyServer.rfind( ":" ) + 1 ));
}
else
{
newUrl->setPort("5060");
}
}

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

//根據新的INVITE命令的Request line設置一個新的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設置成和上一個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
發送
Sptr < Contact > contact = new Contact( inviteMsg );
//在UaCallInfo中增加Contact列表
// Add this to the contact list
call->addContact( contact );
//在UaCallInfo中的Contact列表設置當前連接,以便爲有可能的下一個302消息創造當前的INVITE
call->setContact( contact );

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

(未完待續)
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章