ICE同步调用与异步调用(1)

1 前言

        软件模块之间总是存在着一定的接口,从调用方式上,可以把他们分为三类:同步调用、回调和异步调用。同步调用是一种阻塞式调用,调用方要等待对方执行完毕才返回,它是一种单向调用(oneway);回调是一种双向调用模式(twoway),也就是说,被调用方在接口被调用时也会调用对方的接口;异步调用是一种类似消息或事件的机制,不过它的调用方向刚好相反,接口的服务在收到某种讯息或发生某种事件时,会主动通知客户方(即调用客户方的接口)。

2 常见知识

  • Client(客户端)

       估计这个大家都非常清楚,通俗的讲就是一个请求的发起方。

  • Proxy(代理)

       代理实际上就是远程服务驻在本地的一个代表,创建它时首先会和远程服务经行握手和状态确认等操作,Client所有的操作都是通过Proxy来办理的。代理又分为直接代理(已经知道服务端的位置及其他信息)和间接代理(不知道服务器在哪里,由Registry注册器告诉它地址等信息)。

  • Adapter(适配器)

        Adapter是配置相当于服务单位(Servant)的管理者,它管理着每个请求该分配给哪一个服务单位。

  • Servant(服务单元)

       它是服务的最小单位,一般是具体到 某个接口的实现。

  • Interface(接口)

        ICE我们会经常提到了接口的实现,但是这个接口是谁定义的,这个时候我们不免要提到Slice(Specification Language for Ice),也就是ICE所使用的“语言”,正是有了这个“中间语言”,我们才可以做到支持各种编程语言,因为您所使用的语言只要跟Slice打交道就可以了。

ICE同步调用与异步调用(1)_第1张图片

图2.1 ICE体系结构

ICE同步调用与异步调用(1)_第2张图片

图2.2 ICE支持的平台

3 配置属性

        通过配置文件,我们可以设置Ice程序各个方面的应用,Ice在运行时能够识别配置文件中规定的属性集。Ice只会在创建通信器时读入属性配置。也就是说,若要设置属性的话,必须在创建通讯器以前设置,否则不起作用。

3.1 配置文件

        以#开头部分为注释。属性单词之间可以有空格。属性前、后空格自动清除,中间的空格会保留。

属性格式:<应用程序名>.<类别名>[.<子类别名>]。

        以Ice,IceBox,IceGrid,IcePatch2,IceSSL,IceStorm,Freeze,Glacier2为前缀的属性专用于Ice运行时,不能用于用户程序和配置。

        配置文件可通过环境变量ICE_CONFIG指定,也可以在命令行指定:

>server --Ice.Config=c:/Zeroc/config server.config

       属性也可以在命令行上指定:

>server --Ice.MessageSizeMax=4096 --Ice.Config= c:/Zeroc/config

       无论命令行指定的属性在前,还是在后,命令行指定的属性总是会覆盖配置文件中的同名属性。

3.2 命令行分析、初始化

>server --myoption --Ice.Config=server.config -x --Ice.Trace.Network=3 -y

       当通讯器执行初始化语句:

// ...

Ice::CommunicatorPtr ic;

// ...

ic = Ice::initialize(argc, argv);

// ...

        initialize返回时,argv中包含的形参向量为:--myoption –x –y;与Ice相关参数均被传给Ice运行时,并从形参向量中删除,留给应用程序的只是应用程序特定的参数向量。

         Ice:Application的run函数具有类似功能,传入run函数的参数向量只含有应用程序特定的形参向量。Ice相关形参向量已在调用run函数前,由Ice:Application处理,并传给了Ice运行时。

Ice.ProgramName属性(argv[0])为应用程序名称。

3.3 编程操作属性

         可以通过通讯器的属性方法操作属性。

3.3.1 读取属性

  • getProperty

         返回指定属性的字符串值,属性未定义,返回空串。

  • getPropertyWithDefault

         返回指定属性的字符串值,属性未定义,返回提供的缺省值。

  • getPropertyAsInt

         类似getProperty,返回整数,若非有效数值,则返回0。

  • getPropertyAsIntWithDefault

         类似getPropertyAsInt,若非有效数值,则返回缺省值。

  • getPropertiesForPrefix

        返回指定前缀的属性集合,类型为PropertyDict。

// ...

Ice::CommunicatorPtr ic;

// ...

ic = Ice::initialize(argc, argv);

//

