在Vovida的基礎(chǔ)上實現(xiàn)自己的SIP協(xié)議棧(三)
在Vovida的基礎(chǔ)上實現(xiàn)自己的SIP協(xié)議棧(四)
盧政 2003/08/06
3.2.7.5 授權(quán)檢查
a.示意圖和信令部分:

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
407消息:
sip-res: SIP/2.0 407 Proxy Authentication Required
->[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.180:5060]
Header: To: [sip:6711@192.168.26.180:5060]
Header: Call-ID: c2943000-1e262-513-2e323931@192.168.26.10
Header: CSeq: 100 REGISTER
Header: WWW-Authenticate: Digest
realm=vovida.com,algorithm=MD5,nonce=966645751
Header: Content-Length: 0
下一個INVITE消息:
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: 101 INVITE
Header: Authorization: Digest
username="6711",realm="vovida.com",uri="sip:192.168.26.180",response="fee2efef60a99b4576c
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
b.程序主體部分:
addOperator (new OpReInviteAuthenticated)
這個新的操作是指在接收到407(需要授權(quán)驗證的Inivte)回應(yīng)消息以后系統(tǒng)的操作。
const Sptr < State >
OpReInviteAuthenticated::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() )
{
case 407: // Proxy Authentication Required
break;
default:
{
return 0;
}
}
int cseq = sipMsg->getCSeq().getCSeqData().convertInt();
Sptr < UaCallInfo > call;
call.dynamicCast( event->getCallInfo() );
… …
//和上面Redirect消息的處理機(jī)制一樣,找到源頭的INVITE并且向Marshal Server 回送ACK
Sptr < Contact > origContact = call->findContact( *msg );
assert( origContact != 0 );
AckMsg ack( *msg );
Sptr< BaseUrl > baseUrl =
origContact->getInviteMsg().getRequestLine().getUrl();
assert( baseUrl != 0 );
if( baseUrl->getType() == TEL_URL )
{
assert( 0 );
}
// Assume we have a SIP_URL
Sptr< SipUrl > reqUrl;
reqUrl.dynamicCast( baseUrl );
assert( reqUrl != 0 );
SipRequestLine reqLine = ack.getRequestLine();
reqLine.setUrl( reqUrl );
ack.setRequestLine( reqLine );
sipEvent->getSipStack()->sendAsync( ack );
//如果前面已經(jīng)接收到了一個407的狀態(tài)回應(yīng),那么我們就認(rèn)為密碼錯誤,進(jìn)入校驗循環(huán)直接跳出
if( origContact->getStatus() == 407 )
{
… … return 0;
}
Sptr inviteMsg = new InviteMsg( origContact->getInviteMsg() );
//這個下面的過程和上面的Redirect非常相似,重新創(chuàng)建一個INVITE命令我們可以參考
//Redirect的下面的處理部分,這里不做累述
inviteMsg->setCallId( sipMsg->getCallId() );
newCSeq.setCSeq( ++cseq );
inviteMsg->setCSeq( newCSeq );
//建立授權(quán),把密碼和用戶名稱通過authenticateMessage
if(authenticateMessage( *msg,
*inviteMsg,
UaConfiguration::instance()->getUserName(),
UaConfiguration::instance()->getPassword() ))
{
// went OK
sipEvent->getSipStack()->sendAsync( *inviteMsg );
}
else
{
// xxx there was an authentication problem, so we need to abort out
}
call->setContactMsg(inviteMsg);
// Update the Ring INVITE message
call->setRingInvite( new InviteMsg( *inviteMsg ) );
return 0;
}
c.授權(quán)消息檢查的各個子項目:
這里是指的鑒別授權(quán)的方式我們可以看一下在RFC 2543中如何定義的請參看RFC 2543的6.42 WWW-Authenticate的介紹):
在回應(yīng)的狀態(tài)消息中包含有401/407(Unauthentication)狀態(tài)的時候,表示需要進(jìn)行授權(quán)檢查,在回應(yīng)頭部包含有一個或者多個的由密鑰計算方法和密鑰參數(shù)組成的域例如:
Header: WWW-Authenticate: Digest//表示采用密鑰檢驗的算法
realm=vovida.com,//主叫和被叫方所使用的信任值,也就是授權(quán)值,表示主叫/注冊服務(wù)器共享密碼//域(就是所有的用戶在這個區(qū)域內(nèi)采用相同的密碼,相當(dāng)于公鑰)。
algorithm=MD5,//算法
nonce=966645751//407/401消息回應(yīng)的鑒別符,防止重放攻擊。

