ICE简介及C++程序例子 |
您所在的位置:网站首页 › ice模式是什么意思 › ICE简介及C++程序例子 |
一、ICE简介: 1、ICE是什么? ICE是ZEROC的开源通信协议产品,它的全称是:The Internet Communications Engine,翻译为中文是互联网通信引擎,是一个面向对象的中间件,使我们能够以最小的代价构建分布式应用程序。ICE使我们专注于应用逻辑的开发,它来处理所有底层的网络接口编程,这样我们就不用去考虑这样的细节:打开网络连接、网络数据传输的序列化与反序列化、连接失败的尝试次数等。 2、为什么会有ICE? ICE是分布式应用的一种比较好的解决方案,虽然现在也有一些比较流行的分布式应用解决方案,如微软的.NET(以及原来的DCOM)、CORBA及WEB SERVICE等,但是这些面向对象的中间件都存在一些不足: .NET是微软产品,只面向WINDOWS系统,而实际的情况是在当前的网络环境下,不同的计算机会运行不同的系统,如LINUX上面就不可能使用.NET; CORBA虽然在统一标准方面做了很多的工作,但是不同的供应商实现之间还是缺乏互操作性,并且目前还没有一家供应商可以针对所有的异种环境提供所有的实现支持,且CORBA的实现比较复杂,学习及实施的成本都会比较高; WEB SERVICE最要命的缺点就是他的性能问题,对于要求比较高的行业是很少会考虑WEB SERVICE的。 ICE的产生就是源于.NET、CORBA及WEB SERVICE这些中间件的不足,它可以支持不同的系统,如WINDOWS、LINUX等,也可以支持在多种开发语言上使用,如C++、C、JAVA、RUBY、PYTHON、VB等,服务端可以是上面提到的任何一种语言实现的,客户端也可以根据自己的实际情况选择不同的语言实现,如服务端采用C语言实现,而客户端采用JAVA语言实现,底层的通讯逻辑通过ICE的封装实现,我们只需要关注业务逻辑。 3、ICE是如何工作的? Ice 是一种面向对象的中间件平台,这意味着 Ice为构建面向对象的客户-服务器应用提供了工具、API 和库支持。要与Ice持有的对象进行通信,客户端必须持有这个对象的代理(与CORBA的引用是相同的意思),这里的代理指的是这个对象的实例,ICE在运行时会定位到这个对象,然后寻找或激活它,再把In参数传给远程对象,再通过Out参数获取返回结果。 这里提到的代理又分为直接代理和间接代理,直接代理其内部保存有某个对象的标识,以及它的服务器的运行地址;间接代理指的是其内部保存有某个对象的标识,以及对象适配器名(object adapter name),间接代理没有包含寻址信息,为了正确地定位服务器,客户端在运行时会使用代理内部的对象适配器名,将其传给某个定位器服务,比如IcePack服务,然后,定位器会把适配器名当作关键字,在含有服务器地址的表中进行查找,把当前的服务器地址返回给客户,客户端 run time现在知道了怎样联系服务器,就会像平常一样分派 (dispatch)客户请求。 ICE可以保证在任何的网络环境或者操作系统下,成功的调用只有一次,它在运行时会尽力的定位到远程服务器,在连接失败的情况下会做尝试性重复性连接,确实连不上的情况会给用户以提示。 客户端在调用服务端的方法时,可以采取同步或异步的方式实现,同步调用就相当于调用自己本地的方法一样,其它行为会被阻塞;异步调用是非常有用的调用方式,如服务端需要准备的数据来自于其它异步接口,这个时候客户端就不需要等待,待服务端数据准备充份后,以消息的方式通知客户端,服务端就可以去干其它的事情了,而客户端也可以到服务端获取数据了。 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的安装: 第一步,基于Windows下的安装,所以下载windows版的Ice,官网最新版本是Ice3.4.2: http://www.zeroc.com/download/Ice/3.4/Ice-3.4.2.msi 第二步,安装Ice: 常规安装即可,可以选择安装目录,本次安装是在D:\Ice 第三步,设置Ice的环境变量: 主要有环境变量需要设置:path、classpath、ICE_HOME 例如: path:D:\Ice\bin; classpath:D:\Ice\lib\Ice.jar;D:\Ice\lib\Freeze.jar;D:\Ice\lib\db.jar; ICE_HOME:D:\Ice 第四步,检验: 在命令行中输入:icegridnode --version 如果现实当前安装的ice版本号,那么就说明安装成功。 三、C++示例: 1、服务端: a、添加一个名为Client的win32控制台应用程序的空白项目。 b、在属性页配置属性->C/C++->常规->附加包含目录加入$(ICE_HOME)\include; c、在属性页配置属性->链接器->常规->附加依赖项加入$(ICE_HOME)\lib; d、在属性页配置属性->链接器->输入->附加依赖项加入加入iced.lib和iceutild.lib,也可以直接用$(ICE_HOME)\lib。但是这一步以上两种加入都会出错,错误为:LINK : fatal error LNK1104: 无法打开文件“C :\Program.obj”;所以最好是在工程里直接将那两个库添加进工程来。e、新增一个Caculator.ice文件。用记事本来写即可以: module MCal { //定义ICE接口,映射为C++里同名的一个类(纯虚类,因此不能实例化,必须继承并重载类中的 相应方法) interface Cal { //定义接口里的操作,映射为C++里同名的一个纯虚拟方法 int add(int num1,int num2); int sub(int num1,int num2); }; }; f、编译Caculator.ice会生成Caculator.h 和Cacultor.cpp两个文件。 编译方法一:打开cmd,输入slice2cpp Caculator.ice的路径 ,如slice2cpp D:\TestIce\TestIce\ Caculator.ice。或许在你建立的工程中找不到这两个文件,搜寻一下,看看在哪,然后将这两个文件 放在Caculator.ice的目录下。 g、Caculator.ice编译成功后,建立服务器端CaculatorServer.cpp,type.h type.h文件: #ifndef _TYPE_H #define _TYPE_H #pragma once #include "Calculate.h" using namespace std; using namespace MCal; class CalI:public Cal { public: CalI(); ~CalI(); virtual int add(int num1,int num2,const Ice::Current&); virtual int sub(int num1,int num2,const Ice::Current&); private: int num1d; int num2d; }; #endif CaculatorServer.cpp文件: // ********************************************************************** // Copyright (c) 2003-2011 ZeroC, Inc. All rights reserved. // This copy of Ice is licensed to you under the terms described in the // ICE_LICENSE file included in this distribution. // ********************************************************************** // Ice version 3.4.2// // Generated from file `Calculate.ice' // Warning: do not edit this file. // #include "Calculate.h" #include #include #include #include #ifndef ICE_IGNORE_VERSION # if ICE_INT_VERSION / 100 != 304 # error Ice version mismatch! # endif # if ICE_INT_VERSION % 100 > 50 # error Beta header file detected # endif # if ICE_INT_VERSION % 100 < 2 # error Ice patch level mismatch! # endif #endif static const ::std::string __MCal__Cal__add_name = "add"; static const ::std::string __MCal__Cal__sub_name = "sub"; ::Ice::Object* IceInternal::upCast(::MCal::Cal* p) { return p; } ::IceProxy::Ice::Object* IceInternal::upCast(::IceProxy::MCal::Cal* p) { return p; } void MCal::__read(::IceInternal::BasicStream* __is, ::MCal::CalPrx& v) { ::Ice::ObjectPrx proxy; __is->read(proxy); if(!proxy) { v = 0; } else { v = new ::IceProxy::MCal::Cal; v->__copyFrom(proxy); } } ::Ice::Int IceProxy::MCal::Cal::add(::Ice::Int num1, ::Ice::Int num2, const ::Ice::Context* __ctx) { int __cnt = 0; while(true) { ::IceInternal::Handle< ::IceDelegate::Ice::Object> __delBase; try { __checkTwowayOnly(__MCal__Cal__add_name); __delBase = __getDelegate(false); ::IceDelegate::MCal::Cal* __del = dynamic_cast< ::IceDelegate::MCal::Cal*>(__delBase.get()); return __del->add(num1, num2, __ctx); } catch(const ::IceInternal::LocalExceptionWrapper& __ex) { __handleExceptionWrapper(__delBase, __ex); } catch(const ::Ice::LocalException& __ex) { __handleException(__delBase, __ex, true, __cnt); } } } ::Ice::AsyncResultPtr IceProxy::MCal::Cal::begin_add(::Ice::Int num1, ::Ice::Int num2, const ::Ice::Context* __ctx, const ::IceInternal::CallbackBasePtr& __del, const ::Ice::LocalObjectPtr& __cookie) { __checkAsyncTwowayOnly(__MCal__Cal__add_name);::IceInternal::OutgoingAsyncPtr __result = new ::IceInternal::OutgoingAsync(this, __MCal__Cal__add_name, __del, __cookie); try { __result->__prepare(__MCal__Cal__add_name, ::Ice::Normal, __ctx); ::IceInternal::BasicStream* __os = __result->__getOs(); __os->write(num1); __os->write(num2); __os->endWriteEncaps(); __result->__send(true); } catch(const ::Ice::LocalException& __ex) { __result->__exceptionAsync(__ex); } return __result; } ::Ice::Int IceProxy::MCal::Cal::end_add(const ::Ice::AsyncResultPtr& __result) { ::Ice::AsyncResult::__check(__result, this, __MCal__Cal__add_name); ::Ice::Int __ret; if(!__result->__wait()) { try { __result->__throwUserException(); } catch(const ::Ice::UserException& __ex) { throw ::Ice::UnknownUserException(__FILE__, __LINE__, __ex.ice_name()); } } ::IceInternal::BasicStream* __is = __result->__getIs(); __is->startReadEncaps(); __is->read(__ret); __is->endReadEncaps(); return __ret; } ::Ice::Int IceProxy::MCal::Cal::sub(::Ice::Int num1, ::Ice::Int num2, const ::Ice::Context* __ctx) { int __cnt = 0; while(true) { ::IceInternal::Handle< ::IceDelegate::Ice::Object> __delBase; try { __checkTwowayOnly(__MCal__Cal__sub_name); __delBase = __getDelegate(false); ::IceDelegate::MCal::Cal* __del = dynamic_cast< ::IceDelegate::MCal::Cal*>(__delBase.get()); return __del->sub(num1, num2, __ctx); } catch(const ::IceInternal::LocalExceptionWrapper& __ex) { __handleExceptionWrapper(__delBase, __ex); } catch(const ::Ice::LocalException& __ex) { __handleException(__delBase, __ex, true, __cnt); } } } ::Ice::AsyncResultPtr IceProxy::MCal::Cal::begin_sub(::Ice::Int num1, ::Ice::Int num2, const ::Ice::Context* __ctx, const ::IceInternal::CallbackBasePtr& __del, const ::Ice::LocalObjectPtr& __cookie) { __checkAsyncTwowayOnly(__MCal__Cal__sub_name); ::IceInternal::OutgoingAsyncPtr __result = new ::IceInternal::OutgoingAsync(this, __MCal__Cal__sub_name, __del, __cookie); try { __result->__prepare(__MCal__Cal__sub_name, ::Ice::Normal, __ctx); ::IceInternal::BasicStream* __os = __result->__getOs(); __os->write(num1); __os->write(num2); __os->endWriteEncaps(); __result->__send(true); } catch(const ::Ice::LocalException& __ex) { __result->__exceptionAsync(__ex); } return __result; } ::Ice::Int IceProxy::MCal::Cal::end_sub(const ::Ice::AsyncResultPtr& __result) { ::Ice::AsyncResult::__check(__result, this, __MCal__Cal__sub_name); ::Ice::Int __ret; if(!__result->__wait()) { try { __result->__throwUserException(); } catch(const ::Ice::UserException& __ex) { throw ::Ice::UnknownUserException(__FILE__, __LINE__, __ex.ice_name()); } } ::IceInternal::BasicStream* __is = __result->__getIs(); __is->startReadEncaps(); __is->read(__ret); __is->endReadEncaps(); return __ret; } const ::std::string& IceProxy::MCal::Cal::ice_staticId() { return ::MCal::Cal::ice_staticId(); } ::IceInternal::Handle< ::IceDelegateM::Ice::Object> IceProxy::MCal::Cal::__createDelegateM() { return ::IceInternal::Handle< ::IceDelegateM::Ice::Object>(new ::IceDelegateM::MCal::Cal); } ::IceInternal::Handle< ::IceDelegateD::Ice::Object> IceProxy::MCal::Cal::__createDelegateD() { return ::IceInternal::Handle< ::IceDelegateD::Ice::Object>(new ::IceDelegateD::MCal::Cal); } ::IceProxy::Ice::Object* IceProxy::MCal::Cal::__newInstance() const { return new Cal; } ::Ice::Int IceDelegateM::MCal::Cal::add(::Ice::Int num1, ::Ice::Int num2, const ::Ice::Context* __context) { ::IceInternal::Outgoing __og(__handler.get(), __MCal__Cal__add_name, ::Ice::Normal, __context); try { ::IceInternal::BasicStream* __os = __og.os(); __os->write(num1); __os->write(num2); } catch(const ::Ice::LocalException& __ex) { __og.abort(__ex); } bool __ok = __og.invoke(); ::Ice::Int __ret; try { if(!__ok) { try { __og.throwUserException(); } catch(const ::Ice::UserException& __ex) { ::Ice::UnknownUserException __uue(__FILE__, __LINE__, __ex.ice_name()); throw __uue; } } ::IceInternal::BasicStream* __is = __og.is(); __is->startReadEncaps(); __is->read(__ret); __is->endReadEncaps(); return __ret; } catch(const ::Ice::LocalException& __ex) { throw ::IceInternal::LocalExceptionWrapper(__ex, false); } }::Ice::IntIceDelegateM::MCal::Cal::sub(::Ice::Int num1, ::Ice::Int num2, const ::Ice::Context* __context) { ::IceInternal::Outgoing __og(__handler.get(), __MCal__Cal__sub_name, ::Ice::Normal, __context); try { ::IceInternal::BasicStream* __os = __og.os(); __os->write(num1); __os->write(num2); } catch(const ::Ice::LocalException& __ex) { __og.abort(__ex); } bool __ok = __og.invoke(); ::Ice::Int __ret; try { if(!__ok) { try { __og.throwUserException(); } catch(const ::Ice::UserException& __ex) { ::Ice::UnknownUserException __uue(__FILE__, __LINE__, __ex.ice_name()); throw __uue; } } ::IceInternal::BasicStream* __is = __og.is(); __is->startReadEncaps(); __is->read(__ret); __is->endReadEncaps(); return __ret; } catch(const ::Ice::LocalException& __ex) { throw ::IceInternal::LocalExceptionWrapper(__ex, false); } } ::Ice::Int IceDelegateD::MCal::Cal::add(::Ice::Int num1, ::Ice::Int num2, const ::Ice::Context* __context) { class _DirectI : public ::IceInternal::Direct { public: _DirectI(::Ice::Int& __result, ::Ice::Int num1, ::Ice::Int num2, const ::Ice::Current& __current) : ::IceInternal::Direct(__current), _result(__result), _m_num1(num1), _m_num2(num2) { } virtual ::Ice::DispatchStatus run(::Ice::Object* object) { ::MCal::Cal* servant = dynamic_cast< ::MCal::Cal*>(object); if(!servant) { throw ::Ice::OperationNotExistException(__FILE__, __LINE__, _current.id, _current.facet, _current.operation); } _result = servant->add(_m_num1, _m_num2, _current); return ::Ice::DispatchOK; } private: ::Ice::Int& _result; ::Ice::Int _m_num1; ::Ice::Int _m_num2; }; ::Ice::Current __current; __initCurrent(__current, __MCal__Cal__add_name, ::Ice::Normal, __context); ::Ice::Int __result; try { _DirectI __direct(__result, num1, num2, __current); try { __direct.servant()->__collocDispatch(__direct); } catch(...) { __direct.destroy(); throw; } __direct.destroy(); } catch(const ::Ice::SystemException&) { throw; } catch(const ::IceInternal::LocalExceptionWrapper&) { throw; } catch(const ::std::exception& __ex) { ::IceInternal::LocalExceptionWrapper::throwWrapper(__ex); } catch(...) { throw ::IceInternal::LocalExceptionWrapper(::Ice::UnknownException(__FILE__, __LINE__, "unknown c++ exception"), false); } return __result; } ::Ice::Int IceDelegateD::MCal::Cal::sub(::Ice::Int num1, ::Ice::Int num2, const ::Ice::Context* __context) { class _DirectI : public ::IceInternal::Direct { public: _DirectI(::Ice::Int& __result, ::Ice::Int num1, ::Ice::Int num2, const ::Ice::Current& __current) : ::IceInternal::Direct(__current), _result(__result), _m_num1(num1), _m_num2(num2) { } virtual ::Ice::DispatchStatus run(::Ice::Object* object) { ::MCal::Cal* servant = dynamic_cast< ::MCal::Cal*>(object); if(!servant) { throw ::Ice::OperationNotExistException(__FILE__, __LINE__, _current.id, _current.facet, _current.operation); } _result = servant->sub(_m_num1, _m_num2, _current); return ::Ice::DispatchOK; } private: ::Ice::Int& _result; ::Ice::Int _m_num1; ::Ice::Int _m_num2; }; ::Ice::Current __current; __initCurrent(__current, __MCal__Cal__sub_name, ::Ice::Normal, __context); ::Ice::Int __result; try { _DirectI __direct(__result, num1, num2, __current); try { __direct.servant()->__collocDispatch(__direct); } catch(...) { __direct.destroy(); throw; } __direct.destroy(); } catch(const ::Ice::SystemException&) { throw; } catch(const ::IceInternal::LocalExceptionWrapper&) { throw; } catch(const ::std::exception& __ex) { ::IceInternal::LocalExceptionWrapper::throwWrapper(__ex); } catch(...) { throw ::IceInternal::LocalExceptionWrapper(::Ice::UnknownException(__FILE__, __LINE__, "unknown c++ exception"), false); } return __result; } ::Ice::ObjectPtr MCal::Cal::ice_clone() const { throw ::Ice::CloneNotImplementedException(__FILE__, __LINE__); return 0; // to avoid a warning with some compilers } static const ::std::string __MCal__Cal_ids[2] = { "::Ice::Object", "::MCal::Cal" }; bool MCal::Cal::ice_isA(const ::std::string& _s, const ::Ice::Current&) const { return ::std::binary_search(__MCal__Cal_ids, __MCal__Cal_ids + 2, _s); } ::std::vector< ::std::string> MCal::Cal::ice_ids(const ::Ice::Current&) const { return ::std::vector< ::std::string>(&__MCal__Cal_ids[0], &__MCal__Cal_ids[2]); } const ::std::string& MCal::Cal::ice_id(const ::Ice::Current&) const { return __MCal__Cal_ids[1]; } const ::std::string& MCal::Cal::ice_staticId() { return __MCal__Cal_ids[1]; } ::Ice::DispatchStatus MCal::Cal::___add(::IceInternal::Incoming& __inS, const ::Ice::Current& __current) { __checkMode(::Ice::Normal, __current.mode); ::IceInternal::BasicStream* __is = __inS.is(); __is->startReadEncaps(); ::Ice::Int num1; ::Ice::Int num2; __is->read(num1); __is->read(num2); __is->endReadEncaps(); ::IceInternal::BasicStream* __os = __inS.os(); ::Ice::Int __ret = add(num1, num2, __current); __os->write(__ret); return ::Ice::DispatchOK; } ::Ice::DispatchStatus MCal::Cal::___sub(::IceInternal::Incoming& __inS, const ::Ice::Current& __current) { __checkMode(::Ice::Normal, __current.mode); ::IceInternal::BasicStream* __is = __inS.is(); __is->startReadEncaps(); ::Ice::Int num1; ::Ice::Int num2; __is->read(num1); __is->read(num2); __is->endReadEncaps(); ::IceInternal::BasicStream* __os = __inS.os(); ::Ice::Int __ret = sub(num1, num2, __current); __os->write(__ret); return ::Ice::DispatchOK; } static ::std::string __MCal__Cal_all[] = { "add", "ice_id", "ice_ids", "ice_isA", "ice_ping", "sub" }; ::Ice::DispatchStatus MCal::Cal::__dispatch(::IceInternal::Incoming& in, const ::Ice::Current& current) { ::std::pair< ::std::string*, ::std::string*> r = ::std::equal_range(__MCal__Cal_all, __MCal__Cal_all + 6, current.operation); if(r.first == r.second) { throw ::Ice::OperationNotExistException(__FILE__, __LINE__, current.id, current.facet, current.operation); } switch(r.first - __MCal__Cal_all) { case 0: { return ___add(in, current); } case 1: { return ___ice_id(in, current); } case 2: { return ___ice_ids(in, current); } case 3: { return ___ice_isA(in, current); } case 4: { return ___ice_ping(in, current); } case 5: { return ___sub(in, current); } } assert(false); throw ::Ice::OperationNotExistException(__FILE__, __LINE__, current.id, current.facet, current.operation); } void MCal::Cal::__write(::IceInternal::BasicStream* __os) const { __os->writeTypeId(ice_staticId()); __os->startWriteSlice(); __os->endWriteSlice(); #if defined(_MSC_VER) && (_MSC_VER < 1300) // VC++ 6 compiler bug Object::__write(__os); #else ::Ice::Object::__write(__os); #endif } void MCal::Cal::__read(::IceInternal::BasicStream* __is, bool __rid) { if(__rid) { ::std::string myId; __is->readTypeId(myId); } __is->startReadSlice(); __is->endReadSlice(); #if defined(_MSC_VER) && (_MSC_VER < 1300) // VC++ 6 compiler bug Object::__read(__is, true); #else ::Ice::Object::__read(__is, true); #endif } // COMPILERFIX: Stream API is not supported with VC++ 6 #if !defined(_MSC_VER) || (_MSC_VER >= 1300) void MCal::Cal::__write(const ::Ice::OutputStreamPtr&) const { Ice::MarshalException ex(__FILE__, __LINE__); ex.reason = "type MCal::Cal was not generated with stream support";throw ex; } void MCal::Cal::__read(const ::Ice::InputStreamPtr&, bool) { Ice::MarshalException ex(__FILE__, __LINE__); ex.reason = "type MCal::Cal was not generated with stream support"; throw ex; } #endif void MCal::__patch__CalPtr(void* __addr, ::Ice::ObjectPtr& v) { ::MCal::CalPtr* p = static_cast< ::MCal::CalPtr*>(__addr); assert(p); *p = ::MCal::CalPtr::dynamicCast(v); if(v && !*p) { IceInternal::Ex::throwUOE(::MCal::Cal::ice_staticId(), v->ice_id()); } }编译成功,如果有很多错误,可能是没有加入库文件iced.lib和iceutild.lib 如果成功,服务器端建立成功。 h、客户端程序main.cpp.
重新建立工程,Client,建立客户端CalculatorClient.cpp 将生成的Calculator.h 和Calculator.cpp加入Client工程中,然后建立CalculatorClient.cpp #include #include #include "Calculator.h" #include using namespace std; using namespace MCal; //#define RC_List "/home/tester/Key_Checking/ATT_RC/RMT_B109P_EU_Set.txt" //#define RC_Data "/home/tester/Key_Checking/ATT_RC/RMT_B109P_EU_Set.dat" class MyApp : virtual public Ice::Application { public: virtual int run(int,char*[]); }; int MyApp::run(int,char*[]) { Ice::ObjectPrx base = communicator()->stringToProxy("Cal:default -p 10000"); CalPrx calculator = CalPrx::checkedCast(base); if(!calculator) throw "Invalid proxy!"; cout |
今日新闻 |
推荐新闻 |
CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3 |