本章中我们要讨论另一个常用的应用程序:NFS(网络文件系统),它为客户程序提供透明的文件访问。NFS的基础是Sun RPC:远程过程调用。我们首先必须描述一下RPC。
客户程序使用NFS不需要做什么特别的工作,当NFS内核检测到被访问的文件位于一个NFS服务器时,就会自动产生一个访问该文件的RPC调用。
我们对NFS如何访问文件的细节并不感兴趣,只对它如何使用Internet的协议,尤其是UDP协议,感兴趣。
大多数的网络程序设计都是编写一些调用系统提供的函数来完成特定的网络操作的应用程序。例如,一个函数完成TCP的主动打开,另一个完成TCP的被动打开,一个函数在一个TCP连接上发送数据,另一个设置特定的协议选项(如激活TCP的keepalive定时器)。在1.15节我们提到过两个常用的用于网络编程的函数集(API):插口(socket)和TLI。正像客户端和服务器端运行的操作系统可能会不相同一样,双方使用的API也可能会不相同。由通信协议和应用协议决定一对客户和服务器是否可以彼此通信。如果两台主机连接在一个网络上,并且都有一个TCP/IP的实现,那么一台主机上的一个使用C语言编写的、使用插口和TCP的Unix客户程序可以和另一台主机上的一个使用COBOL语言编写的、使用其他API和TCP的大型机服务器进行通信。
一般来说,客户发送命令给服务器,服务器向客户发送应答。目前为止,我们讨论过的所有应用程序—Ping,Traceroute,选路守护程序、以及DNS、TFTP、BOOTP、SNMP、Telnet、FTP和SMTP的客户和服务器—都是采用这种方式实现的。
远程过程调用RPC(Remote Procedure Call)是一种不同的网络程序设计方法。客户程序编写时只是调用了服务器程序提供的函数。这只是程序员所感觉到的,实际上发生了下面一些动作。
网络程序设计是通过残桩和使用诸如插口或TLI的某个API的RPC库例程来实现的,但是用户程序—客户程序和被客户程序调用的服务器过程—不会和这个API打交道。客户应用程序只是调用服务器的过程,所有网络程序设计的细节都被RPC程序包、客户残桩和服务器残桩所隐藏。
一个RPC程序包提供了很多好处。
RPC程序设计的细节可以参看参考文献[Stevens 1990]的第18章。两个常用的RPC程序包是Sun RPC和开放软件基金(OSF)分布式计算环境(DCE)的RPC程序包。我们对于RPC的兴趣在于想了解Sun RPC中过程调用和过程返回报文的形式,因为本章中讨论的网络文件系统使用了它们。Sun RPC的第2版定义在RFC 1057 [Sun Microsystems 1988a]中。
Sun RPC
SunRPC有两个版本。一个版本建立在插口API基础上,和TCP和UDP打交道。另一个称为TI-RPC的(独立于运输层),建立在TLIAPI基础上,可以和内核提供的任何运输层协议打交道。尽管本章中我们只讨论TCP和UDP,从讨论的观点来看,两者是一样的。
图29-1显示的是使用UDP时,一个RPC过程调用报文的格式。IP首部和UDP首部是标准的首部,我们已经在图3-1和图11-2中显示过。UDP首部以下是RPC程序包定义的部分。
图29-1 RPC过程调用报文作为一个UDP数据报的格式
事务标识符(XID)由客户程序设置,由服务器程序返回。当客户收到一个应答,它将服务器返回的XID与它发送的请求的XID相比较。如果不匹配,客户就放弃这个报文,等待从服务器返回的下一个报文。每次客户发出一个新的RPC,它就会改变报文的XID。但是如果客户重传一个以前发送过的RPC(因为它没有收到服务器的一个应答),重传报文的XID不会修改。
调用(call)变量在过程调用报文中设置为0,在应答报文中设置为1。当前的RPC版本是2。接下来三个变量:程序号、版本号和过程号,标识了服务器上被调用的特定过程。
证书(credential)字段标识了客户。有些情况下,证书字段设置为空值;另外一些情况下,证书字段设置为数字形式的客户的用户号和组号。服务器可以查看证书字段以决定是否执行请求的过程。验证(verifier)字段用于使用了DES加密的安全RPC。尽管证书字段和验证字段是可变长度的字段,它们的长度也作为字段的一部分被编码。
接下来是过程参数(procedure parameter)字段。参数的格式依赖于远程过程的定义。接收者(服务器残桩)如何知道参数字段的大小呢?既然使用的是UDP协议,UDP数据报的大小减去验证字段以上所有字段的长度就是参数的大小。如果使用的不是UDP而是TCP,因为TCP是一个字节流协议,没有记录边界,所以没有固定的长度。为了解决这个问题,在TCP首部和XID之间增加了一个4字节的长度字段,告诉接收者这个RPC调用由多少字节组成。这也使得一个RPC调用报文在必要时可以用多个TCP段来传输(DNS使用了类似的技术,参见习题14-4)。
图29-2显示了一个RPC应答报文的格式。当远程过程返回时,服务器残桩将这个报文发送给客户残桩。
图29-2 RPC应答报文作为一个UDP数据报的格式
应答报文中的XID字段是从调用报文的XID字段复制而来。应答字段设置为1,以区别于调用报文。如果调用报文被接受,状态字段设置为0(如果RPC的版本号不为2,或者服务器不能鉴别客户的身份,调用报文可能被拒绝)。安全的RPC使用验证字段来标识服务器。
如果远程过程调用成功,接受状态字段置为0。一个非零的值可能表示一个不合法的版本号或者一个不合法的过程号。如果使用的不是UDP而是TCP,如同RPC调用报文一样,在TCP首部和XID字段之间插入一个4字节的长度字段。
外部数据表示XDR(eXternal Data Representation)是一个标准,用来对RPC调用报文和应答报文中的值进行编码。这些值包括RPC首部字段(XID、程序号、接受状态等)、过程参数和过程结果。采用标准化的方法对这些值进行编码使得一个系统中的客户可以调用另一个不同架构的系统中的一个过程。XDR在RFC 1014中定义[Sun Microsystems 1987]。
XDR定义了很多数据类型以及它们如何在一个RPC报文中传输的具体形式(如比特顺序,字节顺序等)。发送者必须采用XDR格式构造一个RPC报文,然后接收者将XDR格式的报文转换为本机的表示形式。例如,在图29-1和图29-2中,我们显示的所有整数值(XID、调用字段、程序号等)都是4字节的整数。在XDR中,所有的整数的确占据4个字节。XDR支持的其他数据类型包括无符号整数、布尔类型、浮点数、定长数组、可变长数组和结构。
包含远程过程的RPC服务器程序使用的是临时端口,而不是知名端口。这就需要某种形式的“注册”程序来跟踪哪一个RPC程序使用了哪一个临时端口。在Sun RPC中,这个注册程序被称为端口映射器(port mapper)。
“端口”这个词作为Internet协议族的一个特征,来自于TCP和UDP端口号。既然TIRPC可以工作在任何运输层协议之上,而不仅仅是TCP和UDP,所以使用TI-RPC的系统中(如SVR4和Solaris 2.2),端口映射器的名字变成了rpcbind。下面我们继续使用更为常见的端口映射器的名字。
很自然地,端口映射器本身必须有一个知名端口:UDP端口111和TCP端口111。端口映射器也就是一个RPC服务器程序。它有一个程序号(100000)、一个版本号(2)、一个TCP端口111和一个UDP端口111。服务器程序使用RPC调用向端口映射器注册自身,客户程序使用RPC调用向端口映射器查询。端口映射器提供四个服务过程:
在一个RPC服务器程序启动,接着被一个RPC客户程序调用的过程中,进行了以下一些步骤:
如果使用的是TCP,客户对服务器的TCP端口号做一个主动打开,然后在建立的TCP连接上发送一个RPC调用报文。服务器作为响应,在连接上发送一个RPC应答报文。
程序rpcinfo(8)打印了端口映射器中当前的映射记录(它调用了端口映射器的PMAPPROC_DUMP过程)。这里给出的是典型的输出:
可以看出一些程序确实支持多个版本。在端口映射器中,每一个程序号、版本号和协议的组合都有自己的端口号映射。
安装守护程序(mount daemon)的两个版本可以通过同样的TCP端口号(702)和同样的UDP端口号(699)来访问,而加锁管理程序(lock manager)的每个版本都有各自不同的端口号。
使用NFS,客户可以透明地访问服务器上的文件和文件系统。这不同于提供文件传输的FTP(第27章)。FTP会产生文件一个完整的副本。NFS只访问一个进程引用文件的那一部分,并且NFS的一个目的就是使得这种访问透明。这就意味着任何能够访问一个本地文件的客户程序不需要做任何修改,就应该能够访问一个NFS文件。
NFS是一个使用Sun RPC构造的客户服务器应用程序。NFS客户通过向一个NFS服务器发送RPC请求来访问其上的文件。尽管这一工作可以使用一般的用户进程来实现—即NFS客户可以是一个用户进程,对服务器进行显式调用。而服务器也可以是一个用户进程—因为两个理由,NFS一般不这样实现。首先,访问一个NFS文件必须对客户透明。因此,NFS的客户调用是由客户操作系统代表用户进程来完成的。第二,出于效率的考虑,NFS服务器在服务器操作系统中实现。如果NFS服务器是一个用户进程,每个客户请求和服务器应答(包括读和写的数据)将不得不在内核和用户进程之间进行切换,这个代价太大。
本节中,我们考察在RFC1094中说明的第2版的NFS [Sun Microsystems 1988b]。[X/Open1991]中给出了Sun RPC、XDR和NFS的一个更好的描述。[Stern 1991]给出了使用和管理NFS的细节。第3版的NFS协议在1993年发布,我们在29.7节中对它做一个简单的描述。
图29-3显示了一个NFS客户和一个NFS服务器的典型配置,图中有很多地方需要注意。
图29-3 NFS客户和NFS服务器的典型配置
大多数的Unix主机可以作为一个NFS客户,一个NFS服务器,或者两者都是。大多数PC机的实现(MS-DOS)只提供了NFS客户实现。大多数的IBM大型机只提供了NFS服务器功能。
NFS实际上不仅仅由NFS协议组成。图29-4显示了NFS使用的不同RPC程序。
图29-4 NFS使用的不同RPC程序
在这个图中,程序的版本是在SunOS 4.1.3中使用的。更新的实现提供了其中一些程序更新的版本。例如,Solaris 2.2还支持端口映射器的第3版和第4版,以及安装守护程序的第2版。SVR4支持第3版的端口映射器。
在客户能够访问服务器上的文件系统之前,NFS客户主机必须调用安装守护程序。我们在下面讨论安装守护程序。
加锁管理程序和状态监视器允许客户锁定一个NFS服务器上文件的部分区域。这两个程序独立于NFS协议,因为加锁需要知道客户和服务器的状态,而NFS本身在服务器上是无状态的(下面我们对NFS的无状态会介绍得更多)。[X/Open 1991]的第9,10和11章说明了使用加锁管理程序和状态监视器进行NFS文件锁定的过程。
NFS中一个基本概念是文件句柄(file handle)。它是一个不透明(opaque)的对象,用来引用服务器上的一个文件或目录。不透明指的是服务器创建文件句柄,把它传递给客户,然后客户访问文件时,使用对应的文件句柄。客户不会查看文件句柄的内容—它的内容只对服务器有意义。
每次一个客户进程打开一个实际上位于一个NFS服务器上的文件时,NFS客户就会从NFS服务器那里获得该文件的一个文件句柄。每次NFS客户为用户进程读或写文件时,文件句柄就会传给服务器以指定被访问的文件。
一般情况下,用户进程不会和文件句柄打交道—只有NFS客户和NFS服务器将文件句柄传来传去。在第2版的NFS中,一个文件句柄占据32个字节,第3版中增加为64个字节。
Unix服务器一般在文件句柄中存储下面的信息:文件系统标识符(文件系统最大和最小的设备号),i-node号(在一个文件系统中唯一的数值)和一个i-node的生成码(每当一个i-node被一个不同的文件重用时就改变的数值)。
客户必须在访问服务器上一个文件系统中的文件之前,使用安装协议安装那个文件系统。一般情况下,这是在客户主机引导时完成的。最后的结果就是客户获得服务器文件系统的一个文件句柄。
图29-5显示了一个Unix客户发出mount(8)命令所发生的情况,它说明一个NFS的安装过程。
图29-5 使用Unixmount命令的安装协议
依次发生了下面的动作。
上述实现技术将所有的安装处理,除了客户机上的mount系统调用,都放在用户进程中,而不是放在内核中。我们显示的三个程序—mount命令、端口映射器和安装守护程序—都是用户进程。
作为一个例子,在我们的主机sun(一个NFS客户机)上执行:
sun # mount -t nfs bsdi:/usr /nfs/bsdi/usr
这个命令将主机bsdi(一个NFS服务器)上的/usr目录安装成为本地文件系统/nfs/bsdi/usr。图29-6显示了结果。
图29-6 将bsdi:/usr 目录安装成主机 sun 上的/nfs/bsdi/usr 目录
当我们引用客户机sun上的/nfs/bsdi/usr/rstevens/hello.c文件时,实际上引用的是服务器bsdi上的文件/usr/rstevens/hello.c。
现在我们描述NFS服务器提供的15个过程(使用的个数与NFS过程的实际个数不一样,因为我们把它们按照功能分了组)。尽管NFS被设计成可以在不同的操作系统上工作,而不仅仅是Unix系统,但是一些提供Unix功能的过程可能不被其他操作系统支持(例如硬链接、符号链接、组的属主和执行权等)。[Stevens 1992]的第4章包含了Unix文件系统其他的一些信息,其中有些被NFS采用。
这些过程实际上有一个前缀NFSPROC_,我们把它省略了。
NFS最初是用UDP写的,所有的厂商都提供了这种实现。最新的一些实现也支持TCP。TCP支持主要用于广域网,它可以使文件操作更快。NFS已经不再局限于局域网的使用。
当从LAN转换到W N时,网络的动态特征变化得非常大。往返时间(round-trip time)变动范围大,拥塞经常发生。WAN的这些特征使得我们考虑使用具有TCP属性的算法——慢启动,但是可以避免拥塞。既然UDP没有提供任何类似的东西,那么在NFS客户和服务器上加进同样的算法或者使用TCP。
伯克利实现的Net/2NFS支持UDP或者TCP。[Macklem 1991]描述了这个实现。让我们看一下使用TCP有什么不同。
随着时间的流逝,另外一些厂商也计划支持TCP上的NFS。
我们使用tcpdump来看一下在典型的文件操作中,客户调用了哪些NFS过程。当tcpdump检测到一个包含RPC调用(在图29-1中调用字段等于0)、目的端口是2049的UDP数据报时,它把数据报按照一个NFS请求进行解码。类似地,如果一个UDP数据报是一个RPC应答(在图29-2中应答字段为1),源端口是2049,tcpdump就把此数据报作为一个NFS应答来解码。
第一个例子是使用cat(1)命令将位于一个NFS服务器上的一个文件复制到终端上:
如同图29-6所示,主机sun(NFS客户机)上的文件系统/nfs/bsdi/usr实际上是主机bsdi(NFS服务器)上的/usr文件系统。当cat打开这个文件时,sun上的内核检测到这一点,然后使用NFS去访问文件。图29-7显示了tcpdump的输出。
当tcpdump解析一个NFS请求或应答报文时,它打印客户的XID字段,而不是端口号。第1行和第2行中的XID字段值是0x7aa6。
客户内核中的打开函数一次处理文件名/nfs/bsdi/usr/rstevens/hello.c中的一个成员。当处理到/nfs/bsdi/usr时,它发现这是指向一个已安装的NFS文件系统的一个安装点。
在第1行中,客户调用GETAT TR过程取得客户已经安装的服务器目录的属性(/usr)。这个RPC请求,除IP首部和UDP首部之外,包含104个字节的数据。第2行中的应答返回了一个OK值,除了IP首部和UDP首部之外,包含了96个字节的数据。在这个图中,我们可以看出最小的NFS报文包含大约100个字节的数据。
图29-7 读一个文件的NFS操作
在第3行中,客户调用LOOKUP过程来查看rstevens文件。在第4行中收到一个OK应答。LOOKUP过程说明了文件名rstevens和远程文件系统被安装时由内核保存的文件句柄。应答中包含了下一步要使用的一个新的文件句柄。
在第5行中,客户使用第4行中返回的文件句柄对hello.c调用LOOKUP过程。在第6行返回了另一个文件句柄。新的文件句柄就是客户在第7行和第9行中引用文件/nfs/bsdi/usr/rstevens/hello.c所使用的文件句柄。我们看到客户对于正在打开的路径名的每个成员都调用了一次LOOKUP过程。
在第7行中,客户又调用了一次GETAT TR过程,接着在第9行中调用了READ过程。客户请求从偏移0开始的1024个字节,但是接收到的没有这么多(减去RPC字段和其他由READ过程返回的值的大小,在第10行中返回了38个字节的数据。这是文件hello.c的实际大小)。
在这个例子中,应用进程对于内核所做的这些RPC请求和应答一点儿也不知道。应用进程只是调用了内核的open函数,后者引起了3个RPC请求和3个应答(1~6行),然后应用进程又调用了内核的read函数,它引起了两个请求和两个应答(7~10行)。该文件位于一个NFS文件服务器,这一点对客户应用进程来说是透明的。
作为另一个简单的例子,我们将当前工作目录改变为一个后创建一个新的目录:
图29-8显示了tcpdump的输出。
图29-8 NFS的操作:cd到NFS目录,然后mkdir
改变目录引起客户调用了两次GETATTR过程(1~4行)。当我们创建新的目录时,客户调用了GETAT TR过程(5~6行),接着调用LOOKUP过程(7~8行,用来验证将创建的目录不存在),跟着调用了MKDIR过程来创建目录(9-10行)。在第8行中,应答OK并不表示目录存在。它只是表示过程返回了。tcpdump并不理解NFS过程的返回值。它一般打印OK和应答报文中数据的字节数。
NFS的一个特征(NFS的批评者称之为NFS的一个瑕疵,而不是一个特征)是NFS服务器是无状态的(stateless)。服务器并不记录哪个客户正在访问哪个文件。请注意一下在前面给出的NFS过程中,没有一个open操作和一个close操作。LOOKUP过程的功能与open操作有些类似,但是服务器永远也不会知道客户对一个文件调用了LOOKUP过程之后是否会引用该文件。
无状态设计的理由是为了在服务器崩溃并且重启动时,简化服务器的崩溃恢复操作。
在下面的例子中我们从一个崩溃然后重启动的NFS服务器上读一个文件。这个例子演示了无状态的服务器是如何使得客户不知道服务器的崩溃。除了在服务器崩溃然后重启动时一个时间上的暂停外,客户并不知道发生的问题,客户应用进程没有受到影响。
在客户机sun上,我们对一个长文件(NFS服务器主机svr4上的文件/usr/share/lib/termcap)执行cat命令。在传送过程中把以太网的网线拔掉,关闭然后重启动服务器主机,再重新将网线连上。客户被配置成每个NFS read过程读1024个字节。图29-9显示了tcpdump的输出。
1~10行对应于客户打开文件,操作类似于图29-7所示。在第11行我们看到对文件的第一个READ操作,在12行返回了1024个字节的数据。这个操作一直继续到129行(读1024个字节的数据,跟着一个OK应答)。
在第130行和第131行我们看到两个请求超时,并且分别在132行和133行重传。第一个问题是这里为什么会有两个读请求,一个从偏移65536开始读,另一个从偏移73728开始读?答案是客户内核检测到客户应用进程正在进行顺序地读操作,所以试图预先取得数据块(大多数的Unix内核都采用了这种预读技术)。客户内核也正在运行多个NFS块I/O守护程序,后者试图代表客户产生多个RPC请求。一个守护程序正在从偏移65536处读8192个字节(以1024字节为一组数据块),而另一个正在从73728处预读8192个字节。
客户重传发生在130~168行。在第169行我们看到服务器已经重启动,在它对第168行的客户NFS请求做出应答之前,它发送了一个ARP请求。对168行的响应被发送在171行。客户的READ操作继续进行下去。
除了从129行到171行5分钟的暂停,客户应用进程并不知道服务器崩溃然后又重启动了。这个服务器的崩溃对于客户是透明的。
为了研究这个例子中的超时和重传时间间隔,首先要意识到这儿有两个客户守护程序,分别有它们各自的超时。第1个守护程序(在偏移65536处开始读)的间隔,四舍五入到两个十进制小数点,为0.68,0.87,1.74,3.48,6.96,13.92,20.0,20.0,20.0等等。第2个守护程序(在偏移73728处开始读)的间隔也是一样的(精确到两个小数点)。可以看出这些NFS客户使用了一个这样的超时定时器:间隔为0.875秒的倍数,上限为20秒。每次超时后,重传间隔翻倍:0.875,1.75,3.5,7.0和14.0。
图29-9 当一个NFS服务器崩溃然后重启动时,客户正在读一个文件的过程
客户要重传多久呢?客户有两个与此有关的选项。首先,如果服务器文件系统是“硬”安装的,客户就会永远重传下去。但是如果服务器文件系统是“软”安装的,客户重传了固定数目的次数之后就会放弃。在“硬”安装的情况下,客户还有一个选项决定是否允许用户中断无限制的重传。如果客户主机安装服务器文件系统时说明了中断能力,并且如果我们不想在服务器崩溃之后等5分钟,等着服务器重启动,就可以键入一个中断键以终止客户应用程序。
如果一个RPC过程被服务器执行多次仍然返回同样的结果,那么就把它叫作等幂过程(Idempotent Procedure)。例如,NFS的读过程是等幂的。正像我们在图29-9中看到的,客户只是重发一个特定的READ调用直到它得到一个响应。在我们的例子中,重传的原因是服务器崩溃了。如果服务器没有崩溃,而是RPC应答报文丢失了(既然UDP是不可靠的),客户只是重传请求,服务器再一次执行同样的READ过程。同一个文件的同一部分被重读一次,发送给客户。
这种方法行得通的原因在于每个READ请求指出了读操作开始的偏移位置。如果有一个NFS过程要求服务器读一个文件的下N个字节,这种方法就不行了。除非服务器被做成是有状态的(与无状态相反),如果一个应答丢失了,客户重发读下N个字节的READ请求,结果将是不一样的。这就是为什么NFS的READ和WRITE过程要求客户说明开始的偏移位置的原因。客户维护着状态(每个文件当前的偏移位置),而不是服务器。
不幸的是并不是所有的文件系统操作都是等幂的。例如,考虑下面的动作:客户NFS发出REMOVE请求来删除一个文件;服务器NFS删除了文件,并回答OK;服务器的回答丢失了;客户NFS超时,然后重传请求;服务器NFS找不到指定的文件,回答指出一个错误;客户应用程序接收到一个错误表示文件不存在。这个返回给客户应用程序的错误是不对的—该文件的确存在并且被删除了。
等幂的NFS过程是:GETATTR、STATES、LOOKUP、READ、WRITE、READLINK和READDIR。不是等幂的过程是:CREATE、REMOVE、RENAME、LINK、SYMLINK、MKDIR和RMDIR。SETATTR过程如果不用来截断文件,一般是等幂的。
既然使用UDP总会发生响应报文丢失的现象,NFS服务器需要一种方法来处理非等幂的操作。大多数的服务器实现了一个最近应答的高速缓存,用于存放非等幂操作最近的应答。每当服务器收到一个请求,它首先检查这个高速缓存,如果找到了一个匹配,就返回以前的应答而不再调用相应的NFS过程。[Juszczak 1989]提供了这种高速缓存的实现细节。
等幂服务器过程的概念可以应用于任何基于UDP的应用程序,而不仅仅是NFS。例如,DNS也提供了一个等幂服务。一个DNS的服务器可以任意多次地执行一个解析者的请求而没有任何不良的后果(如果不考虑网络资源浪费的话)。
1993年发布了第3版的NFS协议规范[Sun Microsystem 1994]。其实现有望在1994年成为可能。
我们总结一下第2版和第3版的主要区别。下面把两者分别称为V2和V3。
RPC是构造客户-服务器应用程序的一种方式,使得看起来客户只是调用了服务器的过程。所有的网络操作细节都被隐藏在RPC程序包为一个应用程序生成的客户和服务器残桩以及RPC库的例程中。我们显示了RPC调用和应答报文的格式,并且提到了使用XDR对传输的值进行编码,使得RPC客户和服务器可以运行在不同架构的机器上。
最广泛使用的RPC应用之一就是Sun的NFS,一个在各种大小的主机上广泛实现的异构的文件访问协议。我们浏览了NFS和它使用UDP和TCP的方式。第2版的NFS协议定义了15个过程。
一个客户对一个NFS服务器的访问开始于安装协议,返回给客户一个文件句柄。客户接着可以使用那个文件句柄来访问服务器文件系统中的文件。在服务器上,一次检查文件名的一个成员,返回每个成员的一个新的文件句柄。最后的结果就是要引用的文件的一个文件句柄,它可以在随后的读写操作中被使用。
NFS试图把它的所用过程都做成等幂的,使得如果响应报文丢失了,客户只需要重发一个请求。我们看到了服务器崩溃然后又重启动时,一个客户读服务器上的一个文件的例子。