主叫接收到401/407消息以后,回應(yīng)的下一個命令(例如INVITE)中必須包含鑒權(quán)因子在里面:
Header: Authorization: Digest
username="6711",realm="vovida.com",uri="sip:192.168.26.180",response="fee2efef60a99b4576c
上面幾個因子我就不詳細(xì)說明意思了,大家應(yīng)該一看意思就可以明白:
那么我們根據(jù)RFC2543中14.3的Digest鑒別方式可以看出:最后在主叫部分的信任狀(Respone)如何實現(xiàn):
response=username⊕realm⊕uri⊕realm⊕Password⊕Username
最后在注冊服務(wù)器端根據(jù)上述的算式重算response,并且判斷結(jié)果主叫的response是否相等。
具體的程序按照下面的方式層次調(diào)用,具體代碼不做詳細(xì)的分析:
A.主叫端(客戶端)
1.a(chǎn)uthenticateMessage( *msg, *inviteMsg,
UaConfiguration::instance()->getUserName(),
UaConfiguration::instance()->getPassword() )
... ... ...
void addWwwAuthorization(const StatusMsg& errorMsg, SipCommand& cmdMsg,
Data username,Data password)
... ... ...
void addAuthorization(const StatusMsg& errorMsg,
SipCommand& cmdMsg,
Data username,
Data password,
bool useProxyAuthenticate)
void SipCommand::setAuthDigest(const Data& nonce, const Data& user,
const Data& pwd, const Data& method,
const Data& realm, const Data& requestURI,
const Data& qop, const Data& cnonce,
const Data& alg, const Data& noncecount,
const Data& opaque)
{
Sptr authorization;
myHeaderList.getParsedHeader(authorization, SIP_AUTHORIZATION_HDR);
SipDigest sipDigest;
//核心的MD5加密算法,主要計算出response
Data response = sipDigest.form_SIPdigest(nonce, user, pwd, method,
requestURI, realm, qop, cnonce, alg, noncecount);
cpLog(LOG_DEBUG_STACK, "setAuthDigest::Response = %s\n",
response.logData());
//set this as response in authorization.
authorization->setAuthScheme(AUTH_DIGEST);
以下部分是設(shè)定授權(quán)的頭部的各個域的值
if(user != "")
{
authorization->setTokenDetails("username", user); //1
}
if(realm != "")
{
authorization->setTokenDetails("realm", realm); //2
}
if(nonce != "")
{
authorization->setTokenDetails("nonce", nonce); //3
}
if(response != "")
{
authorization->setTokenDetails("response", response); //4
}
if(qop != "")
{
authorization->setTokenDetails("qop", qop); //5
}
if(requestURI != "")
{
authorization->setTokenDetails("uri", requestURI); //6
}
if(cnonce != "")
{
authorization->setTokenDetails("cnonce", cnonce); //7
}
if(noncecount != "")
{
authorization->setTokenDetails("nc", noncecount); //8
}
if(opaque != "")
{
authorization->setTokenDetails("opaque", opaque); //9
}
if(alg != "")
{
authorization->setTokenDetails("algorithm", alg); // 10
}
}
Data SipDigest::form_SIPdigest( const Data& nonce,
const Data& user,
const Data& pwd,
const Data& method,
const Data& requestURI,
const Data& realm,
const Data& qop,
const Data& cnonce,
const Data& alg,
const Data& noncecount)
B.注冊服務(wù)器端(Marshal Server)授權(quán)鑒定(不列舉原代碼只簡單介紹過程)

3.2.7.6 OpFarEndAnswered處理接收到的OK回應(yīng)
addOperator( new OpFarEndAnswered )
一個標(biāo)準(zhǔn)的OK回應(yīng)的構(gòu)成:
SIP Headers
-----------------------------------------------------------------
sip-res: SIP/2.0 200 OK [192.168.36.180:5060->192.168.6.21:5060]
Header: Via: SIP/2.0/UDP 192.168.6.21:5060
Header: From: [sip:5121@192.168.6.21:5060]
Header: To: [sip:5120@192.168.36.180:5060];tag=c29430002e0620-0
Header: Call-ID: c2943000-e0563-2a1ce-2e323931@192.168.6.21
Header: CSeq: 100 INVITE
Header: Contact: [sip:5120@192.168.6.20:5060]
Header: Record-Route:
[sip:5120@192.168.36.180:5060;maddr=192.168.36.180],
[sip:5120@192.168.36.180:5060;maddr=1]<92.168.36.180>
Header: Server: Cisco IP Phone/ Rev. 1/ SIP enabled
Header: Content-Type: application/sdp
Header: Content-Length: 218
-----------------------------------------------------------------
SDP Headers
-----------------------------------------------------------------
Header: v=0
Header: o=CiscoSystemsSIP-IPPhone-UserAgent 13045 2886 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 30658 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 >
OpFarEndAnswered::process( const Sptr < SipProxyEvent > event )
{
Sptr < SipEvent > sipEvent;
sipEvent.dynamicCast( event );
Sptr < SipMsg > sipMsg = sipEvent->getSipMsg();
Sptr < StatusMsg > msg;
msg.dynamicCast( sipMsg );
if(msg->getStatusLine().getStatusCode() > 200)
{
return 0;
}
Sptr < UaCallInfo > call;
call.dynamicCast( event->getCallInfo() );
assert( call != 0 );
Sptr findContact = call->findContact( *msg );
//在OpInviteUrl和OpReInviteAuthenticated 需要有Invite命令發(fā)送的程序中有用到//call->setContactMsg來設(shè)定當(dāng)前的Contact項目;在這里通過call->getContact的方法來
//進(jìn)行對比,以檢驗是否和OK中返回的Contact項目相符合,如果不符合則表示必定不
//是當(dāng)前的這個INVITE命令返回的內(nèi)容。
Sptr getContact = call->getContact();
if ( *( call->findContact( *msg ) ) != *( call->getContact() ) )
{
return 0;
}
call->getContact()->update( *msg);
int status = msg->getStatusLine().getStatusCode();
if ( status >= 200 )
{ 發(fā)送ACK給被叫端
AckMsg ack( *msg );
sipEvent->getSipStack()->sendAsync( ack );
}
if ( status != 200 )
{
return 0; // Done
}
//如果返回的Contact有兩個聯(lián)絡(luò)地址,那么建立新的路由,并且將該新路由項目保存
//在UaCallInfo中
call->setCallerRoute1List( msg->getrecordrouteList() );
int numContact = msg->getNumContact();
if ( numContact )
{
SipContact contact = msg->getContact( numContact - 1 );
Sptr < SipRoute > route = new SipRoute;
route->setUrl( contact.getUrl() );
call->addRoute1( route );
}
Sptr sdp;
sdp.dynamicCast ( sipMsg->getContentData(0) );
… …
//在UaCallInfo中保存遠(yuǎn)端回傳的SDP
call->setRemoteSdp( new SipSdp( *sdp ) );
Sptr < SipSdp > localSdp = call->getLocalSdp();
Sptr < SipSdp > remoteSdp = call->getRemoteSdp();
//RSVP消息的發(fā)送和接受設(shè)置(下面做詳細(xì)介紹)。
rsvpFarEndAnswered(localSdp, remoteSdp);
Sptr < UaStateMachine > stateMachine;
stateMachine.dynamicCast( event->getCallInfo()->getFeature() );
assert( stateMachine != 0 );
//轉(zhuǎn)入SateInCall狀態(tài)
return stateMachine->findState( "StateInCall" );
}
3.2.7.7在Vocal中如何實現(xiàn)RSVP資源預(yù)留協(xié)議:
RSVP資源預(yù)留協(xié)議的具體內(nèi)容我們在這里就不做詳細(xì)的介紹了,如果對這個還不了解的可以看一下RFC 1633中對RSVP的具體定義,另外在draft-ietf-rsvp-rapi-01.txt中定義關(guān)于RSVP相關(guān)的基本API函數(shù)調(diào)用。
RSVP一般工作機(jī)理如下圖所示:

