gnugk5.5源码分析(5)之H225代理实现

一、gnugk的代理模式

对于gnugk的实现,依据配置项,可以有不同的信令代理行为;具体来说,有下面这几种:

  1. 呼叫信令(signaling messages)由终端之间直接交互,即gnugk完全不代理转发任何的呼叫信令;
  2. gnugk仅代理转发h225协议相关的信令,而不代理转发h245信令和媒体数据流(RTP/RTCP);
  3. gnugk代理转发h225和h245相关的信令,但不代理转发媒体数据流;
  4. 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节主要是从文字上来描述关键的流程点,这里再补充一个时序图,两者配合可以更加直观些。具体的说明,前面章节已经讲了,这里就不再赘述。

3.3.1 没有H460流程的时序图

在这里插入图片描述

3.3.2 有H460流程的时序图

在这里插入图片描述

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值