我是靠谱客的博主 害怕玉米,最近开发中收集的这篇文章主要介绍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, "Q931tTimed 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, "Q931t" << 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, "H245tQueuing 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, "H245tUnknown H245 message: " << h245msg.GetTag());
break;
}
return changed;
}

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

最后

以上就是害怕玉米为你收集整理的gnugk5.5源码分析(6)之H245代理实现的全部内容,希望文章能够帮你解决gnugk5.5源码分析(6)之H245代理实现所遇到的程序开发问题。

如果觉得靠谱客网站的内容还不错,欢迎将靠谱客网站推荐给程序员好友。

本图文内容来源于网友提供,作为学习参考使用,或来自网络收集整理,版权属于原作者所有。
点赞(47)

评论列表共有 0 条评论

立即
投稿
返回
顶部