ICE的异步方法调用 c++
http://www.cppprog.com/2009/0612/125.html
第17章《异步程序设计》
网上摘录比较清晰的解释:
“AMI是客户端的功能,I = Invoke,客户端调用完后马上返回,保证客户端不阻塞。 AMD是服务器端功能,D= Dispatch, 服务器端相应客户调用过程中,使用AMD可以在处理还没有完成时就提前返回结果,就像在函数中间加个return语句一样,不同的是这个return下面 的语句会接着执行。 还有一点很有特色的是,AMI和AMD是完全互相独立的,也就是说对于同一个interface,客户端不会知道服务器是否用AMD方式相应请求,服务器 端也不会知道客户端是否用AMI发起调用。 而且,他们也无需知道,这是实现的细节,不是接口的契约。 ”
异步方法调用(Asynchronous Method Invocation,简称AMI)
下面这种情况就是AMI调用:“斧头帮”大哥(客户端)叫小弟(服务器端)去干收租的活(远程调用),并且给小弟一把烟花炮竹(回调类)。嘱咐说: “我还有其它事情要打扫打扫,如果你的事情办完了,就放'OK'烟花;如果遇到反抗,就放'斧头'烟花!”(服务器答复)。说完,这位大哥就可以放心的做 其它事去了,直到看到天上烟花盛开,根据"OK"或"斧头"状再作处理。
AMI是针对客户端而言的,当客户端使用AMI发出远程调用时,客户端需要提供一个实现了回调接口的类用于接收通知。然后不等待调用完成立即返回,这时可以继续其它活动,当得到服务器端的答复时,客户端的回调类中的方法就会被执行。
例:修改原Helloworld客户端,使用异步方法远程调用printString。
首先,要修改原来的Printer.ice定义文件,在printString方法前加上["ami"]元标识符。
- module Demo{
- interface Printer
- {
- ["ami"] void printString(string s);
- };
- };
同样,再用slice2cpp Printer.ice生成Printer.h和Printer.cpp文件,并把这两个文件加入原项目(如果是直接修改之前的代码的话,因为原先已经加入了这两个文件,这步可以跳过)。
观察生成的Printer.h文件,可以找到这个定义:
- namespace Demo
- {
- class AMI_Printer_printString : public ::IceInternal::OutgoingAsync
- {
- public:
- virtual void ice_response() = 0;
- virtual void ice_exception(const ::Ice::Exception&) = 0;
- ...
- };
- }
这里的AMI_Printer_printString就是printString方法的AMI回调接口,可以发现它AMI回调接口类名的规律是AMI_类名_方法名。
这个接口提供了两个方法:
void ice_response(<params>);
表明操作已成功完成。各个参数代表的是操作的返回值及out 参数。如果操作的有一个非 void返回类型,ice_response 方法的第一个参数就是操作的返回值。操作的所有out 参数都按照声明时的次序,跟在返回值的后面。
void ice_exception(const Ice::Exception &);
表明抛出了本地或用户异常。
同时,slice2cpp还为Printer代理类生成了异步版本的printString方法:
- namespace IceProxy //是代理类
- {
- namespace Demo
- {
- class Printer : virtual public ::IceProxy::Ice::Object
- {
- ...
- public:
- bool printString_async(const ::Demo::AMI_Printer_printStringPtr&,
- const ::std::string&);
- bool printString_async(const ::Demo::AMI_Printer_printStringPtr&,
- const ::std::string&, const ::Ice::Context&);
- ...
- };
- }
- }
结合这两样东西(AMI_Printer_printString接口和printString_async方法),我们的客户端AMI调用方法为:
- 实现AMI_Printer_printString接口的ice_response和ice_exception方法,以响应远程调用完成后的工作。
- 把上面实现的回调对象作为printString_async的参数启动远程调用,然后可以做其它事了。
- 当得到服务端答复后,AMI_Printer_printString接口的ice_response的方法被调用。
演示代码(客户端):
- #include <ice/ice.h>
- #include <printer.h>
- using namespace std;
- using namespace Demo;
- //实现AMI_Printer_printString接口
- struct APP : AMI_Printer_printString
- {
- virtual void ice_response()
- {
- cout << "printString完成" << endl;
- }
- virtual void ice_exception(const ::Ice::Exception& e)
- {
- cout << "出错啦:" << e << endl;
- }
- };
- class MyApp: public Ice::Application{
- public:
- virtual int run(int argc, char*argv[])
- {
- Ice::CommunicatorPtr ic = communicator();
- Ice::ObjectPrx base =
- ic->stringToProxy("SimplePrinter:default -p 10000");
- Demo::PrinterPrx printer = PrinterPrx::checkedCast(base);
- if(!printer) throw "Invalid Proxy!";
- // 使用AMI异步调用
- printer->printString_async(new APP,"Hello World!");
- cout << "做点其它的事情..." << endl;
- system("pause");
- return 0;
- }
- };
- int main(int argc, char* argv[])
- {
- MyApp app;
- return app.main(argc,argv);
- }
服务端代码不变,编译运行,效果应该是调用printer->printString_async之后还能"做点其它的事情...",当服务端完成后客户端收到通知,显示"printString完成"。
另外,为了突出异步效果,可以修改服务器端代码,故意把printString执行得慢一点:
- struct PrinterImp : Printer{
- virtual void printString(const ::std::string& s,
- const ::Ice::Current&)
- {
- Sleep(1000);
- cout << s << endl;
- }
- };
异步方法分派(Asynchronous Method Dispatch,简称AMD)
AMD是针对服务器端而言的,在同步的情况下,服务器端收到一个调用请求后,在线程池中拿出一个空闲线程用于执行这个调用。这样,服务器在同一时刻所能支持的同步请求数受到线程池大小的限制。
如果线程池内的线程都在忙于执行长时间的操作,那么新的请求到来时就会处于长时间得不到答复的状态,这可能会造成客户端长时间等待(如果客户端没使用AMI的话)。
ICE的解决方法是:服务器收到请求时并不马上执行具体工作,而是把执行这项工作所需的参数以及回调类保存到一个地方(比如队列)后就返回。而另外的线程(或线程池)负责取出保存的参数并执行之,执行结束后使用回调类通知客户端工作已完成(或异常)。
还是用上面“斧头帮”来举例:“斧头帮”大哥(客户端)叫小弟(服务器端)去干收租的活(远程调用),这位小弟并不是马上就去收租去了,而是把这件 工作记录到他的日程表里(同时还有好几个老板叫他干活呢,可怜的人啊~~)。然后等有空的时候再按日程表一项项的做(或者叫其它有空的弟兄帮忙做),做完 工作后该放烟花的就放烟花(回调智能客户端),该砍人的就放信号弹啥的。
例:修改原Helloworld服务器端,使用异步方法分派处理printString方法。
首先,要修改原来的Printer.ice定义文件,在printString方法前加上["amd"]元标识符。
- module Demo{
- interface Printer
- {
- ["amd"] void printString(string s);
- };
- };
同样,再用slice2cpp Printer.ice生成Printer.h和Printer.cpp文件,并把这两个文件加入原项目(如果是直接修改之前的代码的话,因为原先已经加入了这两个文件,这步可以跳过)。
观察生成的Printer.h文件,可以发现和AMI类似的一个回调接口AMD_Printer_printString:
- namespace Demo
- {
- class AMD_Printer_printString : virtual public ::IceUtil::Shared
- {
- public:
- virtual void ice_response() = 0;
- virtual void ice_exception(const ::std::exception&) = 0;
- virtual void ice_exception() = 0;
- };
- ...
- }
这个回调接口由ICE自己实现,我们只要拿来用就可以了。在哪里用呢?马上就会发现:我们要实现的Printer接口的printString方法不见了,取而代之的是printString_async方法:
- namespace Demo
- {
- class Printer : virtual public ::Ice::Object
- {
- ...
- virtual void printString_async(
- const ::Demo::AMD_Printer_printStringPtr&,
- const ::std::string&, const ::Ice::Current& = ::Ice::Current()) = 0;
- ...
- };
- }
这个printString_async方法就是我们要实现的异步分派方法,它的第一个参数就是由ICE实现的回调类AMD_Printer_printString,在这个方法里,我们要两种方案:
- 直接做具体工作,完成后在末尾调用回调类的ice_response方法告知客户端已完成。这种方案就和之前普通版的服务端一样,是同步执行的。
- 把回调类和请求所需要的参数放入一个指定的位置,再由其它线程取出执行和通知客户端。这种方案就是异步分派方法,具体实现时还可以有多种 方式,如使用命令模式把参数和具体操作直接封装成一个对象放入队列,然后由另一线程(或线程池)取出执行。后面的示例代码为了简单起见直接使用了 Windows API中的线程池功能,而且也没有使用队列。
示例代码
- #include <ice/ice.h>
- #include "printer.h"
- using namespace std;
- using namespace Demo;
- // 传递给线程函数的参数
- struct CallbackEntry{
- AMD_Printer_printStringPtr callback;
- string str;
- };
- // 线程函数
- DWORD WINAPI DoPrintString(LPVOID lpParameter)
- {
- // 取得参数
- CallbackEntry *pCE = (CallbackEntry *)lpParameter;
- // 工作:打印字符(延时1秒模拟长时间操作)
- Sleep(1000);
- cout << pCE->str << endl;
- // 回调,工作完成。如果工作异常,则调用ice_exception();
- pCE->callback->ice_response();
- // 删除参数(这里使用堆直接传递,其实更好的方法是使用队列)
- delete pCE;
- return TRUE;
- }
- struct PrinterImp : Printer{
- virtual void printString_async(
- const AMD_Printer_printStringPtr &callback,
- const string& s, const Ice::Current&)
- {
- // 参数打包(回调类和pringString方法的参数)
- CallbackEntry *pCE = new CallbackEntry;
- pCE->callback = callback;
- pCE->str = s;
- // 让Windows线程池来执行具体任务
- ::QueueUserWorkItem(DoPrintString,pCE,WT_EXECUTEDEFAULT);
- }
- };
- class MyApp : public Ice::Application{
- public:
- virtual int run(int n, char* v[]){
- Ice::CommunicatorPtr& ic = communicator();
- Ice::ObjectAdapterPtr adapter
- = ic->createObjectAdapterWithEndpoints("SP","default -p 10000");
- Ice::ObjectPtr object = new PrinterImp;
- adapter->add(object, ic->stringToIdentity("SimplePrinter"));
- adapter->activate();
- ic->waitForShutdown();
- return 0;
- }
- };
- int main(int argc, char* argv[])
- {
- MyApp app;
- return app.main(argc, argv);
- }
客户端不需要改变,编译运行服务器然后用客户端测试效果。(其实效果不是很明显,因为AMD提高的是服务器的负荷能力)
DEMO
最近学习了异步通信这一章:异步包括服务端的异步分派(AMD)以及客户端的异步调用(AMI)。
下面介绍一下如何实现简单的异步通信(主要实现了客户端的异步调用,AMD暂未体现):
1.首先编写Ice文件:Demo.ice
- module Demo{
- interface Employee{
- ["amd"]void add(int a, int b, out int sum);
- };
- };
["amd"]表示的是元数据,指定服务器端将提供异步分派的方法函数。该add函数中有两个in参数;一个out参数,作为回传参数。
2.映射到C++代码:使用命令 slice2cpp Demo.ice
经过映射之后,程序将生成Demo.h与Demo.cpp文件,如下面部分代码所示Demo.h
- // ....More code before
- namespace Demo
- {
- class Employee : virtual public ::Ice::Object
- {
- public:
- typedef EmployeePrx ProxyType;
- typedef EmployeePtr PointerType;
- virtual ::Ice::ObjectPtr ice_clone() const;
- virtual bool ice_isA(const ::std::string&, const ::Ice::Current& = ::Ice::Current()) const;
- virtual ::std::vector< ::std::string> ice_ids(const ::Ice::Current& = ::Ice::Current()) const;
- virtual const ::std::string& ice_id(const ::Ice::Current& = ::Ice::Current()) const;
- static const ::std::string& ice_staticId();
- virtual void add_async(const ::Demo::AMD_Employee_addPtr&, ::Ice::Int, ::Ice::Int, const ::Ice::Current& = ::Ice::Current()) = 0;
- // ... More code here
- };
- // ...
3. 实现服务端程序
新建Server.cpp文件,将Demo.h中的虚方法add_async()实现。并新建servant类来继承Ice::application类,初始化Ice run time ,同时将servant注册到对象适配器中去。
- #include <Demo.h>
- #include <Ice/Application.h>
- #include <IceUtil/Monitor.h>
- #include <iostream>
- using namespace std;
- using namespace Demo;
- class Manager : virtual public Employee,public IceUtil::Monitor<IceUtil::Mutex>
- {
- public:
- Manager():_sum(0){}
- ~Manager(){}
- virtual void add_async(const ::Demo::AMD_Employee_addPtr& add_ptr, int a, int b, const ::Ice::Current& =::Ice::Current())
- {
- _sum = a+b;
- sleep(1);
- cout<< "The sum is " <<_sum<<"."<<endl;
- add_ptr->ice_response(_sum); //数据处理完之后,回调函数,调用客户端实现的方法
- }
- private:
- int _sum;
- };
- class myApplication : virtual public Ice::Application
- {
- public:
- virtual int run(int argc , char* argv[])
- {
- Ice::ObjectAdapterPtr adapter =
- communicator()->createObjectAdapterWithEndpoints("GameServer", "default -p 10000");
- Ice::ObjectPtr Manptr = new Manager;
- adapter->add(Manptr, communicator()->stringToIdentity("ManServer"));//将servant类对象指针ManPtr注册到适配器中
- adapter->activate(); //激活适配器,使客户端能够访问适配器中已添加的servant活动表
- communicator()->waitForShutdown();
- }
- };
- int main(int argc , char * argv[])
- {
- myApplication app;
- app.main(argc , argv);
- return 0;
- }
4.实现客户端程序
新建Client.cpp文件,实现回调函数,创建访问指定servant的代理。
- #include<Ice/Ice.h>
- #include<Demo.h>
- #include<iostream>
- #include<IceUtil/IceUtil.h>using namespace std;
- using namespace Demo;class Client_add:public AMD_Employee_add
- {
- public:
- virtual void ice_response(int sum)
- {
- cout << "receive from server--The sum is "<<sum<<"."<<endl;
- }
- virtual void ice_exception(){}
- virtual void ice_exception(const std::exception& e){}
- void failed_operation(const Ice::Exception &e){}
- };
- typedef IceUtil::Handle<Client_add> ClientAddPtr;class ClientApp : virtual public Ice::Application
- {
- public:
- virtual int run(int argc , char* argv[])
- {
- Ice::ObjectPrx base = communicator()->stringToProxy("ManServer:default -p 10000"); EmployeePrx emp = EmployeePrx::checkedCast(base); if(!emp)throw "Invalid Proxy!"; ClientAddPtr amd_add = new Client_add;
- Callback_Employee_addPtr addPtr=newCallback_Employee_add(amd_add,&Client_add::ice_response,&Client_add::failed_operation); //实现回调对象指针
- emp->begin_add(10,20,addPtr); //第三个参数为回调类的智能指针 cout<<"I'm waiting for the data from,but not blocked."<<endl; }
- };int main(int argc , char* argv[])
- {
- ClientApp app;
- app.main(argc , argv); return 0;
- }
5. 编译Server.cpp: g++ Demo.cpp Server.cpp -o Server -lIce -lIceUtil -I./ -I/opt/Ice-3.4.2/include -L/opt/Ice-3.4.2/lib
编译Client.cpp: g++ Demo.cpp Client.cpp -o Client -lIce -lIceUtil -I./ -I/opt/Ice-3.4.2/include -L/opt/Ice-3.4.2/lib
运行服务端:./Server 运行客户端:./Client