田文武
伴随着Internet技术在全球范围内的日益普及,诸多基于互联网架构的新业务正如雨后春笋般涌现,VoIP(Voice over IP)就是一种典型的互联网新兴技术。和传统的电路交换通信技术相比,VoIP技术以IP数据包的形式,传输通信过程中,产生的所有信令与实时音视频数据,从而可以实现更为低廉的通讯成本。同时,由于基于开放的IP网络架构,结合当前最新的处理器芯片和嵌入式操作系统技术,VoIP终端可以实现许多传统电话无法提供的新应用,如传真、视频、数据等,最终为用户提供更为便利的通信服务。本文所介绍在线电话簿、实时天气预报功能,就是对VoIP终端产品功能扩展的一次积极尝试。
图1 HTTP客户端分层设计框图
首先,该功能模块是基于ARM-LINUX嵌入式平台开发的,在系统中处于中心地位的是一个独立的VoIP进程,它包含了VoIP终端主要的功能实现。
HTTP客户端模块包含:HTTP客户端应用接口层,它可以被VoIP进程中各线程共享调用;HTTP客户端线程,它可以满足VoIP进程中,其他线程实时并发地访问远端HTTP服务器的需要;客户端API的语法编解码模块,它服务于HTTP客户端API,使其能够对HTTP报文、XML/XHTML数据等进行高效的解析与构建。HTTP客户端的功能设计框图,如图1所示:
图1中,MMI(Man-Machine Interface)线程主要负责用户与VoIP终端的人机交互任务。通过MMI线程,用户访问远端服务器数据的场景有两种:一种是手动访问的方式;另一种是自动访问的方式。对于手动方式,MMI线程可以直接调用HTTP客户端API函数进行访问;但这种方式存在如何有效缩短手动访问时延的问题。另外一种自动访问的方式,是通过独立的HTTP客户端线程来实现的;该方式面临着另一个问题:如何实现MMI线程与HTTP客户端线程之间的数据同步问题。上述两个问题的具体分析与解决,均将在第三节中进行阐述。每次对远端服务器数据的访问,无论以手动方式还是自动方式,均遵循相同的基本流程,如图2所示:
图2 通过HTTP客户端API访问远端服务器流程图
上层模块(MMI线程或HTTP客户端线程等)传递相关的参数给HTTP客户端API层;
API层调用相应的报文编码函数根据上层传递的参数构建出请求报文;
调用Linux套接字API建立与远端HTTP服务器的TCP连接;
一旦连接建立成功,发送请求报文,同时等待接收响应报文;
当成功接收到响应报文,HTTP客户端API层将调用相应的报文解析函数对报文进行解析;
将解析得到的数据返回给上层模块,并关闭TCP连接。
如果连接建立失败、响应超时或失败,同样要将错误的结果返回给上层。
图2描述了HTTP客户端API层访问远端服务器的过程。调用socket()创建一个socket文件描述符,其内核属性默认为阻塞模式。当建立连接或接收响应数据时,如果直接调用connect()或recv()对创建的socket描述符进行操作,调用结果返回之前,当前线程会被挂起,该调用方式成为“阻塞调用”。在“阻塞调用”模式下,如果远端服务器不存在或服务器端不返回数据,connect()/recv()函数就会长时间无法返回,进行该调用的线程将被长时间挂起,而这对于用户而言显然是无法接受的。如何解决该问题?
Unix/Linux中,系统调用select()经常在阻塞调用的非阻塞化场合被用到。其函数声明如下:
select()函数提供了一个fd_set的数据结构,它是一个长整型的数组,每一个数组元素都能与一个打开的文件描述符(如socket描述符)相关联。当调用select()时,由内核根据I/O状态修改fd_set的内容,由此来通知执行了select()的进程哪一个socket或文件可读写。同时select()还提供了一个timeval结构,在调用时,如果超过了timeval参数所指定的时间长度仍然没有文件描述符满足条件,select()就会直接返回。通过timeval参数可以定制符合用户要求的最大等待时间(建立连接、接收响应数据等)。
非阻塞连接建立过程如下:
A. 建立socket,将socket文件描述符设为非阻塞模式;
B. 调用connect()函数,建立与socket绑定的连接(该调
用会立刻返回);
C. 调用select()检查socket是否可读写;
D. 根据select()返回值判断connect()的结果:成功、超时或失败;
E. 将socket重新设置为阻塞模式。
区别于非阻塞连接过程,由于网络数据传输本身的时延效应,需要在不改变socket描述符阻塞属性的情况下,通过动态轮询的方式来实现非阻塞的数据接收过程,如图3所示:
图3 非阻塞数据接收过程
N表示最大轮询次数,以SELECT_T 代表Select()调用的超时时间,则整个数据接收过程的时间延迟最大为
实际测试表明,通过select()函数实现的非阻塞网络通信机制,可以实现通信过程中实时性和可靠性的有效平衡。
VoIP进程的每个线程均是基于一种消息或事件驱动的自循环模型设计的。
这种由消息或事件驱动的自循环线程模型,如图4所示:
图4 消息/事件驱动的自循环线程模型
在每次循环过程中,线程检查是否有新的消息传入或事件发生;若发现新的消息或事件,则进行相应的处理,完成后进入下次循环过程,若当前没有新的消息或事件,则继续等待消息/事件的到来,直到定时器超时进入下次循环。
HTTP客户端线程的特殊之处在于,它一直等待请求消息而不会超时,一旦受到请求消息就立即处理,并返回响应消息给外部请求发送者,然后进入下一个等待周期。
当MMI线程需要自动访问远端服务器时,直接向HTTP客户端消息队列发送一个请求;处于“等待”状态的HTTP客户端线程收到MMI线程的请求消息,会立即提取消息中的服务器地址和HTTP请求相关的参数,然后按照图2所示的流程通过HTTP客户端API层与远端服务器进行交互;当收到HTTP客户端API层的返回数据时,HTTP客户端线程就会将这些数据以一定的组织形式,发送到MMI线程的消息队列,然后重新进入侍候状态;当MMI线程收到来自HTTP客户端线程的消息时,该自动访问过程结束。
3.1.1 手动搜索功能
该功能通过MMI线程直接调用HTTP客户端API实现,基本流程为:用户输入搜索条件;MMI线程调用HTTP客户端API向远端服务器发送HTTP搜索请求;如果搜索返回成功,则将搜索结果提供给用户查看,否则,提示用户错误信息。
3.1.2 自动查询功能
该功能通过图5所示的MMI与HTTP客户端线程的同步机制实现,基本流程,如图5所示:
在线天气预报属于MMI线程中的常驻功能,一旦MMI线程启动,就会直接触发它的运行.
检查在线天气功能是否开启,如未开启,则重置计时器,等待其超时再次检测功能是否开启,否则进入下一步;
向HTTP客户端线程发送信息查询请求;如客户端线程成功返回响应消息,则进入下一步,否则重新进行上一步;
将HTTP客户端线程返回的天气信息显示在LCD屏上;
重置定时器;
等待定时器超时,返回第一步再次更新天气信息。
本文针对当前VoIP终端特色功能扩展的需要,设计了一种轻型、高效的HTTP客户端,并对该客户端的设计架构和实现中遇到的关键问题进行了阐述。由于采用了相对开放的架构设计,实现了相互独立的HTTP客户端接口层、HTTP客户端线程以及语法编解码模块,因此,除了已实现的在线号码簿和在线天气信息外,还可以基于该客户端,进行其他类似的功能扩展。实际测试表明,该客户端具有实时性高、稳定性强、系统资源开销少等特点,达到了预期的设计指标。
[1]Wikipedia: Voice over IP[G/OL], http://en.wikipedia.org/wiki/Voice_over_IP, 2011.
[2]Neil Matthew, Richard Stones. Beginning Linux Progrmaming 4th Edition[M]. 北京,人民邮电出版社 ,2010.
[3]宋敬彬, 孙海滨. Linux网络编程,[M]北京,清华大学出版社, 2010.
[4]谢希仁. 计算机网络(第5版)[M].北京电子工业出版社,2008.
[5]许信顺. 嵌入式 Linux应用编程[M], 北京,机械工业出版社, 2007.
[6]Internet Engineering Task Force, Hypertext Transfer Protocol - HTTP/1.1[S], http://tools.ietf.org/html/rfc2616,1999.