前面我们分析了一些协议,以及协议栈的内容。相信大家也都了解了H.323协议的内容了。那么这里我我们就针对这个协议栈开发内容进行一下讲解。开发H.323协议栈是通信设计过程中的一项极其艰巨的任务,难点主要在于:复杂的协议栈开发需要投入数年的工程设计资源,而且掌握这些复杂的标准还需要一个知识的积累和学习过程,本文将探讨开发H.323协议栈和VoIP应用系统遇到的问题、歧义、困难等。
利用基于原语(primitive)的H.323协议栈开发IP承载话音(VoIP)应用系统不是一项小任务,因为创建一个鲁棒的应用系统,需要花很多时间去研究含糊的标准和复杂的状态机。
本文用例子说明流程的实现以及原语(primitive)的定义,用以说明如何构建一个基于原语接口的应用系统和一个基于简单接口的替代方案。在开发协议栈之前,建议浏览一下H.323的基本标准。H.323是国际电信联盟(ITU)颁布的标准,由一系列特定协议组成,包括Q.931、H.225、H.245和ASN.1。为了提供呼叫信令功能,H.323部分融合了H.225和Q.931标准。H.245定义了多个流程,以便于进行能力信息互换(exchange capability)、主从判断(master-slave determination)和信道(channel)信令。最后,ASN.1规定了数据格式,使兼容H.323的端点能够互通。
基本概念
使用H.323时,正确理解原语和流程这两个术语很关键。原语用来描述应用层和H.323协议栈下层之间传递的结构或消息。H.323定义了多个原语,有四种类型:请求、指示、响应和确认。每个原语的参数的数量是可变的,这由相关流程决定。这些参数表示应用层和协议栈下层的通信信息。
在H.323协议族中的每个协议定义了一组流程。每个流程代表一个状态机,在大多数情况下,该状态机用原语的形式规定一组消息,这些消息以特定的顺序发送和接收。这些原语便于应用层和下层的通信。
流程提供具体的功能,可以异步启动或终止,或启动后在整个对话过程中保持激活状态。例如,H.245流程包括主从判断、能力信息互换、单向和双向信道信令。其中,只有信令信道在实际的对话过程中保持激活。其它只是激活后发送和接收数据,然后就终止了。Q.931/H.225流程包括呼叫建立和拆除。图1表示一个完整的H.323协议栈开发的实现。值得指出的是,该实现依赖于网络协议栈和实时操作系统(RTOS)。多数应用系统需要RTOS以便同时处理多个流程和/或呼叫。
#p#
呼叫信令
如上所述,H.323融合了Q.391和H.225协议,可提供呼叫信令功能。实际上,Q.931是ISDN相关的协议,用于建立和拆除呼叫。尽管从来没有打算应用于VoIP应用系统,但是通过在该协议上增加信息,可以为H.323提供比较类似的相关功能。
Q.931分组(packet)包含多个称为信息单元(information element)的参数。例如,Q.931分组可以包含一个用户信息单元。H.323规定用户信息单元必须包含一条H.225消息。H.323的附加信息存于此。有关网关、网守(gatekeeper)和协商的大部分信息由H.225承载。
Q.931和H.225定义呼叫信令,而H.245定义许多呼叫业务。最常用的业务包括主从判断、能力信息互换、信道信令。当Q.931建立起呼叫,这些流程启动。此时,两个终端已经同意互连,但是还没有收发多媒体数据。
主从判断流程协商决定哪个终端是主,哪个是从。该流程可应用于:将一次协商中相同类型的媒体数据流联系起来;避免和解决由于编解码器间的依赖关系造成的冲突。
能力(capability)信息互换流程告知远程终端的音频、视频或数据能力。这可以避免能力猜测过程(即建立一个信道并发送远程终端可能无法识别的数据)。
逻辑信道信令过程协商建立实时协议/实时控制协议(RTP/RTCP)信道,用于收发多媒体数据。
定义数据
抽象语法表示法(ASN.1)标准详尽说明了怎样表示语法或结构化数据分组,它用于在本地和远程端点间发送H.225和H.245消息。X.691规定了在ASN.1语法结构和网络接收的原始数据之间的编码和解码方法。H.225和H.245等ITU标准都为所有的协议消息规定了ASN.1语法结构。
RTP和RTCP也包括在H.323之中。RTP定义了一个消息头,附加到多媒体数据分组的前端,并通过用户数据报协议(UDP)发送。消息头包含了有关多媒体数据的信息,包括顺序号和时间戳。RTCP用这些数据来收集网络性能统计信息,例如分组间的抖动(测量分组到达时间的不规则性)和分组片段的丢失。
协议栈开发
开发H.323协议栈是一项艰巨的任务。困难产生于标准定义不详尽而且不一致。标准的模糊导致互操作问题,且所实现的协议栈移植性差。
Q.931和H.225定义了呼叫信令流程,但是定义不够充分。与H.245相比,Q.931和H.225定义的原语缺乏充分的文字说明。另外,产生混乱的原因是不完整的ASN.1标准,因而开发人员需要将X.691编码格式数据反向转换。RTP/RTCP、H.245和应用层间的关系也存在问题。
在H.323中,Q.931和H.225协议进行了融合,但是融合不够好。尤其是Q.931规范包含一些描述很充分的流程图,这些流程图显示相关消息、原语和超时之间的关系。而H.225将Q.931中的多个消息标记成“禁用”,但却没有规定反映这些变化的新流程。这样,H.225缺乏足够的信息,因此,开发者得到的文档不完善。
与此相反,H.245是一个定义清晰的协议,具有大量的流程图。与Q.931和H.225不同,它规定了每个原语的参数。这是极其有用的,而且这表明了Q.931和H.225的缺陷。尽管Q.931是基于原语的,但是没有规定原语的字段。整个H.225标准只有一次提到原语而且没有提供包含参数的信息。为每个原语选择字段的工作留给了开发者,因此,Q.931的接口变成专有的和不可移植的接口。
影响H.323协议栈开发学习曲线的主要方面是ASN.1。尽管ASN.1详细说明了怎样描述语法,但是,将语法结构编码成字节流的方法有多个。X.691规定了打包编码原则(PER),是H.225和H.245使用的编码规则集。不幸的是,X.691的不足削弱了ASN.1的优点。ASN.1具有扩展给定语法的能力,而且能够以完全后向兼容的方式编码。但是,X.691只粗略地解释了怎样进行编码扩展。为了弥补X.691标准的不足,需要做大量的反向工程工作。通过购买现成的协议栈开发产品可以避免该任务。
RTP/RTCP和其它H.323相关协议的结合引入了更多难于捉摸的标准问题。为了设计一个模块化的H.323协议栈,需要在标准规定的范围内仔细定义各协议间通信的信息结构。H.323标准不能清晰地描述各协议的互通性。RTP/RTCP就是这种缺陷的一个范例。#p#
实时协议问题
RTP/RTCP是设计者的大难题,因为很难从标准中推断出它与其它协议的关系。可能有两种选择:在应用层进行RTP/RTCP处理或者在下层协议中处理。
如果在应用层处理RTP/RTCP,应用程序必须知道RTP信道使用的端口号。因为使用原语进行协议栈通信,H.245原语必须能够将所有需要的端口信息传送给应用层。但是,这些原语没有描述远程主机端口号的参数。因此,需要以非标准方式修改这些原语,增补缺少的信息。
如果在下层处理RTP/RTCP,协议栈开发需要有关编解码器的信息以便调用合适的设备驱动程序。然而,该解决方案也不够合理,因为协议栈必须知道特定的设备信息,而这是不可移植的。
创建VoIP应用系统
为了更好地理解如何利用H.323协议栈实现VoIP应用系统,让我们看几个例子,它们展示了下层协议和应用层之间的交互关系。
要利用基于原语的H.323协议栈实现的基本的VoIP应用系统,必须实现一组由H.323标准定义的流程。这些流程由可重入状态机组成,基于输入或输出原语的消息由状态机执行。另一个替代方案是面向任务的,为每一个流程启动一个新线程。无论哪种方案,用原语表示的输入或输出消息都是异步发生的。应用系统程序必须把状态值保持在这些流程中,而且某些流程可能会有多个运行实例。
每个原语包括多个参数,这些参数必须在应用层定义。在Q.931中,参数相当简单且易于理解。然而,当学习H.245协议时,阅读复杂的ASN.1表结构是很困难的。有关能力集(capability set)流程的原语尤其复杂,ASN.1结构可能嵌套5到6层深。对于不熟悉ASN.1的人来说,这可不是简单工作。
本地流程时序
另一个核心开发问题是时序,即为了建立或拆除对远程主机的呼叫,本地流程所执行的时序。当考虑其它H.323实现或应用的互操作性时,研究时序问题尤其必要,这类系统的实例有Microsoft的NetMeeting和NetSpeak的WebPhone。尽管H.323规范揭示了流程之间的依赖关系,必须通过实验测试和反向工程来揭示发起呼叫并建立通信需要的时序。
#p#
图2展示了与远程终端建立通信关系时H.323协议栈必须执行的流程。注意,同一水平线上的流程可能同时运行,但是他们都完成后该时序才能继续下去。
Q.931呼叫建立流程启动呼叫建立过程并且通知远程终端有一个呼入。当呼叫建立起来后,某个终端可能启动H.245规定的主从判断流程或能力信息互换流程。每个终端都需要执行能力信息互换流程,但是只要一个终端执行主从判断流程就可以了。主从判断和能力信息互换完成后,逻辑信道打通了。最后,该对话通过另一个Q.931流程关闭。
尽管该时序看起来直接明了,而且一些依赖关系在标准中定义的比较松散,因而很难实现该时序。因为仅依赖关系就占了H.245规范的257页还多,实现时很容易疏忽。
另一个导致混乱的问题起因于异步执行的流程。例如,主从判断流程可以在能力信息互换流程之前或之后执行,而且可能同时或者相互覆盖执行。更有甚着,能力信息互换流程可能在一个闪断信道(on the fly once channel)上执行。这样可以在对话期间动态改变编解码器,然而给协议栈开发增加了工作负担。
实现流程
H.323定义Q.931为呼叫信令协议,在此,将描述怎样实现实际的流程。基于原语的H.323协议栈开发要求应用程序开发者定义原语并用其与下层通信。为了方便描述呼叫建立流程,我们从Q.931规范的25页文档中归纳出一个流程图(如图3)。
当实现呼叫建立流程时,首先发送建立请求消息,然后该流程等待一条告警指示消息。当该指示消息接收到后,该流程再次等待一条确认消息。如果这条确认消息也接收到了,该流程终止,应用程序可以开始处理H.245流程。
为了开发基于原语的H.323协议栈流程的状态机,开发者需要精通H.323协议,例如上述Q.931呼叫建立协议。注意,H.245流程比Q.931更具有面向状态的特点。每个H.245流程必须按照标准规定的时序处理接收到的指示消息并发送请求消息。每个状态机的具体实现将需要数月时间。
如果采用替代方案,H.323协议栈开发不使用原语,协议栈需要包括一个已经实现了上述流程和状态机的中间层,并提供一个简化的应用编程接口(API)。对于前面的例子,协议栈要发一个呼出,只需要调用下面这一个函数即可:
在使用API实现的系统中,makeCall()函数接受远程端点的主机名字(hostname)和IP地址,并执行所有呼叫远程终端的步骤。该方案需要一个流程构造前述的原语,实现处理所有输入输出原语的状态机。使用基于API的协议栈不需要理解原语接口,可以节省数月的开发时间。
给原语参数赋值
前文的例子描述了流程的实现。下面的例子展示怎样给原语赋值,以能力信息互换流程的“TRANSFER.request”原语为例。
“TRANSFER.request”原语有四个字段,用ASN.1格式填充。这四个字段是PROTOID、MUXCAP、CAPTABLE 和 CAPDESCRIPTORS。在此,我们集中讨论CAPTABLE参数,它是特定终端支持的所有编解码器的列表。在此例中,填充的CAPTABLE参数表示以下终端能力:单一G.711 A律64k编解码器,能够接收的分组长达180ms音频数据。下面的伪码是初始化一个ASN.1结构元素的基本步骤。
CAPTABLE参数实际上是CapabilityTableEntry的数组。填充该参数的第一步是为该数组分配内存空间。每个被支持的编解码器都需要一个CapabilityTableEntry。在本例中,数组只有一个元素,因为只支持G.711编解码器。每个CapabilityTableEntry有两个元素:TableEntryNumber字段和可选的能力信息结构。
CAPTABLE[0].Capability.TableEntryNumber = 1 (1)
在行1的语句中,CapabilityTableEntryNumber任意设置,但是在同一消息中取值要不同。该参数由CAPDESCRIPTORS参数使用,以描述编解码器之间的依赖关系。CAPDESCRIPTORS结构要复杂得多,不在本文讨论范围内。
能力信息结构描述了至少12种基本能力/业务中的一种。该结构是可选的,但是不选用的情况不多。在特定的应用方式下,ReceiveAudioCapability被选用。像ReceiveAudioCapability的AudioCapability结构包含14多种不同的编解码器中的一种。用户必须选用其中一种编解码器。一旦选用了某特定的编解码器,相关字段必须定义。在g711Alaw64k情况下,只需要一个字段。第二行语句表示编解码器驱动器能够处理的分组大小至多180ms。
CAPTABLE[0].capability.receiveAudioCapability.g711Alaw64k = 180 (2)
值得注意的是,这个简单例子在一个参数中只定义了一个编解码器。其它原语和参数如CAPDESCRIPTORS要复杂得多。处理这种原语的过程枯燥、耗时且会给项目造成不必要的困难。
如果采用替代方案,开发者使用简单的API协议栈,则不需要关心这些细节。只要给出用ASN.1正确描述的编解码器驱动器,一个在用户层的简单的函数调用就能处理所有这些细节:
独立进行简单API协议栈研究和开发,只需投入数百个工时去解决有关ASN.1的问题就可以了。在应用层,仅仅RegisterCodec()函数就可以为开发者节省相当多的时间。当成本和上市时间最重要时,该协议栈开发的简单性具有不可估量的价值。