`

ICE笔记(06):简单文件系统的设计、实现

    博客分类:
  • Ice
阅读更多

colorado

 

本文介绍了比较完整的Ice应用程序,它实现了简单文件系统。本程序位于ICE发布的$ICE_HOME/demo/book/simple_filesystem目录。本文内容涉及DPWI第5,7,9章。通过这个程序的学习,读者应该掌握了Ice应用程序开发的基本方法、步骤。此后要通过深入学习Ice知识来提高Ice应用程序的开发水平。到本文为止,Ice for C++ 应用程序开发的基础部分就介绍完了。

1、系统需求
该系统为层次结构,由目录和文件组成。目录是子目录、文件的容器,顶层为根目录。同级各个目录、文件不能同名。
该系统包含固定数目的目录、文件。不能创建、销毁目录、文件。只能一次读写文件全部内容,不能读写部分内容,不支持路径。

2、定义Slice文件
Filesystem.ice:
module Filesystem {
interface Node {
idempotent string name();
};
exception GenericError {
string reason;
};
sequence<string> Lines;
interface File extends Node {
idempotent Lines read();
idempotent void write(Lines text) throws GenericError;
};
sequence<Node*> NodeSeq;
interface Directory extends Node {
idempotent NodeSeq list();
};
};
根据规范:

• Node为节点,提供name操作;文件File和目录Direcotry继承自Node。
• File只支持文本文件,read不会失败,只有write会遇到异常。read/write是幂等操作。
• Directory提供了列出内容的list操作。返回结果为Node序列,序列中包含的是Node代理,这里File/Directory的基代理,可以转型为相应的具体代理。

3、服务器端
服务器由以下文件组成:
• Server.cpp :这个文件含有服务器主程序。
• FilesystemI.h:这个文件含有文件系统服务者的定义。
• FilesystemI.cpp:这个文件含有文件系统服务者的实现。

3.1、主程序
Server.cpp main函数:
int main(int argc, char* argv[])
{
FilesystemApp app;
return app.main(argc, argv);
}

定义了FilesystemApp作为启动类,该类继承自Ice::Application类,在run成员函数中实现服务器

主流程:
virtual int run(int, char*[]) {
// 收到中止信号时干净地终止程序
shutdownOnInterrupt();

// 创建对象适配器
Ice::ObjectAdapterPtr adapter =
communicator()->createObjectAdapterWithEndpoints("SimpleFilesystem", "default -p 10000");

// 创建根目录(名为"/",没有父节点)
DirectoryIPtr root = new DirectoryI(communicator(), "/", 0);
root->activate(adapter);

// 在根目录下创建README文件
FileIPtr file = new FileI(communicator(), "README", root);
Lines text;
text.push_back("This file system contains a collection of poetry.");
file->write(text);
file->activate(adapter);

// 在根目录下创建Coleridge目录
DirectoryIPtr coleridge = new DirectoryI(communicator(), "Coleridge", root);
coleridge->activate(adapter);

// 在Coleridge目录下创建Kubla_Khan文件
file = new FileI(communicator(), "Kubla_Khan", coleridge);
text.erase(text.begin(), text.end());
text.push_back("In Xanadu did Kubla Khan");
text.push_back("A stately pleasure-dome decree:");
text.push_back("Where Alph, the sacred river, ran");
text.push_back("Through caverns measureless to man");
text.push_back("Down to a sunless sea.");
file->write(text);
file->activate(adapter);

// 所有对象已创建,现在可以接收客户端请求
adapter->activate();

// 等待完成
communicator()->waitForShutdown();
if (interrupted()) {
cerr << appName()
<< ": received signal, shutting down" << endl;
}
return 0;
};


⑴、安装shutdownOnInterrupt(),遇到关闭信号,就关闭程序。
⑵、创建对象适配器adapter,适配器名SimpleFilesystem,协议:缺省(TCP),端口:10000。
⑶、创建文件系统
文件系统结构:
RootDir
↙ ↘
Coleridge README

Kubla_Khan
• 首先调用new DirectoryI(communicator(), "/", 0) 创建根目录"/",根没有父目录,传入0作为父目录句柄。返回目录指针存入root;
• 然后调用new FileI(communicator(), "README", root)创建文件README,它的父目录为root;为文件添加内容;
• 继续调用new DirectoryI(communicator(), "Coleridge", root)创建子目录Coleridge;
• 最后调用new FileI(communicator(), "Kubla_Khan", coleridge)创建文件Kubla_Khan,它的父目录为Coleridge;为文件添加内容;

 

每次创建节点后,都要调用NodeI::activate(...),将该节点添加到适配器adapter中,也将节点加入它的父目录。

⑷、激活适配器
调用adapter->activate()激活适配器;

⑸、关闭程序
调用communicator()->waitForShutdown()
挂起当前主函数所在线程,等待通讯器关闭;如果通讯器关闭了,就检查是否正常关闭,如果是由于关闭信号(如Ctrl+C)引发的关闭,打印关闭信号信息;关闭程序。

3.2 服务者类定义
slice2cpp编译Filesystem.ice生成Filesystem.h,Filesystem.cpp映射文件,定义了文件系统接口规范。FilesystemI.h,FilesystemI.cpp继承了映射文件中的类并予以实现。为了避免错误,加快生成,可以使用如下命令:
slice2cpp --impl Filesystem.ice
这会同时生成如下文件:
Filesystem.h
Filesystem.cpp
FilesystemI.h
FilesystemI.cpp

这样,开发FilesystemI就不容易出错了。

FilesystemI.h:
namespace Filesystem {

class DirectoryI; //提前声明,DirectoryIPtr引用之
typedef IceUtil::Handle<DirectoryI> DirectoryIPtr; //提前声明,NodeI,FileI引用之

class NodeI : virtual public Node {
public:
virtual std::string name(const Ice::Current&);
NodeI(const Ice::CommunicatorPtr&, const std::string&, const DirectoryIPtr&);
void activate(const Ice::ObjectAdapterPtr&);
private:
std::string _name;
Ice::Identity _id;
DirectoryIPtr _parent;
NodeI(const NodeI&); // 禁止复制
void operator=(const NodeI&); // 禁止赋值
};

typedef IceUtil::Handle<NodeI> NodeIPtr;

class FileI : virtual public File, virtual public NodeI {
public:
virtual Lines read(const Ice::Current&);
virtual void write(const Lines&,const Ice::Current& = Ice::Current());
FileI(const Ice::CommunicatorPtr&, const std::string&, const DirectoryIPtr&);
private:
Lines _lines;
};

typedef IceUtil::Handle<FileI> FileIPtr;

class DirectoryI : virtual public Directory,virtual public NodeI {
public:
virtual NodeSeq list(const Ice::Current&);
DirectoryI(const Ice::CommunicatorPtr&, const std::string&, const DirectoryIPtr&);
void addChild(const Filesystem::NodePrx&);
private:
Filesystem::NodeSeq _contents;
};
}

slice2cpp --impl 生成文件系统使用了每个类的实现继承,如下图所示:
⑴、NodeI
NodeI是具体基类,从Node类继承name操作;FileI,DirectoryI采用了多继承。实现NodeI,可以由FileI,DirectoryI复用;如果FileI,DirectoryI只单继承各自的File,Directory类,则需要分别实现各项公共操作——File,Directory,Node由slice2cpp生成的映射类,不能直接实现公共操作。

NodeI禁止复制构造和赋值,就会使所有继承类也禁止复制构造和赋值。

NodeI成员_name保存节点名称,_parent保存父目录指针,_id保存当前对象的标识。构造函数:
NodeI(const Ice::CommunicatorPtr&, const std::string&, const DirectoryIPtr&);
创建节点时初始化节点名称和父目录,传入通讯器,用于为服务者创建标识(当前实现中未使用这种方法,而是使用了UUID)。

⑵、FileI
Lines类型变量_lines存储文件内容,该类型定义于Filesystem.h中,不用查看该文件,根据slice规范就应该知道Lines为std::vector<std::string>类型——这就需要完全掌握Slice到C++映射的规则,才能帮助你使用和开发Ice应用程序。

⑶、DirectoryI
每个目录都要存储子目录、文件列表,这里使用了NodeSeq类型的_contents变量。根据slice规范定义sequence<Node*> NodeSeq;可知NodeSeq是vector<NodePrx>,每个元素都是指向一个节点的代理。addChild函数用于将子节点加入到NodeSeq序列中。list函数则列出NodeSeq序列中的名称。

3.3 服务者类实现

#include <IceUtil/IceUtil.h>
#include <FilesystemI.h>

using namespace std;

//————————————————————————————————————
// NodeI
//————————————————————————————————————
std::string
Filesystem::NodeI::name(const Ice::Current&)
{
return _name;
}

// NodeI 构造器
Filesystem::NodeI::NodeI(const Ice::CommunicatorPtr& communicator, const string& name, const DirectoryIPtr& parent) : _name(name), _parent(parent)
{
// 创建标识,根目录具有固定标识 "RootDir",非根目录使用UUID,把标识保存到_id成员中。
if(parent)
{
_id.name = IceUtil::generateUUID();
}
else
{
_id.name = "RootDir";
}
}

// NodeI activate() 成员函数
// 创建节点后,调用本函数,NodeI调用适配器的add操作把自身加入到ASM中,
// 并将add返回的代理转化为NodePrx类型,调用addChild添加到它父节点_contents列表中。
// 如果父节点不存在,则什么也不做。
void
Filesystem::NodeI::activate(const Ice::ObjectAdapterPtr& a)
{
NodePrx thisNode = NodePrx::uncheckedCast(a->add(this, _id));
if(_parent)
{
_parent->addChild(thisNode);
}
}

//————————————————————————————————————
// FileI
//————————————————————————————————————
Filesystem::Lines
Filesystem::FileI::read(const Ice::Current&)
{
return _lines; //读取内容
}

void
Filesystem::FileI::write(const Filesystem::Lines& text, const Ice::Current&)
{
_lines = text; //写入内容
}

// FileI 构造器,只是将参数简单地传给NodeI基类
Filesystem::FileI::FileI(const Ice::CommunicatorPtr& communicator, const string& name, const DirectoryIPtr& parent) : NodeI(communicator, name, parent)
{
}

//————————————————————————————————————
// Directory
//————————————————————————————————————
Filesystem::NodeSeq
Filesystem::DirectoryI::list(const Ice::Current& c)
{
return _contents;
}

// DirectoryI 构造器,只是将参数简单地传给NodeI基类
Filesystem::DirectoryI::DirectoryI(const Ice::CommunicatorPtr& communicator, const string& name,const DirectoryIPtr& parent) : NodeI(communicator, name, parent)
{
}

// addChild 由子节点调用以便将自身添加到父目录的_contents成员
void
Filesystem::DirectoryI::addChild(const NodePrx& child)
{
_contents.push_back(child); //vector
}


4、客户端

 

#include <Ice/Ice.h>
#include <Filesystem.h>
#include <iostream>
#include <iterator>

using namespace std;
using namespace Filesystem;

// 以树形风格递归打印目录dir的内容。
// 对于文件,则显示每个文件的内容。
// "depth"参数是当前嵌套层次(用于缩进)。

static void
listRecursive(const DirectoryPrx& dir, int depth = 0)
{
string indent(++depth, '/t'); //缩进

NodeSeq contents = dir->list(); //dir目录下的所有子节点

for (NodeSeq::const_iterator i = contents.begin(); i != contents.end(); ++i) {
DirectoryPrx dir = DirectoryPrx::checkedCast(*i);
FilePrx file = FilePrx::uncheckedCast(*i);
cout << indent << (*i)->name() << (dir ? " (directory):" : " (file):") << endl;
if (dir) {
listRecursive(dir, depth);
} else {
Lines text = file->read();
for (Lines::const_iterator j = text.begin(); j != text.end(); ++j)
cout << indent << "/t" << *j << endl;
}
}
}

int
main(int argc, char* argv[])
{
int status = 0;
Ice::CommunicatorPtr ic;
try {
// 创建通讯器
ic = Ice::initialize(argc, argv);

// 为根目录创建代理
Ice::ObjectPrx base = ic->stringToProxy("RootDir:default -p 10000");
if (!base)
throw "Could not create proxy";

// 将代理向下转型为Directory代理
DirectoryPrx rootDir = DirectoryPrx::checkedCast(base);
if (!rootDir)
throw "Invalid proxy";

// 递归列出根目录的内容
cout << "Contents of root directory:" << endl;
listRecursive(rootDir);
} catch (const Ice::Exception & ex) {
cerr << ex << endl;
status = 1;
} catch (const char * msg) {
cerr << msg << endl;
status = 1;
}

// 清除
if (ic)
ic->destroy();

return status;
}

客户端文件:
Filesystem.h
Filesystem.cpp
Client.cpp


在初始化运行时之后,客户创建一个代理,指向文件系统的根目录。端口为"RootDir:default -p 10000",使用缺省协议(TCP)在10000 端口处侦听。根目录的对象标识叫作RootDir。客户把代理向下转换成DirectoryPrx,并把这个代理传给listRecursive,由它打印出文件系统的内容。

listRecursive 完成了主要工作。它接收目录代理为参数,以及一个缩进层次参数(缩进每次递归调用就增加一层,使得缩进与节点深度对应)。listRecursive 调用目录的list 操作,并且遍历所返回的节点序列:
⑴、代码调用checkedCast,把Node代理窄化成Directory 代理;并且调用uncheckedCast,把Node代理窄化成File代理。在这两个转换中只有、而且肯定会有一个成功,所以不需要两次调用checkedCast:如果节点是Directory,代理就是checkedCast返回的DirectoryPrx;如果checkedCast失败,节点就一定是File,只要使用uncheckedCast就可以正确的获得File。一般而言,如果向下转型肯定能成功的话,最好使用uncheckedCast,而不是checkedCast。
⑵、根据节点性质,打印文件/目录名字。
⑶、如果是目录,代码就会递归调用listRecursive,增加缩进层次;如果是文件,就调用文件的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.

5、总结
现在,我们实现了文件系统的Ice应用程序,但是我们应用程序还有一些问题:
⑴、端点信息硬编码到程序中;
⑵、客户进行了一些不必要的远程调用;
⑶、不支持并发,两个客户同时读、写同一个文件,会产生冲突。

要解决这些问题,需要学习DPWI第4部分高级Ice。通过对Ice的深入了解,可以实现高性能、高并发的Ice应用程序。

 

 

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics