5.5 客户端开发
-
5.5.5 C++客户端
C++客户端支持静态模式,Variant模式。脚本模式通过与Lua库集成实现Lua支持,这个单独介绍。
C++客户端使用C++11标准实现,最大限度保持和java版本,C#版本一致。C++库在各常用平台上提供支持(g++,ndk,clang),这里以VS2013控制台项目举例。
-
静态模式
1. 首先应该创建C++解决方案和应用项目,确定应用项目的源码目录sdir,执行如下生成命令:
2.
java -jar <path to limax.jar> xmlgen –c++ –noServiceXML –outputPath sdir example.client.xml
3. 源码目录里面将看见两个新建目录xmlgeninc与xmlgensrc。xmlgeninc无需提交到版本控制系统。
4. 按照生成目录结构,在项目内建立同样的目录结构,然后使用添加现有项操作手工添加所有生成的.cpp文件,以后的新生成的.cpp文件也必须手工添加。为了编辑器查看方便建议.h也添加进来。
5. 将C++版本的limax项目添加到解决方案中。
6. 为应用项目添加引用,引用到limax项目。
7. 编辑项目属性,将limax项目的include目录作为附加包含目录。
主函数代码:
#include "stdafx.h" #include <iostream> #include "limax.h" #include "xmlgeninc/xmlgen.h" using namespace limax; class MyApp : public EndpointListener { const int providerId = 100; public: MyApp() { Endpoint::openEngine(); auto config = Endpoint::createEndpointConfigBuilder( "127.0.0.1", 10000, LoginConfig::plainLogin("testabc", "123456", "test")) ->staticViewCreatorManagers({ example::getShareViewCreatorManager(providerId) }) ->endpointState({ example::getExampleClientStateClient(providerId) }) ->build(); Endpoint::start(config, this); } ~MyApp(){ std::mutex mutex; std::condition_variable_any cond; std::lock_guard<std::mutex> l(mutex); Endpoint::closeEngine([&](){ std::lock_guard<std::mutex> l(mutex); cond.notify_one(); }); cond.wait(mutex); } void run() { Sleep(2000); } void onManagerInitialized(EndpointManager*,EndpointConfig*) { std::cout << "onManagerInitialized" << std::endl; } void onManagerUninitialized(EndpointManager*) { std::cout << "onManagerUninitialized" << std::endl; } void onTransportAdded(Transport*) { std::cout << "onTransportAdded" << std::endl; View* mysessionview = example::ExampleClient::share::MySessionView::getInstance(); mysessionview->registerListener([](const ViewChangedEvent &e){std::cout << e.toString() << std::endl; }); } void onTransportRemoved(Transport*){ std::cout << "onTransportRemoved" << std::endl; } void onAbort(Transport*) { std::cout << "onAbort" << std::endl; } void onSocketConnected() { std::cout << "onSocketConnected" << std::endl; } void onKeyExchangeDone() { std::cout << "onKeyExchangeDone" << std::endl; } void onKeepAlived(int ping) { std::cout << "onKeepAlived " << ping << std::endl; } void onErrorOccured(int errorsource, int errorvalue, const std::string& info) { std::cout << "onErrorOccured " << errorsource << " " << errorvalue << " " << info << std::endl; } void destroy() {} }; int _tmain(int argc, _TCHAR* argv[]) { MyApp().run(); return 0; }
与java版本C#版本比起来除语言特性外无太大差异。onErrorOccured稍有差别,当source为SOURCE_ENDPOINT时,code对应那些SYSTEM类型错误,info报告了具体错误内容,source/code的定义参见endpoint.h头文件。另外MyApp类中多了一个方法destroy。具体原因下面介绍。
share.MyTemporaryView.cpp:
#include "share.MyTemporaryView.h" #include "../xmlgeninc/views/share.MySessionView.h" #include <iostream> using namespace limax; namespace example { namespace ExampleClient { namespace share { void MyTemporaryView::onOpen(const std::vector<int64_t>& sessionids) { std::cout << toString() << " onOpen"; for (auto &l : sessionids) std::cout << " " << l; std::cout << std::endl; registerListener([](const ViewChangedEvent &e){std::cout << e.toString() << std::endl; }); MySessionView::getInstance()->control(99999); } void MyTemporaryView::onAttach(int64_t sessionid) {} void MyTemporaryView::onDetach(int64_t sessionid, int reason) { if (reason >= 0) { //Application Reason } else { //Connection abort Reason } } void MyTemporaryView::onClose() { std::cout << toString() << " onClose " << std::endl; } } } }
这个看起来差异也不大,注意一下,因为使用了MySessionView,所以需要的头文件share.MySessionView.h必须自己手工include。另外,同一目录下生成的头文件share.MyTemporaryView.h中,可以添加需要的成员变量,成员方法。
运行结果:
onManagerInitialized
onSocketConnected
onKeyExchangeDone
onTransportAdded
[class = example.ExampleClient.share.MyTemporaryView ProviderId = 100 classindex = 2 instanceindex = 78] onOpen 61440
[class = example.ExampleClient.share.MyTemporaryView ProviderId = 100 classindex = 2 instanceindex = 78] 61440 _var0 00A3BCBC NEW
onKeepAlived 15
[class = example.ExampleClient.share.MySessionView ProviderId = 100 classindex = 1] 61440 var0 0088585C NEW
[class = example.ExampleClient.share.MySessionView ProviderId = 100 classindex = 1] 61440 var0 0088585C REPLACE
[class = example.ExampleClient.share.MyTemporaryView ProviderId = 100 classindex = 2 instanceindex = 78] 61440 _var0 00A3BCBC REPLACE
onTransportRemoved
[class = example.ExampleClient.share.MyTemporaryView ProviderId = 100 classindex = 2 instanceindex = 78] onClose
onManagerUninitialized
这个结果看起来与前面各版本均一致,只不过View的值被显示为了一个地址了,这是因为消息通告ViewChangedEvent的定义中,value的类型只能是void*。
-
Variant模式
修改构造函数,创建Variant模式下的配置
auto config = Endpoint::createEndpointConfigBuilder( "127.0.0.1", 10000, LoginConfig::plainLogin("testabc", "123456", "test")) ->variantProviderIds({ providerId }) ->build();
修改onTransportAdded方法:
class MyTemporaryViewHandler : public TemporaryViewHandler { VariantView* mySessionView; public: MyTemporaryViewHandler(VariantView* v) : mySessionView(v) {} virtual void onOpen(VariantView* view, const std::vector<int64_t>& sessionids){ std::cout << view->toString() << " onOpen"; for (auto &l : sessionids) std::cout << " " << l; std::cout << std::endl; view->registerListener([](const VariantViewChangedEvent &e){std::cout << e.toString() << std::endl; }); Variant param = Variant::createStruct(); param.setValue("var0", 99999); mySessionView->sendControl("control", param); } virtual void onClose(VariantView* view){ std::cout << view->toString() << " onClose " << std::endl; } virtual void onAttach(VariantView* view, int64_t sessionid) {} virtual void onDetach(VariantView* view, int64_t sessionid, int reason) {} virtual void destroy() { delete this; } }; void onTransportAdded(Transport*transport) { std::cout << "onTransportAdded" << std::endl; VariantManager* manager = VariantManager::getInstance(transport->getManager(), providerId); VariantView* mySessionView = manager->getSessionOrGlobalView("share.MySessionView"); mySessionView->registerListener([](const VariantViewChangedEvent &e){std::cout << e.toString() << std::endl; }); manager->setTemporaryViewHandler("share.MyTemporaryView", new MyTemporaryViewHandler(mySessionView)); }
这段代码看起来和C#版本非常类似,除了语言特性差异。另外MyTemporaryViewHandler类中多了一个方法destroy。具体原因下面介绍。
运行结果:
onManagerInitialized
onSocketConnected
onKeyExchangeDone
onTransportAdded
[view = share.MyTemporaryView ProviderId = 100 classindex = 2 instanceindex = 79] onOpen 61440
[view = share.MyTemporaryView ProviderId = 100 classindex = 2 instanceindex = 79] 61440 _var0 Hello 61440 NEW
onKeepAlived 16
[view = share.MySessionView ProviderId = 100 classindex = 1] 61440 var0 Hello 61440 NEW
[view = share.MySessionView ProviderId = 100 classindex = 1] 61440 var0 99999 REPLACE
[view = share.MyTemporaryView ProviderId = 100 classindex = 2 instanceindex = 79] 61440 _var0 99999 REPLACE
onTransportRemoved
[view = share.MyTemporaryView ProviderId = 100 classindex = 2 instanceindex = 79] onClose
onManagerUninitialized
这里的结果就能看到实际的value了,因为Variant知道一些类型信息。
-
各种注意事项
1. 主函数代码必须包含头文件limax.h,使用静态模式需要额外包含生成的头文件xmlgeninc/xmlgen.h。修改生成代码时, 如果需要使用别的生成代码, 需要哪些包含哪些。参见前面对share.MyTemporaryView.cpp的修改。
2. 为了简单起见,limax库代码只使用limax名字空间,不再作进一步的划分,所以using namespace limax;
3. 创建配置时,staticViewCreatorManagers,endpointState,variantProviderIds的参数使用C++11特性的初始化列表作为参数,可以支持多个。同C#一样,同样提供runOnUiThread与uiThreadSchedule两个辅助方法方便使用,->executor([](Runnable r){ runOnUiThread(r); })。
4. 由库提供出来的各种指针EndpointManager*,Trasnport*,以及各种形式的View*,用户可以直接使用不允许delete。EndpointManager 的寿命到onManagerUninitialized为止;Transport以及全局View,会话View的寿命到onTransportRemoved为止;临时View的寿命到临时View自身onClose为止,如果一定需要持有这些指针,必须严格注意寿命问题,尤其是通过lambda表达式的闭包引用更要小心。
5. 需要应用提供对象的情况下,对象应该由用户自己创建出来提供指针交由库使用,并且提供自己的destroy方法,前面的MyApp::destroy 和 MyTemporaryViewHandler::destroy就是典型情况。这是由于库内部使用的new/delete与应用本身的new/delete可能不配对。
6. 如果使用Objective-C++,生成静态模式代码时使用-oc参数代替-c++参数,确保生成需要的.mm文件。
-