Ice::PropertiesPtr props = ic->getProperties();

Ice::Int maxSize = props->getPropertyAsIntWithDefault("Filesystem.MaxFileSize",1024);

// ...

3.3.2 设置属性

        setProperty可将属性设置为指定值。由于通讯器在调用Ice:initialize(argc,argv);时就会读取配置文件,之后不再重新读配置。因此本函数应该在初始化之前调用,初始化之后设置属性,在重启应用程序前是不起作用的。

        设置属性集:

Ice::PropertiesPtr props = Ice::createProperties(argc, argv);

props->setProperty("Ice.Trace.Network","0");

props->setProperty("Ice.Trace.Protocol","0");

 

// ...

Ice::InitializationData id;

id.properties = props;

Ice::CommunicatorPtr ic = Ice::initialize(id);

// ...

3.3.3 分析属性

  • getCommandLineOptions

        把属性集转换为命令行选项。

  • parseCommandLineOptions

        把命令行选项转换为属性集。

        使用范例:

int main(int argc, char* argv[])

{

    // 创建一个空属性集。

    Ice::PropertiesPtr props = Ice::createProperties();

    // 将argc/argv转换为字符串序列。

    Ice::StringSeq args = Ice::argsToStringSeq(argc, argv);

    // 析出所有以--Filesystem开始的选项。

    args = props->parseCommandLineOptions("Filesystem", args);

    // args现在只包含未析出的选项。所有以--Filesystem开头的选项被转换为属性,

    // 存放在props对象中。现在,把剩余的参数转换回arg/argv向量。

    Ice::stringSeqToArgs(args, argc, argv);

    // 初始化通讯器。

    Ice::InitializationData id;

    id.properties = props;

    Ice::CommunicatorPtr ic = Ice::initialize(argc, argv, id);

    // 创建通讯器之后,argc/argv只包括与Ice属性和Filesystem属性无关的选项。

    // ...

}

 

4 ICE调用模式

        ICE采用的网络协议有TCP、UDP以及SSL三种,不同于WebService,ICE在调用模式上有好几种选择方案,并且每种方案正对不同的网络协议的特性做了相应的选择。

  • Oneway(单向调用)

       客户端只需将调用注册到本地传输缓冲区(Local Transport Buffers)后就立即返回,并不对调用结果负责。

  • Twoway(双向调用)

       最通用的模式,同步方法调用模式,只能用TCP或SSL协议。

  • Datagram(数据报)

       类似于Oneway调用,不同的是 Datagram调用只能采用UDP协议而且只能调用无返回值和无输出参数的方法。

  • BatchOneway(批量单向调用)

       先将调用存在调用缓冲区里面,到达一定限额后自动批量发送所有请求(也可手动刷除缓冲区)。

  • BatchDatagram(批量数据报)

       与上类似。

       不同的调用模式其实对应着不同的业务,对于大部分的有返回值的或需要实时响应的方法,我们可能都采用twoway方式调用,对于一些无需返回值或者不依赖返回值的业务,我们可以用Oneway或者BatchOneway方式,例如消息通知。剩下的Datagram和BatchDatagram方式,一般用在无返回值且不做可靠性检查的业务上,例如日志。

5 同步调用

        默认情况下,使用由ICE的请求调用模型也就是远程同步调用:一个函数操作调用行为像本地函数调用一样,就是客户端在调用期间线程被挂起,而在调用完成之后恢复。

5.1 定义接口文件

#ifndef demo_idl

#define demo_idl

//

// version 1.0.0

//

 

module Demo

{

    interface Hello

    {

        string sayHello(int num, out string strMsg);

    };

};

 

#endif


        利用Slice定义接口/类方法时和其他编程语言很相似,需要一个返回值和若干个参数,但是需要注意的是Slice不支持引用方式的参数传递,参数要么为输入参数,要么为输出参数,不同时为in和out。作为out参数的时候,不管客户端对out参数的初始赋值是什么,在服务端都取不到该值(参数strMsg在服务端始终为空),但是服务端可以对该参数进行赋值,再传递给客户端。还要注意的是:out参数一定是放在所有输入参数后面,不允许交叉使用。

5.2 服务端实现代码

#include 

#include 

using namespace std;

using namespace Demo;

 

class HelloI : public Demo::Hello

{

public:

 

    virtual string sayHello(int nNum, string& strMsg, const Ice::Current&)

