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

一、前述

对于H323协议来说,H460协议主要是用来完成网络穿透功能,而其中使用更多的是H460.18和H460.19协议,H460.18主要是完成信令上的协商穿透,其中就包括了H225信令和H245信令;而H460.19主要是完成媒体上的穿透。因此,本文在讲述gnugk的H245时,也会区分有H460协议过程和无H460协议过程。

二、gnugk相关配置项

gnugk对于H245协议的控制配置项,主要是由H245Routed控制,但是如果配置了EnableH46018且终端支持H460协议,或者Proxy配置项的Enable值为真,则即使H245Routed没有配置,那么gnugk也会自动完成对H245的代理,因为要完整代理h460通话,必须涉及h245流程。

三、所涉及的重点相关类

  • CallSignalSocket
    代表h225的TCP连接的socket对象类,在gk代理的一路通话中,存在2个类对象,分别表示主被叫端。

  • H245Socket
    代表h245的TCP连接的socket对象类,在gk代理的一路通话中,存在2个类对象,分别表示主被叫端。
    一般用在不需要反向代理的socket对象,指非H460协议时的h245的socket对象。

  • NATH245Socket
    继承自H245Socket,重载实现了ConnectRemote方法;也是代表h245的TCP连接的socket;
    一般是用在h460终端下,当该终端为被叫端时,建立h245的过程,需要反向,由终端主动向gk建立。

  • H245Handler
    h245消息处理类,包括了单独建立h245连接的场景和h245隧道的场景。

  • H245ProxyHandler
    继承自H245Handler类,也是处理h245消息;一般用在如h460协议下的h245代理处理过程。

四、h245协议的代理实现过程

以终端是否有启用h460为划分,在整体的h245协议的代理实现上,会有些许差别;
在整个实现中,主要是把握三点:

  1. 处理h245消息的类对象(H245Handler类对象或者H245ProxyHandler类对象)的创建
  2. 理解所创建的h245的socket对象(H245Socket类的对象或者NATH245Socket类的对象)的时机
  3. h245的网络连接的连接过程(涉及主被叫两端)
  4. 处理h245消息的类对象(H245Handler类对象或者H245ProxyHandler类对象)的实现细节

h245连接的建立自然是在h225连接之后,所以涉及到h225消息的处理,对h225流程不清楚,可参考学习下h225过程的文章;接下来的说明中,并不按照上述4点的顺序来说明,而是以时间线索来贯穿,在其中来点明所涉及到上述4点,同时为了方便描述,把h225连接的建立过程也一同讲一次。

4.1 h225连接的建立

在CallSignalSocket::OnSetup消息的处理中,终端不需要进入H460流程时,会调用到bool CallSignalSocket::CreateRemote(H225_Setup_UUIE & setupBody)方法,来生成代表另一端连接的CallSignalSocket对象,remote = new CallSignalSocket(this, peerPort),而后gk会主动连接被叫终端;而如果需要涉及到h460的被叫端,则会先通过ras的UDP连接发送一个SCI消息给被叫端,由被叫端主动连接gk。

  • 被叫端无h460过程

在remote = new CallSignalSocket(this, peerPort)的这个实例化方法里面,重点是关注CallSignalSocket::SetRemote(CallSignalSocket * socket)方法,在这个方法里面,把两个CallSignalSocket建立起关联,并且创建其后续用于处理H245的代理类对象m_h245handler;

void CallSignalSocket::SetRemote(CallSignalSocket * socket)
{
	// 可以看到在CallSignalSocket类内部有个remote对象,表示的就是另一端的CallSignalSocket对象
	// 而且是同属于一个callptr实例,因此m_call指向同一个对象。
	remote = socket;
	m_call = socket->m_call;
	m_call->SetSocket(socket, this);
	
	// 省略中间的代码逻辑
	
	// 在创建被叫端的CallSignalSocket对象时,把被叫端和主叫端的socket放入同一个后续的网络监听处理线程
	// 因此设置添加到同一个ProxyHandler对象里面。
	SetHandler(socket->GetHandler());
	
	// 省略中间的代码逻辑	
	
	if (m_call->GetProxyMode() == CallRec::ProxyEnabled) {
		// 省略相应的代码逻辑
	} else {
		// 这里可以看到,对于逻辑上需要H245代理时,会创建后续处理H245消息的代理类H245Handler对象
		if (m_call->IsH245Routed()) {
			socket->m_h245handler = new H245Handler(socket->localAddr, calling, socket->masqAddr);
			m_h245handler = new H245Handler(localAddr, called, masqAddr);
		}
	}
}