發(fā)送方發(fā)送PATH消息,消息中包含有數(shù)據(jù)業(yè)務(wù)特征,該消息沿所選的路徑傳送,沿途的路由器按照PATH準(zhǔn)備路由資源,接收方接收到PATH消息以后,根據(jù)業(yè)務(wù)特征和所需要的QOS計算出所需要的資源,回送RESV消息,消息中包含的參數(shù)就包括請求預(yù)留的帶寬,延PATH的原路途返回,沿途的路由器接收到RESV操作后才執(zhí)行資源預(yù)留操作。發(fā)送方接收到RESV消息以后才發(fā)送用戶數(shù)據(jù)。
一.在H.323協(xié)議中如何實現(xiàn)RSVP功能:
對于一個H.323或者是SIP的多媒體通訊系統(tǒng)而言,為了保證實時通訊的質(zhì)量,一般來說采用了很多方面來保證QOS,對于H.323來說方式?jīng)]有SIP那樣靈活,在H.323v3版本采用了一些幾種方式來增強QOS保證:
a. 增強的RAS過程,在ARQ中指明了是否具備資源預(yù)留能力;
b. 增強的能力交換過程,收發(fā)端點都具備RSVP功能,通過能力交換過程可以雙方具備RSVP能力(RSVP屬于能力集合的一個部分),在OpenLogicalChannel原語中定義了一個參數(shù)qOSCapability來表示;
c. 增強的邏輯信道能力在邏輯信道打開過程中包含Path和Resv兩個過程
下面我們用圖來表示邏輯信道的打開過程和資源預(yù)留過程:

1. 發(fā)送端點向接受端點發(fā)送OpenLogicalChannel消息在qOSCapability中標(biāo)明該信道的RSVP參數(shù)和綜合業(yè)務(wù)類別。
2. 接收端點創(chuàng)建RSVP會話(調(diào)用Rapi_session API)向發(fā)送端點發(fā)送OpenLogicalChannel Ack。
3. 在OpenLogical Ack中包含F(xiàn)lowControl=0,抑制當(dāng)前的媒體數(shù)據(jù)流。
4. 4和5表示發(fā)送端點和接收端點執(zhí)行RSVP過程。
5. 接收端點接收到ResvConfirm以后知道預(yù)留成功。
6. FlowControl為最大的比特率,當(dāng)前的媒體數(shù)據(jù)流為最大。
要注意的一點是由于通訊是雙向的實際上述的過程發(fā)送和接收方完全要對掉所以上述的過程要執(zhí)行兩遍。
二.在SIP中實現(xiàn)RSVP功能:
Vocal的SIP協(xié)議棧軟件中提供了一個非常簡便的實現(xiàn)RSVP的方式,當(dāng)然按照這個方式實現(xiàn)RSVP是相當(dāng)?shù)牟怀墒斓,很多參量在?yīng)用程序都沒有反饋并且處理,僅僅是在路由器之間相互的匯報,不過這個簡單的方式實現(xiàn)RSVP的構(gòu)架,所以仍然有一定的使用價值。
一般說來在SIP中實現(xiàn)RSVP的步驟如下:

(點擊放大)
在上圖中實線的部分是SIP命令,虛線部分是RSVP消息
Vocal中的RSVP實現(xiàn)過程:
1. 首先是主叫部分發(fā)送INVITE命令,我們知道命令中包含有主叫的會話描述(這里我們稱為Remote SDP);
2. 被叫部分此時處于OpRing的狀態(tài)中接收到主叫的INVITE消息以后,根據(jù)主叫的INVITE消息和主叫的SDP,得到主叫的地址和主叫的RSVP端口(主叫的RTP端口);被叫調(diào)用setupRsvp子程序發(fā)送包含有數(shù)據(jù)流標(biāo)識和數(shù)據(jù)業(yè)務(wù)流特征的PATH消息到主叫,具體發(fā)送的業(yè)務(wù)流Tspec特征如下:
//Sender Tspec的定義:
rapi_tspec_t *tspec_ptr = &(snd_tspec);
qos_tspecx_t *qos_tspec = &(tspec_ptr->tspecbody_qosx);
qos_tspec->spec_type = QOS_TSPEC;//發(fā)送方業(yè)務(wù)流特征標(biāo)示
qos_tspec->xtspec_r = 10000;// Token Rate (B/s)//業(yè)務(wù)流量
qos_tspec->xtspec_b = 200;// Token Bucket Depth (B)//標(biāo)記存儲桶寬度
qos_tspec->xtspec_p = 10000;// Peak Data Rate (B/s)//突發(fā)流量
qos_tspec->xtspec_m = 200;// Minimum policed unit//
qos_tspec->xtspec_M = 200; /* default 65535 */ Maximum SDU size
tspec_ptr->len = sizeof(rapi_hdr_t) + sizeof(qos_tspecx_t);
tspec_ptr->form = RAPI_TSPECTYPE_Simplified;
這里似乎和RSVP的--呼叫方發(fā)送PATH消息的精神有一些違背,是被叫方發(fā)送PATH消息,其實二者沒有什么不同,首先主叫方,沒有收到被叫方的SDP所以不能確定被叫方接收RSVP消息的端口和IP地址,其次,媒體流是雙向的,雙方都必須在網(wǎng)路上通過PATH--Reserve的方式預(yù)流資源。
3. 在完成了一系列SIP命令和狀態(tài)的交換(RING,OK過程)以后,呼叫方開始準(zhǔn)備發(fā)送ACK消息了,也就是處于操作OpFarEndAnswered()的時候,調(diào)用rsvpFarEndAnswered發(fā)送Reserve消息,為什么要在這個時候發(fā)送Reserve消息呢?因為主叫在下一個過程(收到ACK消息后,打開RTP通道之前)的時候,已經(jīng)保證了所有的主叫到被叫之間的路由器都已經(jīng)收到了PATH預(yù)留消息,
4.第5,6兩個消息是主叫端點向被叫端點之間的路由器發(fā)送PATH消息,并且接收對端的RESV消息的過程。和1,2,3的過程基本上一樣,最后在雙方的RTP通道打開之前,主叫/被叫之間的路由器實現(xiàn)穩(wěn)定狀態(tài),也就是都收到主叫和被叫的資源預(yù)留的信息。
5.在被叫端點主要調(diào)用的函數(shù):
a. void setupRsvp(SipSdp& localSdp, SipSdp& remoteSdp)
主要由被叫端調(diào)用,用于在收到主叫發(fā)送過來的INVITE消息以后根據(jù)主叫的SDP回送被叫資源預(yù)留PATH消息。
b. void rsvpFarEndAnswered(Sptr localSdp, Sptr remoteSdp)
主要由主叫端調(diào)用,用于在向被叫端發(fā)送ACK消息前向被叫發(fā)送RESV消息和開始主叫資源預(yù)留PATH消息。
c. void rsvpAckHandler(Sptr localSdp, Sptr remoteSdp)
主要由被叫端調(diào)用,用于在收到主叫發(fā)送過來的ACK消息以后根據(jù)主叫的SDP回送主叫資源預(yù)留RESV消息。
6. 如何改進(jìn)Vocal的RSVP機(jī)制使它在廣域網(wǎng)上應(yīng)用:
一.對于目前在Vocal中對于RSVP的處理過程是非常簡單的,至少在用戶端都沒有對AdSpec和Tspec做任何具體的運算,僅僅是交給路徑上的路由器去預(yù)留資源,這樣做如果是一個簡單的沒有太復(fù)雜的網(wǎng)絡(luò)狀態(tài)的區(qū)域網(wǎng)內(nèi)部,采用這種方法當(dāng)然是無可厚非的,不過如果是在有復(fù)雜網(wǎng)絡(luò)狀態(tài)的廣域網(wǎng)上這樣就可以說是不是很行得通了,一般來說在主干網(wǎng)絡(luò)上會運行DiffServ的機(jī)制(所有的流都分組為多個服務(wù)類別的方式),這樣在骨干網(wǎng)上的RSVP消息當(dāng)然就會被忽略,所以我們的PATH和SESV消息都要實現(xiàn)對Diffserv的映射,換一句話來說,就地讓骨干網(wǎng)看起來象RSVP的一個節(jié)點,一般來說我們把DTOT(子網(wǎng)絡(luò)到主干網(wǎng)絡(luò)的傳播延遲
在RFC2205中有定義)改變成透過骨干網(wǎng)的傳播延遲和平均排隊延遲的值(這個是由主干網(wǎng)羅入口/出口路由器做的工作),對于RESV來說上沿著已經(jīng)建立的途徑傳遞,那么這個問題就不存在了。
另外有幾個情況是在如果主干網(wǎng)絡(luò)使用DiffSev需要注意的:
a. 如果有一個流不符合Tspec時--而這個時候路由器已經(jīng)為所有的入口和出口規(guī)劃了每一條虛鏈路的時候,一個不符合Tspec的流就足以毀壞同一類別所有其他留所爭取的服務(wù)質(zhì)量,例如入口處歸納低質(zhì)量的視頻/音頻流時候,出現(xiàn)了高質(zhì)量的視頻流。
b. 分散的/突發(fā)的流合并到平緩的流中時候。
不過一般來說每個路由器都具備檢查流的Tspec的能力,特別是作為主干網(wǎng)絡(luò)入口的路由器(例如一些大的網(wǎng)絡(luò)(BGP/EBGP)的入/出口地方)。在運行視頻會議或者是其他突發(fā)流很多的惡劣工作狀況的時候:
我們前面已經(jīng)反復(fù)地闡述:在Vocal中只不過是實現(xiàn)了一個簡單RSVP構(gòu)架,最重要的一點就是它不能夠?qū)崿F(xiàn)軟狀態(tài),也就是定期刷新消息的Tspec和Rspec,如果在視頻信號的時候這樣的情況出現(xiàn)得特別頻繁,由于視頻信號并不總是處于一種穩(wěn)定的平緩的狀態(tài)傳輸,以及當(dāng)路由改變的時刻,RSVP消息需要能準(zhǔn)確的沿著新的路由往復(fù)(這種情況是常常出現(xiàn)的,特別在大型網(wǎng)絡(luò)中)。
解決上述問題的途徑首先就是要在RSVP建立保證服務(wù)預(yù)定,也就是要根據(jù)接收端根據(jù)發(fā)送端的AdSpec消息計算預(yù)留的帶寬(而在Vocal中基本上沒有處理AdSpec),AdSpec中參加帶寬運算的主要是兩個參數(shù):Dtot和Ctot,第一個參數(shù)是最小路徑延遲,第二個是路徑帶寬,通過這兩個參數(shù)根據(jù)公式D=(b(存儲桶深度)+Ctot)/p
+ Dtot計算出端到端之間的延遲:
例如:
PATH流的初始特征:
Tspec(p=10mbps,L=2kbps,r=1mbps,b=32kbps) AdSpec(Ctot=0,Dtot=0)
經(jīng)過第一個路由器:
Tspec(p=10mbps,L=2kbps,r=1mbps,b=32kbps) AdSpec(Ctot=11,Dtot=0.05s)
經(jīng)過第二個路由器:
Tspec(p=10mbps,L=2kbps,r=1mbps,b=32kbps) AdSpec(Ctot=55,Dtot=0.1s)
現(xiàn)在我們來計算Resv中Rspec項目:
最長的延遲為0.1s的延遲,我們在Rspec中所計算的預(yù)留帶寬必須符合這個要求,那么根據(jù)公式:
D=(b+Ctot)/p + Dtot
b:存儲桶深度
計算出的D為0.185S我們在根據(jù)這個公式來計算預(yù)留帶寬
R=((p-r)(L+Ctot)+(b-L)p)/((t-Dtot)(p-r)+b-L)
r:業(yè)務(wù)流量
t:所需要的延遲
注意:所需要的延遲在0.1-0.185之間變化,這樣我們通過上述公式得出一個比較確切的R值R=1.66Mbps。所以Rspec為:R=1.66Mbps;松弛項(S):用于指示Qos的富裕量,如果所有的路由器按照R預(yù)留,那么整個路徑上的端到端的延遲會比要求的時延要少S毫秒:這里可以選擇0.05S,具體的松弛項計算可以參看RFC2205定義。
上面我們解決了服務(wù)預(yù)定的問題,但是它只不過讓我們的終端程序運行進(jìn)一步合理化,可以正確的規(guī)劃需要預(yù)留的帶寬,但是中間還是有關(guān)鍵問題沒有解決,也就是我們上面說的軟狀態(tài)--定期刷新Tspec和Rspec,以及探測/感知主干路由的變化
從程序上實現(xiàn)這些事實上并不困難,我們在打開媒體通訊的RTP信道以后,可以用一個進(jìn)程定期的發(fā)送PATH和RESV消息,讓流的接收者進(jìn)行定期刷新,特別是在視頻通訊階段(采用H.263算法)出現(xiàn)幀間幀的時候,數(shù)據(jù)的流量必然會大大增加,這個時候,如果提前刷新預(yù)留的狀態(tài),那么我們可以在初期就避免網(wǎng)絡(luò)出現(xiàn)阻塞的問題,當(dāng)然,如果流量超過了路徑所承受的標(biāo)準(zhǔn),那么必然會依靠增加S(松弛度)來阻塞數(shù)據(jù)包,這個時候,必然通訊會出現(xiàn)一個比較明顯的延遲,不過根據(jù)實驗結(jié)果表明,這些還是可以接受的。
如果IP路由發(fā)生了變化,那么上述的解決辦法同樣的適用,定期傳送RSVP的消息可以重新根據(jù)流的路徑進(jìn)行新的定義,所以他們也會沿著新的路徑進(jìn)行傳輸,所以沿著相反路徑的RESV消息將試圖沿著新的路由進(jìn)行預(yù)定,舊的預(yù)定就會超時然后取消。
但是我們?nèi)绻紤]到主干網(wǎng)絡(luò)使用DiffServ就不會那么樂觀了(當(dāng)然主干網(wǎng)絡(luò)的路由變化不會那么劇烈),主干網(wǎng)絡(luò)上PATH項目中變化的部分就是AdSpec中的Dtot/Ctot,我們在上面已經(jīng)說了如何定義Dtot的內(nèi)容(子網(wǎng)絡(luò)到主干網(wǎng)絡(luò)的傳播延遲的計算
在RFC2205中有定義)改變成透過骨干網(wǎng)的傳播延遲和平均排隊延遲的值,所以目前來說如果主干網(wǎng)絡(luò)是功能強大的千兆路由器組成,那么PATH中的Dtot和Ctot可以在中繼的時候得到更新,如果是ATM或者是MPLS網(wǎng)絡(luò)的話,出入口也可以得到更新,這樣的話你事實上完全不必操心。
不過不管怎么說上述的數(shù)值如果是周期性計算并在RESV消息中更新的話,必然大大的加大路由器的運算開銷,特別在跨洋多點視頻會議的時候,這樣用戶接入服務(wù)供應(yīng)商區(qū)域網(wǎng)入口路由器可能會發(fā)生"餓死"的情況(沒有用戶數(shù)據(jù)時候反而被大量無用的RESV所淹沒),所以在使用主干網(wǎng)絡(luò)的時候,我們必須有一個機(jī)制探測主干網(wǎng)絡(luò)的傳播延遲并且通報給服務(wù)供應(yīng)商區(qū)域網(wǎng)入口路由器,最好是主干入口路由器本身就具備這樣的功能,進(jìn)一步簡化主干網(wǎng)絡(luò)為一個簡單的RSVP節(jié)點,變成由主干網(wǎng)絡(luò)的接入路由器通告服務(wù)供應(yīng)商的路由器端對端的延遲
,這樣把計算的過程交給主干路由器*,而服務(wù)供應(yīng)商區(qū)域網(wǎng)入口路由器只負(fù)責(zé)更新AdSpec,這樣效率就可以得到大大的提高,不過協(xié)議的復(fù)雜性就增加了不少。**
3.2.8用戶處于通話的StateInCall狀態(tài):
StateInCall::StateInCall()
{
addEntryOperator( new OpStartAudioDuplex );
addOperator( new OpAck );
addOperator( new OpConfTargetOk );
addOperator( new OpFwdDigit );
addOperator( new OpTerminateCall );
addOperator( new OpEndCall );
addOperator( new OpReInvite );//多方會議和呼叫等待
addOperator( new OpStartCallWaiting );
if ( UaConfiguration::instance()->getXferMode() != XferOff )
{
addOperator( new OpSecondCall );
addOperator( new OpRecvdXfer );
}
addExitOperator( new OpStopAudio );
}
無論是主叫還是被叫,最后情況下都會進(jìn)入到StateInCall狀態(tài)中去,顧名思義,這個狀態(tài)是打開媒體流(RTP/RTCP)通道,并且開始語音通訊的狀態(tài).
3.2.8.1 OpStartAudioDuplex主叫打開媒體通道端口,向被叫發(fā)送媒體信息
首先我們來看一下在這個狀態(tài)中加入的第一個操作OpStartAudioDuplex,作為主叫端,它打開了主叫的RTP/RTCP端口,開始向被叫發(fā)送媒體消息。
const Sptr < State >
OpStartAudioDuplex::process( const Sptr < SipProxyEvent > event )
{
Sptr < UaCallInfo > call;
call.dynamicCast( event->getCallInfo() );
assert( call != 0 );
Sptr < SipSdp > remoteSdp;
Sptr < SipSdp > localSdp;
注釋:*主干路由器大部分時候和用戶接入服務(wù)供應(yīng)商區(qū)域網(wǎng)入口路由器采用靜態(tài)路由的方式接入,所以我們預(yù)想對于主干路由器來說事實上只要僅僅發(fā)送通告給服務(wù)供應(yīng)商區(qū)域網(wǎng)入口路由器就可以了,
**這個問題我們曾經(jīng)在IETF的討論中和很多通訊工程師進(jìn)行討論,完全可以增加此類的限制,不過上述的情況有不少人認(rèn)為發(fā)生的可能性不大,我個人一直認(rèn)為隨著服務(wù)供應(yīng)商所提供的多媒體業(yè)務(wù)的進(jìn)一步擴(kuò)展,這種危機(jī)可能在近幾年就會要爆發(fā)出來。
//取得引起振鈴的INVITE消息
Sptr < InviteMsg > inviteMsg = call->getRingInvite();
//取出當(dāng)前的Contact字段中所引發(fā)的INVITE消息,在OpInviteUrl::process中會定義這//個當(dāng)前的SipCOntact字段。
InviteMsg invMsg = call->getContact()->getInviteMsg();
//兩者相比較,檢驗是否引起震鈴的INVITE消息和當(dāng)前Contact字段中的INVITE消息
//是否相同,相同的話就把對端和本地的SDP取出,
if ( *inviteMsg == invMsg )
{
remoteSdp = call->getRemoteSdp();
localSdp = call->getLocalSdp();
}
else
{//不相同的話以遠(yuǎn)端第二次回送的震鈴消息為準(zhǔn),取出SDP
remoteSdp = call->getRemote2Sdp();
localSdp = call->getLocal2Sdp();
}
assert( localSdp != 0 );
//掛上本地設(shè)備事件的處理隊列
Sptr < UaHardwareEvent > signal
= new UaHardwareEvent( UaDevice::getDeviceQueue() );
//定義所要處理的事件類型
signal->type = HardwareAudioType;
//向當(dāng)前聲音設(shè)備(Sounnd Card或者是Phone Card等等)控制臺發(fā)送請求
struct HardwareAudioRequest* request = &(signal->signalOrRequest.request);
//開始聲音發(fā)送,ResGwDevice::processSessionMsg中會調(diào)用這個狀態(tài)的檢測,并且會打
//開聲音設(shè)備發(fā)送媒體信息,調(diào)用ResGwDevice::audioStart,稍后介紹。
request->type = AudioStart;
… …
//設(shè)定遠(yuǎn)端的Rtp接收發(fā)送端口
request->remotePort = remoteSdp->getRtpPort();
//設(shè)定本地的Rtp接收發(fā)送端口
// Set local host and port
request->localPort = localSdp->getRtpPort();
strcpy( request->localHost,
localSdp->getConnAddress().getData(lo) );
……
request->echoCancellation = true;
//設(shè)定RTP包的發(fā)送速率
request->rtpPacketSize = getRtpPacketSize(*localSdp);
… …
//停止震鈴回送。
request->sendRingback = false;
//在本地設(shè)備事件的處理隊列掛上當(dāng)前的處理請求
UaDevice::getDeviceQueue()->add( signal );
return 0;
}
(未完待續(xù))
在Vovida的基礎(chǔ)上實現(xiàn)自己的SIP協(xié)議棧(五)
作者聯(lián)系方法:lu_zheng@21cn.com
作者供稿 CTI論壇編輯