    {

        strMsg = "echo message.";

        cout << "Hello World!" << endl;

        return strMsg;

    }

 

};

 

class HelloServer : public Ice::Application

{

public:

 

    virtual int run(int, char*[]);

};

 

int

main(int argc, char* argv[])

{

    HelloServer app;

    return app.main(argc, argv);

}

 

int

HelloServer::run(int argc, char*[])

{

    Ice::CommunicatorPtr ic = communicator(); 

    Ice::ObjectAdapterPtr adapter = ic->createObjectAdapterWithEndpoints("HelloAdapter", "default -p 10000"); 

    Demo::HelloPtr hello = new HelloI;

    adapter->add(hello, communicator()->stringToIdentity("Hello"));

    adapter->activate();

    communicator()->waitForShutdown();

    return EXIT_SUCCESS;

}

 

5.3 客户端实现代码

#include 

#include 

 

using namespace std;

using namespace Demo;

 

class HelloClient : public Ice::Application

{

public:

 

    HelloClient();

 

    virtual int run(int, char*[]);

 

private:

 

    void menu();

};

 

int

main(int argc, char* argv[])

{

    HelloClient app;

    return app.main(argc, argv);

}

 

//

// Since this is an interactive demo we don't want any signal

// handling.

//

HelloClient::HelloClient() :

    Ice::Application(Ice::NoSignalHandling)

{

}

 

int

HelloClient::run(int argc, char* argv[])

{

    Ice::CommunicatorPtr ic = communicator(); 

    Ice::ObjectPrx base = ic->stringToProxy("Hello:default -h 192.168.11.100 -p 10000"); 

    HelloPrx hello =  HelloPrx::checkedCast(base); 

    if (!hello) 

    {

        cerr << argv[0] << ": invalid proxy" << endl;

        return EXIT_FAILURE;

    }

    if (argc > 1)

    {

        cerr << appName() << ": too many arguments" << endl;

        return EXIT_FAILURE;

    }

 

    menu();

 

    char c;

    do

    {

        try

        {

            int nNum = 1;

            string strMsg = "Hello World!";

            cout << "==> ";

            cin >> c;

            if (c == 't')

            {

                string strRet = hello->sayHello(nNum, strMsg);

                cout << strRet << endl;

            }

            else if (c == 'x')

            {

                // Nothing to do

            }

            else if(c == '?')

            { 

                menu();

            }

            else

            {

                cout << "unknown command `" << c << "'" << endl;

                menu();

            }

        }

        catch (const Ice::Exception& ex)

        {

            cerr << ex << endl;

        }

    }

    while (cin.good() && c != 'x');

 

    return EXIT_SUCCESS;

}

 

void

HelloClient::menu()

{

    cout <<

        "usage:\n"

        "t: send greeting to server\n"

        "x: exit\n"

        "?: help\n";

}

 

6 异步调用

       ICE具备异步方法调用的API函数(AMI-- Asynchronous Method Invocation)。然而,这个API相当冗长而且使用不容易,应用不灵活。随着ICE3.4发布之后,ZeroC引入了一个新的API的异步方法调用不再遭受这些问题了,为开发人员提供了更多的选择,他们如何可以构建自己的代码。新特性的API适合可用于C++,Java,和Python。

       ICE异步有两种形式,一种是客户端的AMI形式,一种是服务器端的AMD(Asynchronous Method Dispatch),在3.4版本之后AMI已经不再显示指定时,slice就会生成callback类,在客户端直接用say()是同步的,begin_say( …)就是异步的。

       在实际的项目中,AMD操作一般将request data存入队列(比如,需要回调的对象和参数),让应用的线程(或线程池)稍后处理。这样,服务器就可以节省分配线程,所以可以高效同时支持上千个客户端。

       另外一种AMD的用途是当客户请求结束后需要继续处理。为了使客户端产生最小的时延,当操作还再分配的时候就立即返回,然后用分派线程去干其他的事情。

        AMI是客户端的功能,客户端调用完后马上返回,保证客户端不阻塞。AMD是服务器端功能,服务器端相应客户调用过程中。使用AMD可以在处理还没有完成时,就提前返回结果,就像在函数中间加个return语句一样,不同的是这个return下面的语句会接着执行。

        还有一点很有特色的是,AMI和AMD是完全互相独立的,也就是说对于同一个interface,客户端不会知道服务器是否用AMD方式来回应请求,服务器端也不会知道客户端是否用AMI发起调用。而且,它们也无需知道,这些都是ICE内部实现的细节。

        本文概述了最新的C++ API其最重要的特点。旧的API仍然可用,您可以在同一个程序中使用新的和旧的API。这会使您逐渐迁移旧代码,而不必马上改变您编写所有的旧API代码。我们强烈建议您使用新的API。(旧的API已被废弃,最终将被彻底删除了。)