在处理完setup消息后,此时CallSignalSocket::ReceiveData返回的处理结果是Connecting,因此CallSignalSocket::Dispatch状态机进入gk主动连接被叫端的过程,主要是通过InternalConnectTo连接被叫端,在连接成功后,再调用ForwardData方法,把Setup消息转给被叫端。

		case Connecting:
			if (InternalConnectTo()) {
				if (GkConfig()->HasKey(RoutedSec, "TcpKeepAlive"))
					remote->Self()->SetOption(SO_KEEPALIVE, Toolkit::AsBool(
						GkConfig()->GetString(RoutedSec, "TcpKeepAlive", "0")) ? 1 : 0,
						SOL_SOCKET);

				ConfigReloadMutex.EndRead();
				const bool isReadable = remote->IsReadable(2 * setupTimeout);
				ConfigReloadMutex.StartRead();
				// 若被叫端响应超时,则挂断处理。
				if (!isReadable) {
					PTRACE(3, "Q931\tTimed out waiting for a response to Setup or SCI message from " << remote->GetName());
					if (m_call)
						m_call->SetDisconnectCause(Q931::TimerExpiry);
					OnError();
				}
				// 若被叫端正常响应了,则先把主被叫的CallSignalSocket对象放入同一个处理线程中,再去触发被叫的响应
				GetHandler()->Insert(this, remote);
				return;
			} else if (m_call && m_call->MoveToNextRoute() && (m_h245socket == NULL || m_call->DisableRetryChecks())) {
				// 若有其它路由规则,则尝试使用下一个路由规则
			} else {
				PTRACE(3, "Q931\t" << AsString(peerAddr, peerPort) << " DIDN'T ACCEPT THE CALL");
			   // 无法主动连接到被叫端,进入挂断处理
			}
  • 被叫端涉及h460过程

CallSignalSocket::OnSetup处理函数的最后面,可以看到gk通过ras的UDP连接向被叫端发送了SCI消息,此时在处理了setup消息后,CallSignalSocket::ReceiveData返回的处理结果是DelayedConnecting,等终端主动连接。

	// if destination route/endpoint is a traversal client
	if (m_call->GetCalledParty() && m_call->GetCalledParty()->IsTraversalClient() && !m_call->GetCalledParty()->UsesH46017()) {
		// 中间这里有一段构建SCI消息的实现
		// 这里就是把SCI发给被叫端了
		RasSrv->SendRas(sci_ras, m_call->GetCalledParty()->GetRasAddress(), m_call->GetCalledParty()->GetRasServerIP(), m_call->GetCalledParty()->GetH235Authenticators());

		// store Setup,把主叫发过来的Setup先保存起来
		m_call->StoreSetup(msg);
		// 这里把处理结果设置为延后连接,作用就如同注释说的,等收到被叫的facility后,再转。
		m_result = DelayedConnecting;	// don't forward now, wait for endpoint to send Facility
	}

终端在收到SCI后,会先响应一个SCR消息,表示自身已经收到了消息,而后终端向该地址端口发起TCP连接,在TCP连接成功连接后,会在该h225的TCP连接上,通过Facility消息通知gk,Gk从收到Facility才认为与终端成功建立了h225连接,进入CallSignalSocket::OnFacility的具体处理中,正常来说,此时进入case H225_FacilityReason::e_undefinedReason:的处理分支上,主要关键点代码如下:

// 通过callIdentifier标识符,找到代表这一路完整通话的m_call对象;
m_call = CallTable::Instance()->FindCallRec(callIdentifier);

//创建后续处理H245消息的代理对象,这个H245ProxyHandler类继承自H245Handler类,只是重载实现了一些操作;
H245ProxyHandler *proxyhandler = new H245ProxyHandler(m_call->GetCallIdentifier(), callingSocket->localAddr, calling, callingSocket->masqAddr);

