使用ICE实现一个简单的文件系统

更新时间:2024-03-23 01:54:01 阅读量: 综合文库 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

一、文件系统简介

本章的目的是通过使用ICE来实现一个简单的文件系统应用,文件系统应用将实现一个简单的层次结构的文件系统,文件系统由目录和文件组成,目录是可以容纳目录或文件的容器。

二、文件系统的Slice 定义

文件和目录有共同之处:它们都有名字,而且文件和目录都可以包含在目录中。可以使用基类型来提供共有的功能,用派生类型来提供目录和文件专有的功能。如下图所示:

文件系统的完整Slice 定义如下:

module Filesystem {//映射成为java中的package interface Node {//文件和目录的基接口, }; exception GenericError {//异常 }; sequence Lines;//sequence映射为一个java数组结构,Lines是一string reason;//reason数据成员中会提供对失败原因的解释。 string name();//因为目录和文件都有名字 个数组,数组中存放的是string类型的数据。 interface File extends Node {//文件接口,继承Node Lines read();//提供对文件内容的读操作,假定read 操作永远不会失败。Read操作返回的是Lines字符串数组。 void write(Lines text) throws GenericError;//写操作,假定写操作会遇到出错的情况。 }; sequence NodeSeq;//NodeSeq序列包含的是Node* 类型的元素,sequence被slice2java编译成一个数组,Node*编译为一个Node的代理NodePrx。最终编译成一个数组,数组中存放的是节点的代理NodePrx对象。 interface Directory extends Node { NodeSeq list();//返回一个数组,数组中存放的是一些节点代理,list函数会被客户端调用,每个代理都指向在服务器上的一个远地节点。 }; interface Filesys { Directory* getRoot();//客户端调用此函数,获得根目录,返回的是一个Directory的一个代理对象到客户端。以便客户端对Directory进行其他的远程操作。 }; 三、开发文件系统客户

客户会从根目录开始,递归地列出文件系统的内容。对于文件系统中的每一个节点,客户都会显示节点名,以及该节点是文件还是目录。如果节点是文件,客户就获取文件的内容,并打印它们。 下面是客户代码的主体:

package Filesystem.client; import Filesystem.DirectoryPrx; import Filesystem.DirectoryPrxHelper; import Filesystem.FilePrx; import Filesystem.FilePrxHelper; import Filesystem.NodePrx; public class Client { }; /** * 递归打印出目录中内容 * @param dir 服务器目录在本地的代理对象 * @param depth 记录目录的深度,方便打印缩进。 */ static void listRecursive(DirectoryPrx dir, int depth) { //每递归调用一次,depth深度增加1 char[] indentCh = new char[++depth]; java.util.Arrays.fill(indentCh, '\\t'); String indent = new String(indentCh); NodePrx[] contents = dir.list(); for (int i = 0; i < contents.length; ++i) { DirectoryPrx subdir = DirectoryPrxHelper.checkedCast(contents[i]); FilePrx file = FilePrxHelper.uncheckedCast(contents[i]); System.out.println(indent + contents[i].name() + (subdir != null ? \(directory):\ : \(file):\)); if (subdir != null) { listRecursive(subdir, depth); } else { String[] text = file.read(); for (int j = 0; j < text.length; ++j) System.out.println(indent + \ + text[j]); } } } public static void main(String[] args) { int status = 0; Ice.Communicator ic = null; try { ic = Ice.Util.initialize(args); Ice.ObjectPrx base = ic.stringToProxy(\); if (base == null) throw new RuntimeException(\create proxy\); DirectoryPrx rootDir = DirectoryPrxHelper.checkedCast(base); if (rootDir == null) throw new RuntimeException(\); System.out.println(\of root directory:\); listRecursive(rootDir, 0); } catch (Ice.LocalException e) { e.printStackTrace(); status = 1; } catch (Exception e) { System.err.println(e.getMessage()); status = 1; } finally { if (ic != null) ic.destroy(); } System.exit(status); } } 1. Main函数中,在初始化run time 之后,客户创建一个代理,指向文件系统

的根目录。在这个例子中,我们假定服务器运行在本地主机上,并且使用缺省协议(TCP/IP)在10000 端口处侦听。根目录的对象标识叫作RootDir。 2. 客户把代理向下转换成DirectoryPrx,并把这个代理传给listRecursive,

由它打印出文件系统的内容。

大多数工作都是在listRecursive 中完成的。这个函数收到的参数是一个代理,指向要列出的目录;另外还有一个缩进层次参数(缩进层次随着每一次递归调用增加,这样,打印每个节点名时的缩进层次就会与该节点的树深度对应)。listRecursive 调用目录的list 操作,并且遍历所返回的节点序列: 1. 代码调用checkedCast,把Node 代理窄化成Directory 代理;并且调用

uncheckedCast,把Node 代理窄化成File 代理。在这两个转换中只有,而且肯定会有一个成功,所以不需要两次调用checkedCast:如果节点是一个Directory,代理就使用checkedCast 返回的DirectoryPrx ;如果checkedCast 失败,我们知道了这个节点是一个File,因此,要获得FilePrx,使用uncheckedCast 就足够了。一般而言,如果你知道向下转换到特定类型能成功,就最好使用uncheckedCast,而不是checkedCast,因为uncheckedCast 不需要进行任何网络通信。

2. 代码打印文件或目录的名字,然后,取决于成功的转换是哪一个,在名字的

后面打印\或\。 3. 代码检查节点的类型:

? 如果是目录,代码就会递归,同时增加缩进层次。

? 如果是文件,代码就调用文件的read 操作,取回文件内容,然后遍历返回

的内容行序列,打印每一行。

假定我们有一个很小的文件系统,由两个文件和两个目录组成:

这个文件的客户产生的输出是: Contents of root directory: README (file): This file system contains a collection of poetry. Coleridge (directory): Kubla_Khan (file): In Xanadu did Kubla Khan A stately pleasure-dome decree: Where Alph, the sacred river, ran Through caverns measureless to man Down to a sunless sea. 四、开发文件系统服务器 服务器由三个源文件组成:

? Server.java,这个文件含有服务器主程序。

? DirectoryI.java,这个文件含有Directory servant 的实现。 ? FileI.java,这个文件含有File servant 的实现。

1.服务器的main程序

Server类派生自Ice.Application,在其run 方法中含有主应用逻辑。run方法创建对象适配器、为文件系统里的目录和文件创建一些servants,然后激活适配器。下面是main程序完成代码: package Filesystem.server; import Filesystem.File; import Filesystem.GenericError; import Filesystem.servant.DirectoryI; import Filesystem.servant.FileI; public class Server extends Ice.Application { public int run(String[] args) { Ice.ObjectAdapter adapter = communicator() .createObjectAdapterWithEndpoints(\, \); DirectoryI._adapter = adapter; FileI._adapter = adapter; //创建根目录,名字是\,没有父目录。 //对于没有父目录的根目录,传递null作为父引用 DirectoryI root = new DirectoryI(\, null); //在根目录中创建README 文件 File file = new FileI(\, root); String[] text; text = new String[] { \poetry.\ }; try { //用文本填充文件 file.write(text, null); } catch (GenericError e) { } //创建Coleridge 子目录 DirectoryI coleridge = new DirectoryI(\, root); //在Coleridge目录中创建Kubla_Khan文件 file = new FileI(\, coleridge); System.err.println(e.reason); //Kubla_Khan文件的内容 text = new String[] { \, \, \, \, \sea.\ }; public static void main(String[] args) { Server app = new Server(); } try { file.write(text, null); } catch (GenericError e) { } adapter.activate(); System.err.println(e.reason); communicator().waitForShutdown(); return 0; } } System.exit(app.main(\, args)); 服务器实例化文件系统的几个节点,创建了下图所示的结构:

2.FileI Servant 类

FileI servant 类具有这样的基本结构: public class FileI extends _FileDisp { } 这个类有一些数据成员: ? _adapter

这个静态成员存储的是一个引用,指向我们在服务器中使用的唯一一个对象适配器。 ? _name

这个成员存储的是servant 所体现的文件的名字。 ? _parent

这个成员存储的是一个引用,指向文件的父目录的servant。 ? _lines

这个成员存放文件的内容。

public static Ice.ObjectAdapter _adapter; private String _name; private DirectoryI _parent; private String[] _lines; _name 和_parent 数据成员由构造器初始化。 FileI servant类完成代码如下: package Filesystem.servant; import Filesystem.GenericError; import Filesystem.NodePrx; import Filesystem.NodePrxHelper; import Filesystem._FileDisp; public class FileI extends _FileDisp { //这个静态成员存储的是一个引用,指向我们在服务器中使用的唯一一个对象适配器。 public static Ice.ObjectAdapter _adapter; //这个成员存储的是servant 所体现的文件的名字。 private String _name; //这个成员存储的是一个引用,指向文件的父目录的servant。 private DirectoryI _parent; //这个成员存放文件的内容。 private String[] _lines; /** * _name 和_parent 数据成员由构造器初始化: * @param name * @param parent */ public FileI(String name, DirectoryI parent) { _name = name; _parent = parent; //核实指向父目录的引用不是null,因为每个文件都必须有父目录。 assert (_parent != null); // 为这个文件生成一个标识, Ice.Identity myID = Ice.Util.stringToIdentity(Ice.Util.generateUUID()); //把自己增加到适配器的servant映射表中。 _adapter.add(this, myID); // 为这个文件创建代理 NodePrx thisNode = NodePrxHelper.uncheckedCast(_adapter .createProxy(myID)); //调用父目录的addChild 方法,把自己增加到父目录的后代节点列表中。 //非线程安全 public void write(String[] text, Ice.Current current) throws /** * name 方法继承自生成的Node 接口 * 它简单地返回_name 成员的值。 */ public String name(Ice.Current current) { } //非线程安全 public String[] read(Ice.Current current) { } return _lines; return _name; } _parent.addChild(thisNode); GenericError { _lines = text;

} } 3.DirectoryI Servant 类

DirectoryI 类具有这样的基本结构: package Filesystem; public final class DirectoryI extends _DirectoryDisp { public static Ice.ObjectAdapter _adapter; private String _name; private DirectoryI _parent; private java.util.ArrayList _contents = new java.util.ArrayList(); }

和FileI 类的情况一样,我们拥有一些数据成员,用于存储对象适配器、名字,以及父目录(对于根目录, _parent 成员存放的是null 引用)。此外,我们还有一个_contents 数据成员,存储的是子目录列表。这些数据成员都由构造器初始化。

DirectoryI servant类完成代码如下: package Filesystem.servant; import Filesystem.NodePrx; import Filesystem.NodePrxHelper; import Filesystem._DirectoryDisp; public final class DirectoryI extends _DirectoryDisp { //指向服务器中唯一一个对象适配器 public static Ice.ObjectAdapter _adapter; //存储目录的名称 private String _name; //指向父目录 private DirectoryI _parent; //存储子目录列表 private java.util.ArrayList _contents = new java.util.ArrayList(); public DirectoryI(String name, DirectoryI parent) { _name = name; _parent = parent; //为新目录创建标识, //对于根目录,使用固定的\标识 Ice.Identity myID = Ice.Util .stringToIdentity(_parent != null ? Ice.Util.generateUUID() /** * 助手函数,子目录或文件可以调用它,把自己增加到父目录的后代节点 } : \); //把自己增加到servant 映射表中 _adapter.add(this, myID); //创建一个指向自身的代理 NodePrx thisNode = NodePrxHelper.uncheckedCast(_adapter .createProxy(myID)); //把自己增加到父目录的后代节点列表中。 if (_parent != null) _parent.addChild(thisNode); 列表中。 } * @param child */ void addChild(NodePrx child) { } _contents.add(child); public String name(Ice.Current current) { } return _name; public NodePrx[] list(Ice.Current current) { } NodePrx[] result = new NodePrx[_contents.size()]; _contents.toArray(result); return result; 注意,按照现在的情况,这里给出的服务器代码并不是线程安全的,因为如果有两个客户分别通过不同的线程,同时访问同一个文件,一个线程可能在读取_lines 数据成员,而另一个线程在更新它。显然,如果发生这样的情况,我们可能会写入或返回垃圾数据。 要让read 和write 操作成为线程安全,需要使用java提供的同步关键字,或者是使用java提供的同步API中的类。

参考《Ice 分布式程序设计》 马维达 译,

本文来源:https://www.bwwdw.com/article/idr8.html

Top