在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: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中的内容创建一个合法的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地址,并设置端口号码,例如//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文件暂时定为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:93831073@192.168.36.180 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: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
下一个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系列的状态码:
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;
}

(未完待续)

作者联系方法:lu_zheng@21cn.com

在Vovida的基础上实现自己的SIP协议栈(四)

作者供稿 CTI论坛编辑