一、gnugk的代理模式
对于gnugk的实现,依据配置项,可以有不同的信令代理行为;具体来说,有下面这几种:
- 呼叫信令(signaling messages)由终端之间直接交互,即gnugk完全不代理转发任何的呼叫信令;
- gnugk仅代理转发h225协议相关的信令,而不代理转发h245信令和媒体数据流(RTP/RTCP);
- gnugk代理转发h225和h245相关的信令,但不代理转发媒体数据流;
- gnugk代理转发h225和h245相关的信息,并且代理转发媒体数据流,包括T120的通道数据;这时gnugk扮演一个完全代理的作用;
对于第1种,因为信令和媒体都是终端之间直接进行的,跟gnugk服务没有关系,所以就是标准的H323协议的交互流程,并没有什么好说的;本文旨在说明第2种场景实现;后续第3种、第4种场景,由接下来的文章再继续说明。
二、gnugk对h225信令的包装
gnugk对h225协议信令的包装处理,主要集中在sigmsg.h和sigmsg.cxx文件中。
2.1 h225协议上规定的数据类型
对于H.225协议,其协议上规定的呼叫信令相关的数据类型主要如下:
class H225_H323_UserInformation;
class H225_Setup_UUIE;
class H225_SetupAck_UUIE;
class H225_CallProceeding_UUIE;
class H225_Alerting_UUIE;
class H225_Connect_UUIE;
class H225_Progress_UUIE;
class H225_Facility_UUIE;
class H225_ReleaseComplete_UUIE;
class H225_Information_UUIE;
class H225_Notify_UUIE;
class H225_Status_UUIE;
class H225_StatusInquiry_UUIE;
2.2 SignalingMsg类
gnugk通过定义一个SignalingMsg类,来表示呼叫信令所共同的部分,比如本端的地址端口、远端的地址端口等这些常用的信息,其中比较最重要的一个静态函数是Create方法;这是一个创建各类h225信令消息的简单工厂方法实现,具体的实现,等会再详细查看;
前面说了,SignalingMsg类是对所有h225信令共同部分的抽象定义与实现;那么针对每个h225信令,如何具体的定义呢?这里gnugk是通过了H225SignalingMsg模板类来实现。
template<class UUIE>
class H225SignalingMsg : public SignalingMsg {
public:
/// Build a new SignalingMsg
H225SignalingMsg(
Q931 * q931pdu, /// this pointer is not cloned and deleted by this class destructor
H225_H323_UserInformation * uuie, /// decoded User-User IE
UUIE & /*uuieBody*/, /// decoded UUIE body
const PIPSocket::Address & localAddr, /// an address the message has been received on
WORD localPort, /// a port number the message has been received on
const PIPSocket::Address & peerAddr, /// an address the message has been received from
WORD peerPort /// a port number the message has been received from
) : SignalingMsg(q931pdu, uuie, localAddr, localPort, peerAddr, peerPort),
m_uuieBody(uuie->m_h323_uu_pdu.m_h323_message_body) { }
UUIE & GetUUIEBody() const { return m_uuieBody; }
virtual SignalingMsg * Clone()
{
if (!m_uuie)
return NULL;
H225_H323_UserInformation *uuieClone = (H225_H323_UserInformation*)(m_uuie->Clone());
return new H225SignalingMsg<UUIE>(new Q931(*m_q931), uuieClone,
(UUIE&)(uuieClone->m_h323_uu_pdu.m_h323_message_body),
m_localAddr, m_localPort, m_peerAddr, m_peerPort);
}
private:
H225SignalingMsg();
H225SignalingMsg(const H225SignalingMsg &);
H225SignalingMsg& operator=(const H225SignalingMsg &);
protected:
UUIE & m_uuieBody; /// H.225.0 UUIE structure associated with the message
};
实现上也是比较简单,就是对于每个h225的每个UUIE数据类型,都包装保存在UUIE & m_uuieBody成员中,而每个h225共同相关的就实现保存在SignalingMsg中。
2.3 定义各类h225信令消息类型
typedef H225SignalingMsg<H225_Setup_UUIE> SetupMsg;
typedef H225SignalingMsg<H225_Alerting_UUIE> AlertingMsg;
typedef H225SignalingMsg<H225_CallProceeding_UUIE> CallProceedingMsg;
typedef H225SignalingMsg<H225_Connect_UUIE> ConnectMsg;
typedef H225SignalingMsg<H225_Progress_UUIE> ProgressMsg;
typedef H225SignalingMsg<H225_ReleaseComplete_UUIE> ReleaseCompleteMsg;
typedef H225SignalingMsg<H225_Information_UUIE> InformationMsg;
typedef H225SignalingMsg<H225_Facility_UUIE> FacilityMsg;
//typedef H225SignalingMsg<H225_Notify_UUIE> NotifyMsg;
typedef H225SignalingMsg<H225_Status_UUIE> StatusMsg;
//typedef H225SignalingMsg<H225_StatusInquiry_UUIE> StatusInquiryMsg;
对于每个h225的UUIE消息,通过H225SignalingMsg模板类,都全部重新定义了新的名称,比如SetupMsg/AlertingMsg等等;还记得前面讲到的SignalingMsg::Creat工厂方法,现在可以来看下这个方法实现,其实主是依据不同的UUIE的tag值,创建出相对应的h225信令对象而已。
SignalingMsg* SignalingMsg::Create(
Q931 * q931pdu, /// this pointer is not cloned and deleted by this class destructor
H225_H323_UserInformation * uuie, /// decoded User-User IE
const PIPSocket::Address & localAddr, /// an address the message has been received on
WORD localPort, /// a port number the message has been received on
const PIPSocket::Address & peerAddr, /// an address the message has been received from
WORD peerPort /// a port number the message has been received from
)
{
if (q931pdu == NULL)
return NULL;
if (uuie != NULL) {
H225_H323_UU_PDU_h323_message_body &body = uuie->m_h323_uu_pdu.m_h323_message_body;
switch (body.GetTag()) {
case H225_H323_UU_PDU_h323_message_body::e_setup:
return new SetupMsg(q931pdu, uuie, (H225_Setup_UUIE&)body, localAddr, localPort, peerAddr, peerPort);
case H225_H323_UU_PDU_h323_message_body::e_callProceeding:
return new CallProceedingMsg(q931pdu, uuie, (H225_CallProceeding_UUIE&)body, localAddr, localPort, peerAddr, peerPort);
case H225_H323_UU_PDU_h323_message_body::e_connect:
return new ConnectMsg(q931pdu, uuie, (H225_Connect_UUIE&)body, localAddr, localPort, peerAddr, peerPort);
case H225_H323_UU_PDU_h323_message_body::e_alerting:
return new AlertingMsg(q931pdu, uuie, (H225_Alerting_UUIE&)body, localAddr, localPort, peerAddr, peerPort);
case H225_H323_UU_PDU_h323_message_body::e_information:
return new InformationMsg(q931pdu, uuie, (H225_Information_UUIE&)body, localAddr, localPort, peerAddr, peerPort);
case H225_H323_UU_PDU_h323_message_body::e_releaseComplete:
return new ReleaseCompleteMsg(q931pdu, uuie, (H225_ReleaseComplete_UUIE&)body, localAddr, localPort, peerAddr, peerPort);
case H225_H323_UU_PDU_h323_message_body::e_facility:
return new FacilityMsg(q931pdu, uuie, (H225_Facility_UUIE&)body, localAddr, localPort, peerAddr, peerPort);
case H225_H323_UU_PDU_h323_message_body::e_progress:
return new ProgressMsg(q931pdu, uuie, (H225_Progress_UUIE&)body, localAddr, localPort, peerAddr, peerPort);
case H225_H323_UU_PDU_h323_message_body::e_status:
return new StatusMsg(q931pdu, uuie, (H225_Status_UUIE&)body, localAddr, localPort, peerAddr, peerPort);
// case H225_H323_UU_PDU_h323_message_body::e_statusInquiry:
// return new StatusInquiryMsg(q931pdu, uuie, (H225_StatusInquiry_UUIE&)body, localAddr, localPort, peerAddr, peerPort);
// case H225_H323_UU_PDU_h323_message_body::e_notify:
// return new NotifyMsg(q931pdu, uuie, (H225_Notify_UUIE&)body, localAddr, localPort, peerAddr, peerPort);
}
}
return new SignalingMsg(q931pdu, uuie, localAddr, localPort, peerAddr, peerPort);
}
三、gnugk对h225的处理流程分析
3.1 主要涉及的类定义
对于h225的代理,主要涉及的相关类有ProxyHandler、TCPServer、CallSignalListener、CallSignalSocket;其中由TCPServer、CallSignalListener搭建起h225的TCP网络监听的实现,在监听并接受一个TCP连接后,创建一个CallSignalSocket来代表这一连接对象,而连接之后,后续的针对这一特定socket对象的具体数据处理和监听,就提交到一个ProxyHandler对象;如果对于gnugk的网络监听实现不熟悉的,可以参考阅读网络监听章节的内部,这四个类在类关系图中位置如下:
3.2 H225的流程实现
3.2.1 TCPServer监听并处理TCP的网络连接
void TCPServer::ReadSocket(IPSocket * socket)
{
//忽略不看前面的其它逻辑判断
//对于h225协议来说,这里的socket对象指的是CallSignalListener对象
TCPListenSocket *listener = dynamic_cast<TCPListenSocket *>(socket);
if (!listener)
return;
//调用CallSignalListener对象的CreateAcceptor方法,创建一个CallSignalSocket对象,
//即这里的acceptor,然后通过这个acceptor完成TCP连接的accept操作;
ServerSocket *acceptor = listener->CreateAcceptor();
if (acceptor && acceptor->Accept(*listener)) {
PTRACE(6, GetName() << "\tAccepted new connection on " << socket->GetName() << " from " << acceptor->GetName());
//在成功接受客户端的TCP连接后,通过CreateJob把ServerSocket::Dispatch操作提交到一个线程中去执行。
//在Dispatch中完成具体的数据读取处理。
CreateJob(acceptor, &ServerSocket::Dispatch, "Acceptor");
} else {
PTRACE(4, GetName() << "\tAccept failed on " << socket->GetName());
delete acceptor;
}
}
3.2.2 CallSignalSocket::Dispatch状态机
前面讲到,最终是另起一条线程去处理CallSignalSocket对象的Dispatch方法。这里方法内部的代码结构如下,其主要作用就是接收并完成对h225第一个呼叫信令的处理,即Setup信令。整体的实现逻辑是在限定的时间内,读取并处理该setup信令,如果处理异常或者等待数据超时,则认为通话失败,走入挂断流程,并删除当前的socket对象;这里的数据读取和处理是实现在CallSignalSocket::ReceiveData方法里面,依据ReceiveData的返回值作进一步的处理;可以这么理解,CallSignalSocket::Dispatch实现了对setup消息进行处理的状态机,其主要状态有NoData、Connecting、Forwarding、Closing、Error、DelayedConnecting、NoDataAndDone,状态值定义在父类ProxySocket::Result枚举里面;状态机的状态切换由CallSignalSocket::ReceiveData内部处理返回相应的状态值。
NoData表示真实没有数据或者当成没有数据处理,只更新超时时间timeout的值;
Connection表示由gnugk向被叫方发起h225的TCP连接,若连接成功,由转发相应的Setup消息;
若连接失败,但是还有其它路由规则,则继续尝试下一个路由规则进行Setup消息的转发;
若连接失败,且没有其它路由规则,则认为通话失败,进行相应的清除操作;
DelayedConnecting表示延后进行h225的TCP连接,这个是配合H460协议流程的;因为对于h460流程,对于被叫端,也应该是被叫端主动向gnugk建立连接,而不是gnugk向被叫端建立;这个是h460的协议规则;
Forwarding表示向消息的接收方转发消息;
其它状态,认为是处理异常,应该挂断通话。
void CallSignalSocket::Dispatch()
{
while (timeout > 0) {
if (!IsReadable(timeout)) {
break;
}
switch (ReceiveData()) {
case NoData:
case Connecting:
#ifdef HAS_H46018
case DelayedConnecting:
#endif
case Forwarding:
default:
} /* switch */
} /* while */
if (m_call)
m_call->SetSocket(NULL, NULL);
delete this;
}
3.2.3 CallSignalSocket::ReceiveData读取h225消息和处理
步骤2中可以看到,CallSignalSocket::Dispatch的处理流程,由CallSignalSocket::ReceiveData的返回值来决定 ;在CallSignalSocket::ReceiveData内部
首先通过TCPProxySocket::ReadTPKT方法,从TCP连接中读取数据,即setup消息;
if (!ReadTPKT()) {
#ifdef HAS_H46017
if (m_isnatsocket && !IsOpen()) {
RegistrationTable::Instance()->OnNATSocketClosed(this);
CleanupCall();
}
#endif
return IsOpen() ? NoData : Error;
}
然后通过SignalingMsg::Create方法,把这个Setup消息,转化成一个SignalingMsg对象;
SignalingMsg *msg = SignalingMsg::Create(q931pdu, uuie,
_localAddr, _localPort, _peerAddr, _peerPort);
最后,依据h225的tag作进一步的具体处理,这里gnugk定义了h225消息的处理泵;
switch (msg->GetTag()) {
case Q931::SetupMsg:
m_rawSetup = buffer;
m_rawSetup.MakeUnique();
OnSetup(msg);
break;
case Q931::CallProceedingMsg:
OnCallProceeding(msg);
break;
case Q931::ConnectMsg:
m_rawConnect = buffer;
m_rawConnect.MakeUnique();
OnConnect(msg);
break;
case Q931::AlertingMsg:
OnAlerting(msg);
break;
case Q931::ReleaseCompleteMsg:
OnReleaseComplete(msg);
break;
case Q931::FacilityMsg:
OnFacility(msg);
break;
case Q931::ProgressMsg:
OnProgress(msg);
break;
case Q931::InformationMsg:
OnInformation(msg);
break;
case Q931::StatusMsg:
OnStatus(msg);
break;
}
在onSetup内部,依据是否走H460流程,会有不同的处理;
如果不需要走H460流程,则主要是CallSignalSocket::CreateRemote创建被叫端,然后返回状态为Connecting,由gnugk主动向被叫端发起TCP连接建立h225(CallSignalSocket::InternalConnectTo),并在连接建立后,转发setup消息给远端(CallSignalSocket::ForwardData);然后把通话双方的连接,添加到ProxyHandler线程中继续监听处理其它信令消息(GetHandler()->Insert(this, remote));
如果需要走H460流程,则先发送SCI消息通知被叫端,让被叫端主动上来连接gnugk;这时返回状态为DelayedConnecting延迟建立连接,把当前的CallSignalSocket对象提交给ProxyHandler线程继续监听处理(GetHandler()->Insert(this));当被叫方同gnugk建立h225的TCP后,被叫方与gnugk之间的第一个消息也是通过CallSignalSocket::Dispatch来进行处理,所不同的这时被叫方的第一个h225的消息是Facility消息,在OnFacility内部会对具体的Facility类型进行进一步的判断处理,一般对于H460的其类型是H225_FacilityReason::e_undefinedReason,在具体处理中,通过GetHandler()->MoveTo(callingSocket->GetHandler(), this)把当前被叫端的socket也提交到主叫端同一个ProxyHandler去监听处理;
3.2.4 ProxyHandler::ReadSocket的监听处理
在主叫和被叫相对应的CallSignalSocket进入ProxyHandler后,ProxyHandler就一直在监听这两个CallSignalSocket对象,如果有哪个socket可读数据了,就会触发调用到ProxyHandler::ReadSocket进行读取处理;在ProxyHandler::ReadSocket可以看到,其实际上还是调用CallSignalSocket::ReceiveData,进行数据的具体读取和处理;
3.3 h225的时序图
前面3.2节主要是从文字上来描述关键的流程点,这里再补充一个时序图,两者配合可以更加直观些。具体的说明,前面章节已经讲了,这里就不再赘述。