6.1 定义接口文件

#ifndef demo_ice

#define demo_ice

//

// version 1.0.0

//

 

module Demo

{

    interface Hello

    {

        ["ami" ] void say(string strMsg);

    };

};

 

#endif // demo_ice

 

6.2 服务端实现代码

#include 

#include 

 

using namespace std;

using namespace Demo;

 

class HelloI : public Demo::Hello

{

public:

 

    virtual void say(const string& strMsg, const Ice::Current&);

    {

        cout << strMsg << endl;

    }

};

 

class HelloServer : public Ice::Application

{

public:

 

    virtual int run(int, char*[]);

};

 

int

main(int argc, char* argv[])

{

    HelloServer app;

    return app.main(argc, argv);

}

 

int

HelloServer::run(int argc, char*[])

{

    Ice::CommunicatorPtr ic = communicator(); 

    Ice::ObjectAdapterPtr adapter = ic->createObjectAdapterWithEndpoints("HelloAdapter", "default -p 10000"); 

    Demo::HelloPtr hello = new HelloI;

    adapter->add(hello, communicator()->stringToIdentity("Hello"));

    adapter->activate();

    communicator()->waitForShutdown();

    return EXIT_SUCCESS;

}

 

6.3 客户端实现代码

#include 

#include 

 

using namespace std;

using namespace Demo;

 

struct AMI_Hello : AMI_Hello_say 

{ 

    virtual void ice_response() 

    { 

        cout << "Server ice response" << endl; 

    }

 

    virtual void ice_exception(const ::Ice::Exception& e) 

    { 

        cout << "Error:" << e << endl; 

    } 

}; 

 

class HelloClient : public Ice::Application

{

public:

 

    HelloClient();

 

    virtual int run(int, char*[]);

 

private:

 

    void menu();

};

 

int

main(int argc, char* argv[])

{

    HelloClient app;

    return app.main(argc, argv);

}

 

//

// Since this is an interactive demo we don't want any signal

// handling.

//

HelloClient::HelloClient() :

    Ice::Application(Ice::NoSignalHandling)

{

}

 

int

HelloClient::run(int argc, char* argv[])

{

    Ice::CommunicatorPtr ic = communicator(); 

    Ice::ObjectPrx base = ic->stringToProxy("Hello:default -h 192.168.11.100 -p 10000"); 

    HelloPrx hello =  HelloPrx::checkedCast(base); 

    if (!hello) 

    {

        cerr << argv[0] << ": invalid proxy" << endl;

        return EXIT_FAILURE;

    }

    if (argc > 1)

    {

        cerr << appName() << ": too many arguments" << endl;

        return EXIT_FAILURE;

    }

 

    menu();

 

    char c;

    do

    {

        try

        {

            string strMsg = "Hello World!";

            cout << "==> ";

            cin >> c;

            if (c == 't')

            {

                hello->say(strMsg);

            }

            else if (c == 'a')

            {

                hello->say_async(new AMI_Hello, strMsg); 

                cout << "do other things..."  << endl;

            }

            else if (c == 'n')

            {

                Ice::AsyncResultPtr asyncPtr = hello->begin_say(strMsg); 

                cout << "do other things..."  << endl;

                asyncPtr->waitForCompleted();

                hello->end_say(asyncPtr);   

            }

            else if (c == 'x')

            {

                // Nothing to do

            }

            else if(c == '?')

            { 

                menu();

            }

            else

            {

                cout << "unknown command `" << c << "'" << endl;

                menu();

            }

        }

        catch (const Ice::Exception& ex)

        {

            cerr << ex << endl;

        }

    }

    while (cin.good() && c != 'x');

 

    return EXIT_SUCCESS;

}

 

void

HelloClient::menu()

{

    cout <<

        "usage:\n"

        "t: send greeting to server\n"

        "a: send greeting to server(Asynchronous Method Invocation)\n"  

         "n: send greeting to server(new AMI)\n"

        "x: exit\n"

        "?: help\n";

}


 

你可能感兴趣的:(ice,C++)