我是靠谱客的博主 害怕玉米,这篇文章主要介绍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;

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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消息转给被叫端。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
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,等终端主动连接。

复制代码
1
2
3
4
5
6
7
8
9
10
11
// 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:的处理分支上,主要关键点代码如下:

复制代码
1
2
3
4
5
6
7
8
9
// 通过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消息处理。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
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是一个模板函数。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
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方法(这里在查看时因篇幅原因,一些无关的代码会被删除)

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
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连接。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
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。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
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;因此有下面这句控制流程转换。

复制代码
1
2
3
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;各个消息内部怎么处理,就自行查看吧。

复制代码
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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内容请搜索靠谱客的其他文章。

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

评论列表共有 0 条评论

立即
投稿
返回
顶部