m_h245handler = new H245ProxyHandler(m_call->GetCallIdentifier(), localAddr, called, masqAddr, proxyhandler);

// 转发之前主叫的Setup消息给被叫端
if (q931pdu->Encode(rawSetup))
	this->TransmitData(rawSetup);

4.2 h245连接的建立

在被叫端响应了connect消息后,进入connect消息处理。

void CallSignalSocket::OnConnect(SignalingMsg *msg)
{
	// 注意这里只是截取的代码片断,并不是完整的OnConnectin处理实现
	ConnectMsg *connect = dynamic_cast<ConnectMsg*>(msg);
	
	H225_Connect_UUIE & connectBody = connect->GetUUIEBody();
	
	if (m_call) {
		// 当被叫方响应了Connection后,在H323协议上就代表着对方已经接起,因此会设置标志位。
		m_call->SetConnected();
		RasServer::Instance()->LogAcctEvent(GkAcctLogger::AcctConnect, m_call);
	}

	// 如果通信时,支持走FastStart模式,则会从这里进入该模式下的相关处理
	// 但是在本文讨论中,我们按照H245流程是建立在单独的TCP连接基础之上来讨论的,并不走FastStart模式
	if (HandleFastStart(connectBody, false))
		msg->SetUUIEChanged();

	// 这里是重点,进入处理connect消息携带的H245地址信息
	if (HandleH245Address(connectBody))
		msg->SetUUIEChanged();

	SendPostDialDigits();	// 1st check, for tunneled H.245 connections
}

处理Connect消息中的H245地址,是由HandleH245Address来实现的,HandleH245Address是一个模板函数。

	template<class UUIE> bool HandleH245Address(UUIE & uu)
	{
		// 判断是否包含h245地址信息
		if (uu.HasOptionalField(UUIE::e_h245Address)) {
			if (m_call)
				m_call->SetH245ResponseReceived();
			// 这个方法内部作更详细的处理
			if (SetH245Address(uu.m_h245Address))
				//成功完成处理后,返回
				return (m_h245handler != NULL);
			// 如果前面处理失败,则删除245地址
			uu.RemoveOptionalField(UUIE::e_h245Address);
			return true;
		}
		return false;
	}

接下来,查看SetH245Address方法(这里在查看时因篇幅原因,一些无关的代码会被删除)

bool CallSignalSocket::SetH245Address(H225_TransportAddress & h245addr)
{
	CallSignalSocket *ret = static_cast<CallSignalSocket *>(remote);

	m_h245handler->OnH245Address(h245addr);
	bool userevert = m_isnatsocket;
	bool setMultiplexPort = false;
#ifdef HAS_H46018
	if (m_call->H46019Required() && IsTraversalClient()) {
		userevert = true;
	}
#endif
	if (m_h245socket) {
		//此时走到这里时,这个h245的socket还没有创建起来,所以这里不会进来处理相关流程
	}

	//这里依据是否H460被叫端,创建相应的h245的socket对象,不需要反转建立时为H245Socket,否则为NATH245Socket;
	m_h245socket = userevert ? new NATH245Socket(this) : new H245Socket(this);	
	if (!(m_call->GetRerouteState() == RerouteInitiated)) {
		//当前并不是重新路由,因此其路由状态不等于RerouteInitiated
		//这里创建远端的H245Socket,在创建时传递了本端的m_h245socket,其内部构造中,会把两个socket关联起来
		ret->m_h245socket = new H245Socket(m_h245socket, ret);	
	}

	//设置保存对端的h245地址
	m_h245socket->SetH245Address(h245addr, masqAddr);

	if (m_h245TunnelingTranslation && !m_h245Tunneling && GetRemote() && GetRemote()->m_h245Tunneling) {
		CreateJob(m_h245socket, &H245Socket::ConnectToDirectly, "H245ActiveConnector");	// connect directly
		return false;	// remove H.245Address from message if it goes to tunneling side
	}
	if (m_call->GetRerouteState() == RerouteInitiated) {
		// if in reroute, don't listen, actively connect to the other side, half of the H.245 connection is already up
		m_h245socket->SetRemoteSocket(ret->m_h245socket);
		if (ret->m_h245socket) {
            ret->m_h245socket->SetRemoteSocket(m_h245socket);
        } else {
            PTRACE(1, "Reroute: Error mixed tunneled / non-tunneled call");
        }
		CreateJob(m_h245socket, &H245Socket::ConnectToRerouteDestination, "H245RerouteConnector");
	} else {
		// 现在正在处理的是connect消息,这里启动监听后,这个connect消息会转发到主叫端,等待主叫端主动连接上来。
		CreateJob(m_h245socket, &H245Socket::ConnectTo, "H245Connector");	// start a listener
	}
	return true;
}

