Design and Implementation of a Cross-platform Protocol-independent Network Class Library
JIANG Jin-peng1, BAO Cong-xiao2, Li Xing1
(1. Department of Electronics Engineering, Tsinghua Univ., Beijing 100084, China;
2.Network Center, Tsinghua Univ., Beijing 100084, China)
Abstract: Although the socket API is the defact standard of network programming interface nowadays, it suffers from some shortages as high complexity of interface, poor support for protocol-independent programming, and low cross-platform capability. Concerning these problems, cross-platform and protocol-independent network programming techniques are studied. Furthermore, a cross-platform protocol-independent network class library is implemented with C++, which supports both Windows and Linux, protocol-independent network programming, unicast, ASM and SSM multicast, supports both UDP and TCP transmission,and standard I/O stream based on TCP communication. It validated that this class library has accomplished the cross-platform and protocol-independent design goal through application in a practical network measurement system.
Keywords: cross-platform; protocol-independent; network API; iostream; class library
1 引言
1983年socket API随着BSD4.2[1]系统一起发布,发展到现在已经成为网络编程事实上的标准。在几乎所有主要操作系统上,网络编程的底层接口都源自socket API。许多高级编程语言(如Java或C#)都在自己的标准库中加入了对网络编程的支持,但这些支持通常仅仅是对底层socket API的简单封装,并没有引入革命性的变化。
在二十几年的发展历程中,socket API经历了互联网的迅猛发展,各种新技术、新协议、新应用层出不穷。由于socket API接口设计的前瞻性和灵活性,在一次次变革的浪潮中,socket API仅仅通过引入一些小的修改就可以适应新技术。例如仅仅通过增加IPv6地址结构和一些socket选项就适应了IPv6[2]和组播编程,而没有修改任何一个socket API接口定义。
虽然现在socket API取得了巨大的成功,但它也逐渐暴露出一些不足:
(1)接口多而复杂,不方便记忆和使用。由于socket API需要保持向前兼容,一些不推荐使用的结构与函数仍然在被大量使用。setsockopt和getsockopt接口虽然带来了很强的通用性和可扩展性,但使用起来非常不方便不直观。
(2)协议无关性差。编写同时支持IPv4和IPv6的简单代码仍然是一件需要深入理解socket API和细心编程的麻烦工作。稍有不慎就会写出协议相关的代码。
(3)不完全跨平台。尽管各种操作系统的socket API都源自Berkeley socket API,而且都受一些标准的规范(例如POSIX[3])。但它们在各自对标准的解读过程中和实现上都存在一些细节的差异,这些差异导致基于socket API编写的程序通常并不能直接拥有跨平台特性。
基于上述理由,需要一个新的易用的跨平台协议无关的网络编程接口。本文提出并使用C++实现了这样一个类库。选择C++的原因是它支持高级面向对象设计的同时还保留了对底层的高效率访问能力。
2 类库结构
首先,为了以协议无关的方式对IPv4和IPv6地址进行解析、存储和输出,首先引入了协议无关的地址类:IPAddress。IPEndPoint表示传输层协议TCP或UDP的端地址,包括IP地址和端口两部分。IPAddressInfo相当于addrinfo结构,用于保存DNS解析后的的地址域名信息。Socket Set封装了fd_set,简化了fd_set的使用和最大文件描述符的计算。Socket是类库中最核心的类,它对常用socket API进行了面向对象的封装,它同时支持TCP和UDP,它将所有涉及到IP地址的接口里的地址结构全部用IPAddress或IPEndPoint代替,对外提供协议无关的接口,除支持单播外,还全面支持组播(包括ASM和SSM[4])。
为了进一步方便网络程序的开发,对TCP的流式接口与C++的标准输入输出流[5]进行了融合。从basic_iostreambuf派生出SockStreamBuf,实现了socket流式输入输出的缓冲区。在SockStreamBuf的基础上,通过从basic_iostream派生,实现了SockStream类。这样就可以使用标准C++的输入输出流接口对TCP数据流进行操作了,这大大提高了网络编程的方便性和灵活性。
为了对类库的功能进行扩展并测试基础模块的正确性和稳定性,实现了用于HTTP[6]客户端通信的几个类。由于HTTP的传输可以采用不同的传输编码(Transfer-Encoding),为了支持最常见的不编码和chunked编码方式,通过对basic_iostreambuf的派生以及对SockStreamBuf的聚合,实现了支持两种传输方式的缓冲区类:HttpStreamBuf。 HttpStream类使用HttpStreamBuf实现了采用C++标准输入输出流接口的HTTP流。在HttpStream的基础上又实现了HttpRequest和HttpResponse类,进一步提供了HTTP传输的高级接口,包括对URL的解析、查询字符串操作、HTTP头的解析、Cookie、表单提交(Form submit)、文件上传下载等功能。
3 跨平台程序设计
跨平台程序设计是指使软件可以在多种硬件平台和操作系统上运行的软件设计方法。平台包括硬件平台和软件平台。如果使用高级语言进行软件开发,对硬件平台的适应性通常是指针对字节序、字长的适应。对软件平台的适应性主要体现在对操作系统编程模型和系统调用的适应性。操作系统对计算机设备以及系统对象都建立了抽象的可编程模型,并通过系统调用的方式提供给应用程序使用。不同操作系统的模型和接口会有不同,跨平台设计的主要任务就是适应这些差异。本类库的设计目标是支持32/64位大端机和小端机,支持Windows和主流Unix兼容操作系统。目前测试过的平台为32位小端机上的Windows和Linux。
跨平台程序设计是人们一直追求的设计方法。跨平台最明显的好处是只需要写一次代码,就可以支持多种平台,省去了移植的麻烦。它对开发也是有好处的,开发人员可以在自己最熟悉的平台下使用最熟悉的工具开发调试程序,有助于提高编程效率。通过多种编译器检测可以在编译期发现并纠正更多的错误。在一种平台下不容易发现的软件错误或缺陷,在另一种平台下可能经常发生,有利于发现并修正更多的bug,提高软件的健壮性。除此之外对软件的推广及灵活部署也非常有意义。
跨平台程序设计的难度和选择的编程语言有很大关系。Java号称“Write Once, Run Anywhere”,还有许多脚本语言也支持跨平台,例如perl、python。但JVM[7]和脚本解释器自身相当于一个平台,它们隔离了操作系统和程序,效率较低,而且当需要使用平台相关的特性时将非常麻烦。C++的可移性比这些语言差,但效率得到了最大程度的保留。而且也有许多跨平台C++类库的成功典范,例如Qt, boost,ACE等。
分层软件架构对跨平台程序设计来说也是很重要的。可以在底层类库里解决平台相关性,而底层类库向上提供服务的接口完全是平台无关的。基于这样的底层类库开发的上层应用程序很自然地拥有了同样的跨平台能力。因此跨平台网络类库的开发对于开发跨平台的网络程序意义重大。
为了实现跨平台的网络类库,首先需要对不同操作系统的网络编程模型和接口进行分析,求同存异。对网络编程来说就是对socket API进行分析。Windows和Linux等操作系统上的socket API接口都属于Berkeley socket API的衍生物,它们非常相似,这大大降低了跨平台设计的难度。但它们之间还是存在许多细节上的不同,例如需要的头文件不同;winsock需要调用WSAStartup进行初始化;Windows下的select函数第一个参数被忽略,而Linux下第一个参数必须计算出来;Windows下不支持IPv6的SSM组播,而Linux支持;不同平台下对于AF_INET和AF_INET6等常量定义的值是不同的,这些值不能用于文件存储或网络传输。这些细节差异必须在实现类库时进行合适的处理。
第二步是抽象出平台无关的模型和接口。由于在socket API的背后有众多Internet协议支持,无论在什么平台下都需要遵守这些协议,因此总的来说,网络编程的模型和接口也是一致的。在实现本类库时尽量保留了原socket API接口。但为了保证协议无关或使用方便而对参数类型和个数进行了调整并增加了一些新的接口。例如由于网络的不稳定性,socket操作(尤其是TCP操作)经常会发生阻塞导致程序死锁。本类库在所有可能会发生阻塞的函数接口上都增加了timeout超时参数,当超过指定的时间还没有完成操作时,停止阻塞并通知调用者操作超时。
最后完成对各个接口的实现。当不同的操作系统的实现方法不一致时,使用预编译指令区分不同操作系统,分别给予不同的实现。例如:
#ifdef WIN32
// windows implementation … #else
// unix compatible implementation … #endif
4 协议无关网络程序设计
本文中提到的协议无关都是指IPv4/IPv6协议无关。网络编程类库接口的协议无关性是非常有意义的。IPv4的地址空间将耗尽,IPv6的到来是大势所趋,从软件的长远发展考虑需要支持IPv6。但由于种种原因,IPv6的全面推广不可能短时间完成。可以预见,在相当长的一段时间内Internet上将维持着IPv4和IPv6共存的局面。但如果为了支持IPv6而维护两份代码,将会非常麻烦而且容易出错。在这种情况下,协议无关的网络类库提供了一个很好的解决方案。软件开发人员只需要关注于应用软件本身的逻辑,而无须处理繁杂的协议无关的网络编程工作。更进一步,如果大量的应用软件都同时支持IPv4和IPv6,对IPv4向IPv6的平滑过渡是非常有意义的。
首先协议无关网络编程是可行的。这是因为绝大多数情况下,网络编程是指使用TCP或UDP,在传输层之上进行的socket编程。尽管在网络层上IPv4和IPv6有着种种不同,但在传输层,TCP和UDP的传输模型却没有发生变化。TCP的11状态机[1]仍然有效。表现在socket API上就是编程接口中的地址换成了更长的IPv6地址。
socket API虽然是事实上的网络编程标准,而且由于它的接口支持各种网络协议而长盛不衰,但它的接口并不是完全协议无关的。
特别声明:本站注明稿件来源为其他媒体的文/图等稿件均为转载稿,本站转载出于非商业性的教育和科研之目的,并不意味着赞同其观点或证实其内容的真实性。如转载稿涉及版权等问题,请作者在两周内速来电或来函联系。