203 lines
7.5 KiB
Markdown
203 lines
7.5 KiB
Markdown
# 快速入门
|
||
|
||
Messenger SDK一般是一个如下格式的压缩包
|
||
|
||
/libs
|
||
/arm64-v8a
|
||
/x86-64
|
||
/Windows
|
||
/Linux
|
||
...
|
||
/include
|
||
...
|
||
|
||
|
||
其中,libs下放置的是一些静态连接库,包含Android、Linux以及Windows。你需要将这些静态链接库链接到你的项目中,
|
||
并且在你项目的头文件搜索路径中添加 *include* 文件夹。
|
||
|
||
然后,你将可以使用Messenger的API了。
|
||
|
||
## 使用方法
|
||
|
||
Messenger SDK的主要class是 ling::Messenger ,它提供了服务器全部API的抽象。
|
||
它由ling::Client派生而来,ling::Client实现了服务器的通讯协议,包括STL握手过程。
|
||
|
||
> ling::Messenger以及ling::Client,都具有ling前缀,在C++中,这叫命名空间(*namespace*)。SDK内全部的class都在ling命名空间下。
|
||
|
||
|
||
ling::Messenger将是你接触最多的class。
|
||
它有两个构造函数:
|
||
|
||
~~~cpp
|
||
namespace ling{
|
||
class Messenger : public Client {
|
||
public:
|
||
/**
|
||
* 创建一个Messenger实例,创建实例时,即连接到Server。
|
||
*
|
||
* @param host 服务器host
|
||
* @param port 端口号
|
||
* @param projectId 项目id
|
||
* @throw ConnectException 在连接失败时
|
||
*/
|
||
Messenger(std::string host, int port, uint64_t projectId);
|
||
|
||
/**
|
||
* 此重载相比上面的定义,额外需要一个deviceUid参数。
|
||
* 这是设备识别码,上面的实现将会自动读取设备识别码,但是可能不会唯一
|
||
* 扛攻击的能力较弱,如果设备拥有root权限则很可能被修改
|
||
* 所以,如果你有更加适合你需求的方案识别设备,将它作为第四个参数传递进来!
|
||
*
|
||
* @param host 服务器host
|
||
* @param port 端口号
|
||
* @param projectId 项目id
|
||
* @param deviceUid 设备识别码
|
||
* @throw ConnectException 在连接失败时
|
||
*/
|
||
Messenger(std::string host, int port, uint64_t projectId, std::string deviceUid);
|
||
};
|
||
}
|
||
~~~
|
||
|
||
> 以后您会经常看到像这样的声明,提前熟悉它!
|
||
|
||
在Messenger创建完毕后,你可以通过它来和服务器交互。
|
||
|
||
## 范例
|
||
|
||
~~~ cpp
|
||
//别忘了导入定义!
|
||
#include <Messenger.h>
|
||
#include <iostream>
|
||
#include <memory>
|
||
|
||
//因为Messenger每次创建都会连接到服务器
|
||
//所以你不能每次和服务器交互都创建一个实例,而是必须复用!
|
||
//此处使用一个智能指针来管理Messenger的实例
|
||
//手动将没有初始值的指针赋值为nullptr,这是一个良好的编程习惯。
|
||
static std::shared_ptr<ling::Messenger> api = nullptr;
|
||
|
||
int main(void){
|
||
try{
|
||
//这里使用make_shared自动构造智能指针
|
||
::api = std::make_shared<ling::Messenger>("127.0.0.1",2019,1);
|
||
//在此时已经成功连接到服务器,可以进行交互。
|
||
//注意,交互之前必须进行设备注册,这个后面的章节在说。
|
||
} catch (const ling::ConnectException & e){
|
||
//还记得上面的声明中提到连接失败时会抛出ConnectException吗?
|
||
std::cerr << "连接服务器失败" << std::endl;
|
||
}
|
||
}
|
||
|
||
~~~
|
||
|
||
得到**ling::Messenger**的实例之后,你可以通过它来调用Messenger的API。
|
||
例如,像这样来向服务器内注册当前设备。
|
||
|
||
~~~ cpp
|
||
|
||
#include <filesystem>
|
||
|
||
std::shared_ptr<ling::Messenger> api = ...
|
||
|
||
std::filesystem::path filePath = "/path/to/your/flag";
|
||
try{
|
||
if (!std::filesystem::exists(filePath)) {
|
||
::api->createDevice([](std::shared_ptr<ServerDataSuper> data)->void{
|
||
//此处的代码将会在服务器响应createDevice时被执行
|
||
if(data->isSuccess()){
|
||
//注册设备成功,则在磁盘上留下一个标记,下次启动时不再注册
|
||
std::ofstream outputFile(filePath);
|
||
outputFile.close();
|
||
//然后,可以调用其他api了。
|
||
} else {
|
||
//服务器拒绝注册请求,可能是因为一些异常问题导致的,一般不会出现。
|
||
//为了程序健壮性,此处需要记录日志,这里仅仅打印一下。
|
||
std::cout << data->getMessage() << std::endl;
|
||
}
|
||
});
|
||
}
|
||
} catch (const ling::SendException & e){
|
||
//所有需要和服务器交换数据的api都会在网络断开时抛出此异常
|
||
//具体信息请查看异常处理一节。
|
||
//有无需捕获异常的优雅方法,将在后续章节中介绍。
|
||
}
|
||
|
||
~~~
|
||
|
||
### 发生了什么?
|
||
|
||
上面的代码可以简化成如下这样
|
||
|
||
~~~ cpp
|
||
|
||
::api->createDevice([](std::shared_ptr<ServerDataSuper> data)->void{
|
||
|
||
});
|
||
|
||
~~~
|
||
|
||
简单来说,就是调用了全局命名空间中api对象的createDevice成员方法,并将一个lambda表达式作为参数传递给了它。
|
||
|
||
lambda,即匿名函数(*Anonymous Function*)。
|
||
|
||
查看createDevice方法的定义
|
||
|
||
~~~ cpp
|
||
void createDevice(const std::function<void(std::shared_ptr<ServerDataSuper>)> &call);
|
||
~~~
|
||
|
||
可以看到它需要一个**std::function**类型的参数。**std::function**是C++可调用对象的包装,它可以是一个具名函数
|
||
,一个lambda表达式,也可以是一个class的成员方法,一个仿函数等等。一切可调用对象都可以使用**std::function**来包装。
|
||
|
||
参数中紧跟在**std::function**后面的 **<void(std::shared_ptr<ServerDataSuper>>)>** 是这个**std::function**的模板参数,
|
||
表示此**std::function**包装的可调用对象没有返回值,并且只需要一个 **std::shared_ptr<ServerDataSuper>** 类型的参数。
|
||
|
||
前面已经介绍过**std::shared_ptr**了,它同样是一个模板类型,尖括号中的**ServerDataSuper**是此智能指针管理对象的指针。
|
||
|
||
**ServerDataSuper**是Messenger服务器响应数据的封装类型,是所有响应数据的基类。换句话说
|
||
,所有包装服务器响应数据的类型,都**直接或间接**的派生自**ServerDataSuper**。
|
||
|
||
例如,如下是服务器对登陆账号请求的响应信息包装类型的声明
|
||
|
||
~~~ cpp
|
||
|
||
namespace ling {
|
||
class LoginUserData : public ServerDataSuper {
|
||
};
|
||
}
|
||
~~~
|
||
|
||
可以看到它直接从ServerDataSuper派生,这意味着你可以在任何服务器响应类型中调用ServerDataSuper中的方法,它们是公共的。
|
||
|
||
对ServerDataSuper的介绍将在后续章节继续。
|
||
|
||
回到createDevice。
|
||
|
||
将上述简化代码继续简化,可以变成下面的样子。
|
||
|
||
~~~ cpp
|
||
|
||
/// 这段代码和上面简化后的代码是等价的!
|
||
|
||
void createDeviceResponse(std::shared_ptr<ServerDataSuper> data){
|
||
//等到服务器响应后,Messenger才会调用这个函数。
|
||
//注意,Messenger将在单独的线程中调用此函数
|
||
//Messenger将采取一定的策略管理线程,无论如何,你都不应该在此类回调函数中执行需要消耗大量时间的操作。
|
||
}
|
||
|
||
::api->createDevice(createDeviceResponse);
|
||
//调用后将会立即返回,继续向下执行
|
||
|
||
~~~
|
||
|
||
createDeviceResponse的定义,**等价与**上面代码中的lambda表达式。
|
||
|
||
Messenger SDK的API是同服务器异步通信的,也就是说createDevice会立即返回,并继续向下执行。
|
||
等服务器响应createDevice时,Messenger SDK将会调用参数中传递进去的可调用对象
|
||
(在这里是createDeviceResponse)并构造ServerDataSuper对象作为参数。
|
||
|
||
> 这都是C++的基础语法。请百度 回调函数(*callback*)
|
||
|
||
|