来看下,这个异步线程的处理,主要是先等待主叫端向gk连接h245的连接后,再进一步处理gk和被叫端的h245连接。

void H245Socket::ConnectTo()
{
	// 网络socket监听,等待主叫端连接gk
	if (remote->Accept(*listener)) {
        remote->SetConnected(true);
        // 在成功连接后,把被叫端和主叫端放入同一个处理线程中
        GetHandler()->Insert(remote);
        if (sigSocket && sigSocket->GetH245MessageQueueSize() > 0) {
	    	// H.245 connect for tunneling leg - must be mixed mode
		    H245Socket * remoteH245Socket = dynamic_cast<H245Socket *>(remote);
	    	if (remoteH245Socket) {
	    	// ...
	    	}
			return;
		}
		// 在主叫端连接了gk后,gk才去向被叫端建立h245的TCP连接。
		// 这里的ConnectRemote,若指的是H245Socket::ConnectRemote,内部的逻辑就是实现一个TCP的connect操作,
	    // 若指的是NATH245Socket::ConnectRemote,则其内部通过Facility消息,设置类型为H225_FacilityReason::e_startH245,通知被叫端,
	    //让被叫端再次主动过来连接h245的TCP连接;此时这个ConnectRemote操作是阻塞等被叫端上来连接。
		if (ConnectRemote()) {
			ConfigReloadMutex.StartRead();
			SetConnected(true);
			remote->SetConnected(true);
			GetHandler()->Insert(this, remote);
			ConfigReloadMutex.EndRead();
#ifdef HAS_H46018
			if (sigSocket && (sigSocket->IsCallFromTraversalServer() || sigSocket->IsCallToTraversalServer())) {
				SendH46018Indication();
                RegisterKeepAlive(GkConfig()->GetInteger(RoutedSec, "H46018KeepAliveInterval", 19));
			}
#endif
			return;
		}
	} else {
	    if (m_ignoreAcceptError) {
            // need when using H.245 multiplexing, where we close the listen socket from another thread
            return;
	    } else {
    		PTRACE(1, "Error: H.245 Accept() failed this=" << this << " os_socket=" << GetHandle());
	    	SNMP_TRAP(10, SNMPError, Network, "H.245 accept failed");
	    }
	}

	ReadLock lockConfig(ConfigReloadMutex);

	m_signalingSocketMutex.Wait();
	// establish H.245 channel failed, disconnect the call
	// 这里往下的处理就是如何H245连接建立失败,则发起通话挂断的流程。
	PTRACE(1, "Error: Establishing the H.245 channel failed, disconnecting");
	SNMP_TRAP(10, SNMPError, Network, "H.245 failed");
	CallSignalSocket *socket = sigSocket; // use a copy to avoid race conditions with OnSignalingChannelClosed
	if (socket) {
		socket->SetConnected(false);
		socket->RemoveCall();
		if (!socket->IsBlocked())
		    socket->SendReleaseComplete(H225_ReleaseCompleteReason::e_unreachableDestination);
		socket->CloseSocket();
	}
	m_signalingSocketMutex.Signal();

	if (H245Socket *ret = static_cast<H245Socket *>(remote)) {
		ret->m_signalingSocketMutex.Wait();
		socket = ret->sigSocket;
		if (socket) {
			if (socket->IsConnected() && !socket->IsBlocked())
				socket->SendReleaseComplete(H225_ReleaseCompleteReason::e_unreachableDestination);
			socket->SetConnected(false);
			socket->CloseSocket();
		}
		ret->m_signalingSocketMutex.Signal();
	}
	GetHandler()->Insert(this, remote);
}

