数据表示
每种计算机体系结构都有其各自的数据表示定义。即使两种体系结构支持同样的数据类型,但其表示形式也可能不同。
为单台计算机编写程序的程序员不需要考虑数据的表示,因为一台计算机通常只允许有一种表示。
然而,程序员创建客户和服务器软件时,它们必须考虑数据表示,因为对于在通信双方间通信信道上发送的所有数据,两端必须就这些数据的确切表示达成一致。如果两台机器上的本地数据表示不同,从一台机器上程序发送给另一端的数据就必须被转换。
数据表示的中心议题是软件可移植性。一个极端的情况是,程序员可在每一对客户和服务器中,嵌入计算机体系结构的知识,以便一方将数据转换为适用于另一方的表示。如果直接从客户的表示转换为服务器的表示,使用这种模式的设计称为不对称的数据转换,因为转换只是由某一方进行。但遗憾的是,使用不对称的数据转换意味着:对使用的每一对体系结构,程序员都必须要为之编写这对客户和服务器的不同版本,软件的版本数将随着体系结构数的平方增长
网络标准字节顺序
如果多种体系结构使用不同的数据表示,单一的一种源程序如何才能在不同的体系结构下正确的编译和执行呢?更重要的是,如果两台机器使用不同的数据表示,客户或者服务器程序如何把数据发送到另一端呢? TCP/IP(解决协议首部中的整数标识问题)的方式是通过使用一组函数来解决这个问题,这组函数在计算机的本地字节序和网络字节序间进行相互转换。
从本质上讲,在网络中传输的协议首部数据使用了同一种标准数据表示,即协议首部采用了一种标准的、独立于机器的形式来标识整数。我们称这种设计采用的是对称的数据转换,即不是直接将一种表示转换为另一种,而是每个端点都将其本地表示转换为用于传输的标准表示。这样就只需要一种版本的协议软件了。
在网络上传送数据所使用的标准表示称为外部数据表示(
- 优点:客户和服务器不需要理解对方的体系结构。单个客户可以和任意机器上的服务器联系,而不必知道该机器的体系结构
- 缺点:计算的额外开销。即使客户和服务器都在同一体系的计算机上运行,也需要这种开销
外部数据表示
事实上的标准
Sun公司设计了一种外部数据表示,它规定了在网络上传输数据时如何表示成公共实现的数据。Sun的外部数据表示XDR已经成为大多数客户–服务器应用的事实上的标准
使用XDR的软件支持
程序员如果选用XDR表示来用于对称数据转换,在向网络发送数据前,必须小心的将每个数据项放入外部形式中。同样,接收方程序也必须小心处理,以便将每个收到的数据项转换成本地表示。我们可以通过在代码中插入一个函数调用,它在发送前将报文中的每个数据项转换为外部格式,在接收报文时,还要插入一个函数调用,负责将每个数据项转换为内部形式
XDR库例程
某台机器的XDR例程库可完成数据项的转换,将数据从计算机的本地表示转换为XDR标准表示,或者反之。XDR的大多数实现使用了一种缓存范例,它允许程序员创建完全是XDR形式的报文。
一次一片的构造报文
使用缓存范例的XDR需要程序分配足够大的缓存,以便存放一个报文的外部表示,并且需要程序一次往缓存中添加一个数据项(即字段)。比如,在Linux操作系统下给出的XDR版本提供了转换例程,每个例程都在存储器缓存的末尾追加外部表示。程序实现调用xdrmen_create过程,在存储器中分配一个缓存,并且通知XDR它将在该缓存中组成外部表示。xdrmen_create初始化存储器缓存,于是,该缓存表示用于编码(转换成标准表示)或者解码(转换成本地表示)数据的XDR流。该调用通过指定缓存的起始地址为内部指针,将XDR流初始化为空。xdrmen_create返回XDR流的指针,而该指针必须被用于XDR例程的后继调用。使用C创建XDR流的声明和调用是:
#include <rpc/xdr.h>
#define BUFSIZE 4000 // size of memory for encoding
XDR *xdrs; // pointer to an XDR "stream"
char buf[BUFSIZE]; // memory area to hold XDR data
xdrmem_create(xdrs, buf, BUFSIZE, XDR_ENCODE);
一旦程序创建了XDR流,它就可以调用各个XDR转换例程,以便将本地数据对象转换成外部形式。每个调用编码一个数据对象,并将编码信息追加到流的末尾(即在缓存的下一个可用比特位置放置存放编码后的数据,然后更新内部流指针)。比如,xdr_int过程将32bit二进制整数从本地表示转换成XDR表示,然后将编码信息追加到XDR流末尾:
int i;
...
i = 206;
xdr_int(xdrs, &i);
XDR库中的转换例程
下表列出了XDR转换例程
为形成一个报文,应用程序要为报文中的每个数据项调用XDR转换例程。对每个数据项编码并添加XDR流之后,应用程序可以通过发送最终生成的XDR流来发送报文。接收方应用程序必须颠倒整个过程,它调用xdrmen_create创建一个存放XDR流的存储器缓存,并且将传入报文放进缓存。XDR自己在例程访问的流中记录转换的方向,转换例程可以访问到流中的这个记录。接收方在创建用于输入XDR流时,它指明XDR_DECODE为xdrmen_create的第三个参数。结构是,只要接收方在输入流上调用转换例程,该例程就从流中取出一项并转换为本地模式。比如,如果接收方已经建立了一个XDR用于输入的流(即指定了XDR_DECODE),它就能调用xdr_int从输入流中取出一个三维的整数,并将该整数转换为本地表示
int i;
xdr_int(xdrd, &i);
XDR流、IO和TCP
以上代码创建了与存储器缓存相关联的XDR流。使用存储器缓存数据可以使程序更有效,因为它允许应用程序在向网络发送数据前,将大量数据转换为外部形式。在各个数据项都被转换成外部形式并放入缓存后,应用程序必须调用像write这样的IO函数,以便在TCP/IP连接上将其发送出去。
可以让XDR转换例程在每次把数据项转换为外部形式后,自动的将在TCP连接上发送。为此,应用程序实现创建一个TCP套接字,然后调用fdopen将标准IO流与此套接字关联。应用程序不是调用xdrmen_create,而是调用xdrstdio_create创建XDR流,并将它链接到现有的IO描述符上。附在TCP套接字上的XDR流不需要显示调用write和read。应用程序每次调用XDR转换例程,转换例程就使用它所蕴含的描述符,自动完成待缓存的read和write操作。write操作使TCP将外发数据发送到套接字,read操作将使TCP从套接字读取传入数据。应用程序还可以调用Unix标准IO库中的常规函数,以便对IO流进行操作。比如,如果期望输出,应用程序可以在仅转换率少数几个字节的数据后,就使用fflush刷新输出缓存。
记录、记录边界和数据报IO
正如前面所描述的那样,当XDR流链接到TCP套接字时,XDR机制可以很好地工作,因为XDR和TCP的实现都使用流抽象。为使XDR和UDP的合作向它和TCP合作由于容易,设计人员增加了第二个接口。这种变通的设计为应用程序提供了面向记录的接口
为了使用面向记录的接口,程序创建XDR流时需要调用xdrrec_create函数。该调用包含两个参数,inproc和outproc,它们分别定义了输入过程和输出过程。在把数据转换成外部形式时,每个转换例程都检查缓存,如果缓存满了,转换例程就调用outproc发送缓存中已有的内容,为新数据腾出空间。同样,每次应用程序调用转换例程将外部形式转换成本地表示时,例程将检查缓存是否含有数据。如果缓存时空的,转换例程就调用inproc获取更多数据
为使XDR结合UDP使用,应用程序需要创建面向记录的XDR流。它让与流相关联的输入和输出过程调用read和write(或者recv和send)。当应用程序填满缓存时,转换例程就调用write用单个UDP数据报传输缓存内存。当应用程序调用转换例程取出数据时,转换例程将调用read获取下一个数据报,并将它放到缓存中。
通过xdrrec_create创建的XDR流,在几个方面不同于其他XDR流。面向记录的流运行应用程序标志记录边界。此外,发送方可指明是立即发送记录,还是等缓存满后再发送。接收方可以检测记录边界,跳过输入中固定数目的记录,或者查看是否已经收到其他记录