一、前述
在gnugk的源码实现中,关于路由策略相应的配置主要是集中在Routing Configuration章节中。仔细阅读gnugk的使用手册,首先可以了解以下几点主要内容:
- 对于Gnugk实现来说,路由意味着对每一路通话找一个合适的目的IP
- 路由策略的应用时机是对每一路通话的第一个消息生效;而所谓的通话的第一个消息具体情况是指,对于注册终端是ARQ消息;对于未注册终端是Setup消息;对于GK的友邻(neighbors)是LRQ消息;对于其它GK转发的带有ForwardOnFacility原因的Facility消息;
- 路由策略可针对通话的每一个消息具体配置,相应的配置项对应为[RoutingPolicy::OnARQ], [RoutingPolicy::OnLRQ], [RoutingPolicy::OnSetup] 和 [RoutingPolicy::OnFacility]
- 路由查找过程是把通话在一条路由策略链上进行传递判断,策略链上每个具体的路由策略可以判断决定是否应用本策略到该通话上,以及决定是否终止继续向后传递通话给其它路由策略。
- 当前gnugk所支持的所有路由策略类型是:explicit、internal、parent、neighbor、dns、sql、http、ldap、vqueue、numberanalysis、enum、srv、rds、forwarding、catchall、lua、neighborsql、uriservice。
路由策略的类型很多,这里不可能一一讲解说明,每一种具体的路由策略的定义和规则,需要在学习源码时自行好好查看,比如,最经典的策略是internal,是指在gk的内部注册表中判断查找被叫并路由通话。
在开始说明代码实现前,这里先以gk手册的一个例子,说明一下路由策略的具体功能;
[RoutingPolicy]
h323_ID=dns,internal
002=neighbor,internal
Default=internal,neighbor,parent
在上面这个例子中,其具体的配置信息是:
首先,这是一个默认路由的配置项,因为其配置项名称是[RoutingPolicy],而不是针对具体通话消息的路由配置项,如[RoutingPolicy::OnARQ];
其次,这个默认路由配置项里面约定了三条路由规则;这三条路由规则中的Default表示的是默认规则;
最后,可以看到每条路由规则里面都有多个具体的路由策略。
那么上面这个例子的具体路由意思是:
如果通话的被叫方的账号类型是h323_ID,则优先匹配到h323_ID=dns,internal这条路由,由dns策略优先判断是否能解析路由通话,若不能再提交给internal策略,在内部注册表中解析判断;若还是不能,则无法完成匹配,通话无法被路由;
如果被叫方的账号前缀是002开头,则生效第二条路由规则002=neighbor,internal进行查找;先neighbor策略查找判断,再internal策略判断的顺序;
除了上述两种场景的其它被叫类型,生效最后的默认规则Default=internal,neighbor,parent;
二、整体UML类图
整体结构性类图如下:
具体路由策略类图如下:
对于路由的实现,主要是在routing.cxx文件中,从上图中可以看出,在源码实现中,角色主要划分为3种:
- 第1种,定义请求类型,由Request模板类来实现;依据通话的第一个消息类型,定义出4类请求:AdmissionRequest、LocationRequest、SetupRequest、FacilityRequest;
- 第2种,定义路由策略,由Policy类衍生出各具体策略的子类;
- 第3种,定义路由判断的目的,由Route类实现;一个Route实例就表明一条符合要求的路由;
为了串接依据不同请求类型,检索判断各类路由策略,找到符合要求的路由目的地,定义出解析执行类(Analyzer),把上述三类角色串连起来;
而对于构建架构的Policy类来说,其具体的子类,主要是通过重载其对应的OnRequest方法,来达到具体的路由判断;
三、具体的执行流程
下面我们从程序main函数启动开始,把涉及的具体过程讲述一遍;这样可以跟着串连各个类之间是如何具体关联起来的。
3.1 配置文件
对于gk的配置,主要是通过ini文件来完成。详细的配置项如下。
[RoutingPolicy]
default=explicit,internal,parent,neighbor
[RoutingPolicy::OnARQ]
h323_ID=vqueue,internal
default=explicit,internal
[RoutingPolicy::OnLRQ]
0048=internal
default=neighbor
[RoutingPolicy::OnSetup]
dialedDigits=internal,neighbor
default=explicit,internal,parent,neighbor
[RoutingPolicy::OnFacility]
default=internal
如同前面所说的,可针对具体消息作路由策略配置,也可以配置默认的策略;因此,最多可有上面的5种配置项;但是在说明的一点是,这些配置项都是可选的,并不要求同时配置。
上述所涉及的路由规则配置类型有h323_ID、dialedDigits、default、及数字前缀(如0048);其实h225的地址别名类型总共有8种,定义在h225_1.cxx,这些都可以作为路由规则配置;
const static PASN_Names Names_H225_AliasAddress[]={
{"dialedDigits",0}
,{"h323_ID",1}
,{"url_ID",2}
,{"transportID",3}
,{"email_ID",4}
,{"partyNumber",5}
,{"mobileUIM",6}
,{"isupNumber",7}
};
3.2 注册现有的路由策略类型
namespace { // anonymous namespace
SimpleCreator<ExplicitPolicy> ExplicitPolicyCreator("explicit");
SimpleCreator<InternalPolicy> InternalPolicyCreator("internal");
SimpleCreator<ParentPolicy> ParentPolicyCreator("parent");
SimpleCreator<DNSPolicy> DNSPolicyCreator("dns");
SimpleCreator<VirtualQueuePolicy> VirtualQueuePolicyCreator("vqueue");
SimpleCreator<NumberAnalysisPolicy> NumberAnalysisPolicyCreator("numberanalysis");
SimpleCreator<ENUMPolicy> ENUMPolicyCreator("enum");
SimpleCreator<SqlPolicy> SqlPolicyCreator("sql");
SimpleCreator<HttpPolicy> HttpPolicyCreator("http");
SimpleCreator<CatchAllPolicy> CatchAllPolicyCreator("catchall");
SimpleCreator<URIServicePolicy> URISevicePolicyCreator("uriservice");
}
在完成了各策略类的定义后,全部都通过模板工厂,进行注册;其中这里使用的是SimpleCreator模板,要使用该模板,要求工厂产品类型的类要定义一个Base数据类型,因此在PolicyList模板类中,可以看到 typedef T Base; // for SimpleCreator template,这样的一个定义。这个注册过程是定义在routing.cxx文件中,这里的xxxCreator变量就是全局变量,因此在main执行前就加载完成了。
3.3 程序启动,读取配置项
在前面有讲到串连这整个路由策略实现的是由Analyzer单例类来完成,因此,在程序启动后,在RasServer::LoadConfig中通过这个Analyzer单例类,实现了对路由策略配置的加载。
Routing::Analyzer::Instance()->OnReload();
来看下具体的加载过程。
void Analyzer::OnReload()
{
WriteLock lock(m_reloadMutex);
//总共有4类具体路由规则类型,分别针对ARQ/Setup/LRQ/Facility消息,因此这里m_rules的大小为4;
for (int i = 0; i < 4; ++i) {
Rules & rules = m_rules[i];
// 删除原来的加载信息
DeleteObjectsInMap(rules);
rules.clear();
/* 获取对应的配置项下的所有路由规则的配置;SectionName的具体定义为:
const char *SectionName[] = {
"RoutingPolicy::OnARQ",
"RoutingPolicy::OnLRQ",
"RoutingPolicy::OnSetup",
"RoutingPolicy::OnFacility",
"RoutingPolicy"
}; 这里也暗示着各类型对应顺序,如ARQ对应i=0
*/
PStringToString cfgs(GkConfig()->GetAllKeyValues(SectionName[i]));
if (cfgs.GetSize() == 0) // no such a section? try default
//如注释所说,对应的配置项没有配置时,尝试使用默认配置项,即RoutingPolicy
cfgs = GkConfig()->GetAllKeyValues(SectionName[4]);
// 读取解析每个具体的路由规则配置
for (PINDEX j = 0; j < cfgs.GetSize(); ++j) {
PString prefix = cfgs.GetKeyAt(j);
if (prefix *= "default")
prefix = "*";
// 规则类型可能是,;|这些符号分隔的,如dialedDigits,h323_ID=internal,neighbor,
// 所以要进一步处理下。
PStringArray prefixes(prefix.Tokenise(",;|", false));
for (PINDEX k = 0; k < prefixes.GetSize(); ++k)
// 通过Analyzer::Create方法,把字符串形式的路由策略,转化为链表形式的policy类;
// 实际上是转为调用了PolicyList<T>::Create的静态方法;在该方法内很关键的是
// 通过模板工厂创建出各对应的policy子类,Factory<T>::Create(id[0]
rules[prefixes[k]] = Create(cfgs.GetDataAt(j).Trim());
PTRACE(1, SectionName[i] << " add policy " << cfgs.GetDataAt(j) << " for prefix " << prefix);
}
// default policy for backward compatibility
// 任何配置项都没有配置时,则采用内部约定的路由策略
if (rules.empty())
rules["*"] = Create("explicit,internal,parent,neighbor");
}
}
3.4 实际通话中如何应用
即然路由策略具体来说有4类,那么相对应的应用时机也就是这4类消息(ARQ/Setup/LRQ/Facility)的处理;具体对应关系是
- ARQ: AdmissionRequestPDU::Process函数
- Setup:CallSignalSocket::OnSetup函数
- LRQ:template<> bool RasPDU<H225_LocationRequest>::Process函数
- Facility:CallSignalSocket::ForwardCall函数
这里以AdmissionRequestPDU::Process为例,具体点明是如何应用的。
// routing decision,决定路由
bool toParent = false;
H225_TransportAddress CalledAddress;
bool connectWithTLS = false;
// 定义相对应的路由请求,这里是AdmissionRequest
Routing::AdmissionRequest arq(request, this, authData.m_callingStationId, authData.m_clientAuthId);
if (!answer) {
if (!authData.m_destinationRoutes.empty()) {
list<Route>::const_iterator i = authData.m_destinationRoutes.begin();
while (i != authData.m_destinationRoutes.end())
arq.AddRoute(*i++);
} else
// 若鉴权过程中没有指定目的路由,则由这里来启动相应的路由策略判断,来确认是否路由;
// 这里实际是调用到了Request<R, W>::Process()方法;
// 在该方法,借助Analyzer::Instance()->Parse(*this)解析判断;
arq.Process();
下面具体看下解析判断过程:
template<class R, class W>
inline bool Request<R, W>::Process()
{
/* Analyzer重载了4个Parse方法,分别对应4类不同的消息;
bool Parse(AdmissionRequest &);
bool Parse(LocationRequest &);
bool Parse(SetupRequest &);
bool Parse(FacilityRequest &);
,这里实际是Parse(AdmissionRequest &)方法;
*/
return Analyzer::Instance()->Parse(*this);
}
bool Analyzer::Parse(AdmissionRequest & request)
{
ReadLock lock(m_reloadMutex);
request.SetRejectReason(H225_AdmissionRejectReason::e_calledPartyNotRegistered);
// 因为是ARQ,所以对应的路由规则定义是m_rules[0],这个是由SectionName的定义的顺序决定的;
// 因为可能有多条规则定义,通过ChoosePolicy查找最匹配的路由规则定义
Policy *policy = ChoosePolicy(request.GetAliases(), m_rules[0]);
// 从每条路由规则的第一个策略开始执行处理;内部处理细节就自行查看吧,需要留意的是每个具体的路由策略内部是通过OnRequest重载了对4类消息的处理
bool policyApplied = policy ? policy->HandleRas(request) : false;
if (!policyApplied && request.HasRoutes()) {
// 因为可能会有多个具体路由策略都可以路由本次ARQ,因此可能前面已经添加了可路由信息,
// 往后继续判断路由时,某个路由策略又返回了false,认为无法路由,
// 这种情况下就打印提示下第一个匹配上的策略;除了提示,也没有其它具体作用。
Route fallback;
request.GetFirstRoute(fallback);
const char * tagname = request.GetWrapper()
? request.GetWrapper()->GetTagName() : "unknown";
const unsigned seqnum = request.GetRequest().m_requestSeqNum.GetValue();
PTRACE(5, "ROUTING\t" << fallback.m_policy
<< " applied as fallback to the request " << tagname << ' ' << seqnum);
}
return policyApplied || request.HasRoutes();
}
3.5 总结流程
- 在定义相应的路由请求类型(AdmissionRequest、LocationRequest、SetupRequest、FacilityRequest),比如 Routing::AdmissionRequest arq(…);
- 调用Request的Process方法,即arq.Process(); 依据类型,实际调用的是会是Analyzer的bool Parse(AdmissionRequest &)、bool Parse(LocationRequest &)、bool Parse(SetupRequest &)、bool Parse(FacilityRequest &)的其中之一;
- 在Analyzer::Parse内部,又进一步会调用Policy::HandleRas、Handle(SetupRequest & request)、或者Handle(FacilityRequest & request);
- 在Policy的Handle方法中,又进一步调用Policy::OnRequest方法,处理四类不同的路由请求类型;
- 各子类Policy,通过重载OnRequest方法,实现各子类自身的判断逻辑。