五、h245消息处理泵

看过前面网络监听部分的话,就知道,由于ProxyHandler线程来说,当有某socket可以读取数据时,会进入到ProxyHandler::ReadSocket方法里面,在这个方法里面,由借助该socket的ReceiveData读取数据并完成数据的处理,而对于h245消息,其相对应的是H245Socket::ReceiveData。

ProxySocket::Result H245Socket::ReceiveData()
{
	// 读取网络数据
	if (!ReadTPKT()) {
		return NoData;
	}
	
	// 转换数据类型
	PPER_Stream strm(buffer);

	bool suppress = false;
	// 这里从h225的呼叫信令socket对象开始处理;这里之所以要从h225的socket对象开始,我觉得是因为h245消息,
    // 在tunneling或者fastStart等场景下,是借用h225的socket来交互的,所以统一从h225的socket对象开始处理可以达到整体流程上的统一
	if (sigSocket && sigSocket->HandleH245Mesg(strm, suppress, this))
		buffer = strm;

	// 一些特定的消息,不需要转发到对端。
	if (suppress) {
		return NoData;	// eg. H.460.18 genericIndication
	} else {
		if (sigSocket && sigSocket->GetRemote()
			&& sigSocket->GetRemote()->IsH245Tunneling()
			&& sigSocket->GetRemote()->IsH245TunnelingTranslation()) {
			// 如果另一端走tunneling流程,而且通话支持tunneling转化,则按协议发送。
			if (!sigSocket->GetRemote()->SendTunneledH245(strm)) {
				PTRACE(1, "Error: H.245 tunnel send failed to " << sigSocket->GetRemote()->GetName());
			}
			return NoData;	// already forwarded through tunnel
		}
		// check if other H.245 socket is connected already, otherwise queue messages until connected,这里自带注释了,就不解释了。
		if (!remote || !remote->IsConnected() || remote->GetOSSocket() < 0) {
            if (sigSocket) {
                PASN_OctetString h245msg;
                h245msg.SetValue(strm);
                PTRACE(4, "H245\tQueuing H.245 message until connected");
                sigSocket->QueueH245Message(h245msg);
		        return NoData; // queued, don't forward now
            }
		}
		// 正常的返回值都是Forwarding,表示转发到另一端。
		return Forwarding;
	}
}

而在HandleH245Mesg内部的处理中,有一大段是站在h225的socket上处理,然后保存相应数据到m_call对象里面;而后会再次把消息,提交给h245的处理对象来处理,即m_h245handler;因此有下面这句控制流程转换。

if ((!m_h245handler || !m_h245handler->HandleMesg(h245msg, suppress, m_call, h245sock)) && !changed)
		return false;

在m_h245handler->HandleMesg就按协议定义了,h245的4类消息类型,e_request、e_response、
e_command、e_indication;各个消息内部怎么处理,就自行查看吧。

bool H245Handler::HandleMesg(H245_MultimediaSystemControlMessage & h245msg, bool & suppress, callptr & call, H245Socket * h245sock)
{
	bool changed = false;
	switch (h245msg.GetTag())
	{
		case H245_MultimediaSystemControlMessage::e_request:
			changed = HandleRequest(h245msg, call);
			break;
		case H245_MultimediaSystemControlMessage::e_response:
			changed = HandleResponse(h245msg, call);
			break;
		case H245_MultimediaSystemControlMessage::e_command:
			changed = HandleCommand(h245msg, suppress, call, h245sock);
			break;
		case H245_MultimediaSystemControlMessage::e_indication:
			changed = HandleIndication(h245msg, suppress);
			break;
		default:
			PTRACE(2, "H245\tUnknown H245 message: " << h245msg.GetTag());
			break;
	}
	return changed;
}

这里要讲的一点是m_h245handler按被叫端是否是H460处理流程,区分为H245Handler或者是H245ProxyHandler类的对象;而H245ProxyHandler重载了H245Handler的上述4类消息的处理方法,查看代码时,注意区分理解即可。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值