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

一、文件系统简介

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

二、文件系统的Slice 定义

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

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

module Filesystem {//映射成为java中的package
	interface Node {//文件和目录的基接口,
		string name();//因为目录和文件都有名字
	};
	exception GenericError {//异常
		string reason;//reason数据成员中会提供对失败原因的解释。
	};
	sequence<string> Lines;//sequence映射为一个java数组结构,Lines是一个数组,数组中存放的是string类型的数据。
	interface File extends Node {//文件接口,继承Node
		Lines read();//提供对文件内容的读操作,假定read 操作永远不会失败。Read操作返回的是Lines字符串数组。
		void write(Lines text) throws GenericError;//写操作,假定写操作会遇到出错的情况。
	};
	sequence<Node*> 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 + "\t" + 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("RootDir:default -p 10000");
			if (base == null)
				throw new RuntimeException("Cannot create proxy");
			DirectoryPrx rootDir = DirectoryPrxHelper.checkedCast(base);
			if (rootDir == null)
				throw new RuntimeException("Invalid proxy");
			System.out.println("Contents 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. 代码打印文件或目录的名字,然后,取决于成功的转换是哪一个,在名字的后面打印"(directory)" 或"(file)"。

    3. 代码检查节点的类型:

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

    l 如果是文件,代码就调用文件的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.

四、开发文件系统服务器

    服务器由三个源文件组成:

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

    l DirectoryI.java这个文件含有Directory servant 的实现。

    l 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("SimpleFilesystem",
						"default -p 10000");
		DirectoryI._adapter = adapter;
		FileI._adapter = adapter;
		//创建根目录,名字是"/" ,没有父目录。
		//对于没有父目录的根目录,传递null作为父引用
		DirectoryI root = new DirectoryI("/", null);
		//在根目录中创建README 文件
		File file = new FileI("README", root);
		String[] text;
		text = new String[] { "This file system contains a collection of poetry." };
		try {
			//用文本填充文件
			file.write(text, null);
		} catch (GenericError e) {
			System.err.println(e.reason);
		}
		//创建Coleridge 子目录
		DirectoryI coleridge = new DirectoryI("Coleridge", root);
		//在Coleridge目录中创建Kubla_Khan文件
		file = new FileI("Kubla_Khan", coleridge);
       //Kubla_Khan文件的内容
		text = new String[] { "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." };
		try {
			file.write(text, null);
		} catch (GenericError e) {
			System.err.println(e.reason);
		}
		adapter.activate();
		communicator().waitForShutdown();
		return 0;
	}

	public static void main(String[] args) {
		Server app = new Server();
		System.exit(app.main("Server", args));
	}
}

服务器实例化文件系统的几个节点,创建了下图所示的结构

2.FileI Servant 类

    FileI servant 类具有这样的基本结构:

public class FileI extends _FileDisp
{
	public static Ice.ObjectAdapter _adapter;
	private String _name;
	private DirectoryI _parent;
	private String[] _lines;
}

    这个类有一些数据成员:

• _adapter

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

• _name

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

• _parent

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

• _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 方法,把自己增加到父目录的后代节点列表中。
		_parent.addChild(thisNode);
	}

	/**
	 * name 方法继承自生成的Node 接口
	 * 它简单地返回_name 成员的值。
	 */
	public String name(Ice.Current current) {
		return _name;
	}
	//非线程安全
	public String[] read(Ice.Current current) {
		return _lines;
	}

	//非线程安全
	public void write(String[] text, Ice.Current current) throws 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;
		//为新目录创建标识,
		//对于根目录,使用固定的"RootDir" 标识
		Ice.Identity myID = Ice.Util
				.stringToIdentity(_parent != null ? Ice.Util.generateUUID()
						: "RootDir");
		//把自己增加到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 分布式程序设计》 马维达 译.

  • 1
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值