-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
5 changed files
with
430 additions
and
156 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,213 @@ | ||
# Remoting接口文档 | ||
|
||
Remoting通信支持RPC和HTTP两种架构,[参阅这里](https://github.com/NewLifeX/NewLife.Remoting),使用完全相同的接口出入参格式,用法一致,不再分开表述。 | ||
|
||
参考: | ||
|
||
[简易远程消息协议](https://newlifex.com/core/srmp) | ||
|
||
[**ApiHttpClient客户端**](https://newlifex.com/core/api_http) | ||
|
||
|
||
|
||
## 基础知识 | ||
|
||
针对常见基础功能,封装了客户端与服务端实现,例如登录、注销和心跳等。 | ||
|
||
|
||
|
||
### 主要部件 | ||
|
||
**ClientBase**是客户端基类,推荐应用客户端类继承该基类,仅需简单配置即可使用; | ||
|
||
**BaseDeviceController**是服务端控制器基类,推荐应用服务端控制器类继承该基类,也可以直接拷贝该基类代码来使用; | ||
|
||
|
||
|
||
### 功能设置 | ||
|
||
默认实现的功能比较多,都集中在ClientBase基类内部,需要通过Features来设置启用哪些功能。一般写在应用客户端构造函数内。例如ZeroIoT中的HttpDevice构造函数: | ||
|
||
```c# | ||
public HttpDevice(ClientSetting setting) : base(setting) | ||
{ | ||
// 设置动作,开启下行通知 | ||
Features = Features.Login | Features.Logout | Features.Ping | Features.Notify | Features.Upgrade; | ||
SetActions("Device/"); | ||
Actions[Features.CommandReply] = "Thing/ServiceReply"; | ||
} | ||
``` | ||
|
||
不仅通过Features设置了需要开启的功能,还通过SetActions指定了服务端接口的路径前缀(如Device/Login)。其中CommandReply接口比较特殊,独立设置其路径。 | ||
|
||
|
||
|
||
又如星尘节点客户端StarClient,星尘代理StarAgent和码神工具CrazyCoder都通过它连接星尘服务端节点管理平台,提供登录、注销、心跳、更新和指令下发等功能: | ||
|
||
```c# | ||
public StarClient() | ||
{ | ||
Features = Features.Login | Features.Logout | Features.Ping | Features.Upgrade | Features.Notify | Features.CommandReply | Features.PostEvent; | ||
SetActions("Node/"); | ||
|
||
Log = XTrace.Log; | ||
} | ||
``` | ||
|
||
|
||
|
||
再来看看星尘应用客户端AppClient,所有通过nuget引入Stardust的应用,都通过AppClient连接星尘服务端应用管理平台,提供登录、心跳、指令下发、监控数据上报、服务注册与消费等功能: | ||
|
||
```c# | ||
public AppClient() | ||
{ | ||
Features = Features.Login | Features.Ping | Features.Notify | Features.CommandReply; | ||
SetActions("App/"); | ||
|
||
Log = XTrace.Log; | ||
} | ||
``` | ||
|
||
|
||
|
||
### 客户端属性 | ||
|
||
ClientBase客户端主要属性。 | ||
|
||
| 名称 | 类型 | 说明 | | ||
| ---------------- | ----------------------------- | ------------------------------------------------------------ | | ||
| Server | String | 服务端地址。支持http/tcp/udp,支持客户端负载均衡,多地址逗号分隔 | | ||
| Code | String | 编码。设备编码DeviceCode,或应用标识AppId | | ||
| Secret | String | 密钥。设备密钥DeviceSecret,或应用密钥AppSecret | | ||
| PasswordProvider | IPasswordProvider | 密码提供者。用于保护密码传输,默认提供者为空,密码将明文传输。 | | ||
| OnLogined | <LoginEventArgs> | 登录完成后触发 | | ||
| Received | <CommandEventArgs> | 收到下行命令时触发 | | ||
| Status | LoginStatus | 登录状态 | | ||
| Delay | Int32 | 请求到服务端并返回的延迟时间。单位ms | | ||
| Span | TimeSpan | 时间差。服务器时间减去客户端时间 | | ||
| MaxFails | Int32 | 最大失败数。心跳上报失败时进入失败队列,并稍候重试。重试超过该数时,新的数据将被抛弃,默认1440次,约24小时 | | ||
| Commands | IDictionary<String, Delegate> | 命令集合。注册到客户端的命令与委托 | | ||
| Features | Features | 客户端功能特性。默认登录注销心跳,可添加更新等 | | ||
| Actions | IDictionary<Features, String> | 各功能的动作集合。记录每一种功能所对应的动作接口路径。 | | ||
| JsonHost | IJsonHost | Json主机。提供序列化能力 | | ||
| Setting | IClientSetting | 客户端设置 | | ||
|
||
|
||
|
||
### 异步登录 | ||
|
||
客户端刚启动时,网络可能尚未就绪。此时直接调用Login就会抛出异常。ClientBase提供了Open方法,内部通过定时器不断检测网络,并在网络恢复时调用Login,直到成功为止。 | ||
|
||
有可能客户端网络没有问题,而服务端网络有问题,或者中途网络无法连通,或者甚至服务端还没有启动。Open内部的定时登录机制同样有效,直到登录成功为止。 | ||
|
||
|
||
|
||
## 接口详解 | ||
|
||
各个接口按需选用,都不是必须实现。如果没有特殊说明,各接口都是客户端向服务端发起请求,并等待服务端响应。 | ||
|
||
各个应用接口前缀可能不同,下文使用变量表示,例如`/{Prefix}/Login`在ZeroIoT中就是`/Device/Login`。该前缀由前文功能设置中的SetActions指定,并保存在Actions属性中。少数场景可能会修改接口名,例如`/Device/Register`,具体以Actions中为准。 | ||
|
||
各接口请求中,非必要字段可以选填,应尽量提供相关信息,以支持服务端各项功能。 | ||
|
||
各接口默认使用POST请求方式,参数经Json序列化放在Body中。如果使用GET请求,则参数拼接在Url中。 | ||
|
||
登录获取令牌后,所有接口请求均需要在请求头中携带令牌,HTTP请求头:`Authentication: Bearer {token}` 。 | ||
|
||
|
||
|
||
正常响应格式如下(code==0),下文响应表格仅列出data部分: | ||
|
||
```json | ||
{ | ||
"code": 0, | ||
"data": { /* 各接口响应表格字段 */ } | ||
} | ||
``` | ||
|
||
异常响应格式如下(code!=0),data部分为异常字符串: | ||
|
||
```json | ||
{ | ||
"code": 401, | ||
"data": "未登录" | ||
} | ||
``` | ||
|
||
|
||
|
||
### 登录Login | ||
|
||
**地址**:`/{Prefix}/Login` | ||
|
||
**功能**:向服务端提交编码和密钥,服务端验证通过后颁发令牌给客户端,后续所有接口调用带上令牌作为身份标识。 | ||
|
||
**说明**:登录请求一般还提供ClientId、版本信息、IP地址与MAC地址、唯一机器码、本地时间等信息,服务端接口能验证客户端身份,并颁发令牌。服务端找不到关联身份时,也可以根据相关信息注册一个身份,并通过登录响应返回编码和原始明文密钥。 | ||
|
||
|
||
|
||
**请求**(ILoginRequest): | ||
|
||
| 名字 | 类型 | 必要 | 说明 | | ||
| -------- | ------ | :--: | --------------------------------------- | | ||
| Code | String | √ | 编码 | | ||
| Secret | String | √ | 密钥 | | ||
| ClientId | String | √ | 实例。应用可能多实例部署,ip@proccessid | | ||
| Version | String | | 版本 | | ||
| Compile | Int64 | | 编译时间。UTC毫秒 | | ||
| IP | String | | 本地IP地址 | | ||
| Macs | String | | MAC地址 | | ||
| UUID | String | | 唯一标识 | | ||
| Time | Int64 | | 本地时间。UTC毫秒 | | ||
|
||
|
||
|
||
**响应**: | ||
|
||
| 名字 | 类型 | 必要 | 说明 | | ||
| ---------- | ------ | :--: | ------------------------------------------------- | | ||
| Code | String | | 编码。平台下发新编码,仅用于自动注册 | | ||
| Secret | String | | 密钥。平台下发新密钥,仅用于自动注册 | | ||
| Token | String | √ | 令牌。后续所有接口调用均需要在请求头携带该令牌。 | | ||
| Expire | Int32 | | 令牌过期时间。单位秒。默认7200秒 | | ||
| Time | Int64 | | 本地时间。客户端用于计算延迟,Unix毫秒(UTC) | | ||
| ServerTime | Int64 | | 服务器时间。客户端用于计算时间差,Unix毫秒(UTC) | | ||
|
||
|
||
|
||
### 注销Logout | ||
|
||
**功能**:向服务端申请,注销当前令牌。 | ||
|
||
|
||
|
||
### 心跳Ping | ||
|
||
**功能**:定时向服务端发送心跳,主要目的是链路保活,同时上报客户端的一些性能数据。 | ||
|
||
|
||
|
||
### 升级更新Upgrade | ||
|
||
**功能**:定时请求服务端更新接口,获取满足当前客户端条件的升级更新信息,并根据信息执行自动更新流程。 | ||
|
||
|
||
|
||
### 下行通知Notify | ||
|
||
**功能**:服务端通过下行链路(HTTP是WebSocket,RPC是TCP/UDP)向客户端发送命令,指示客户端即时执行特定流程。 | ||
|
||
**方向**:服务端->客户端 | ||
|
||
|
||
|
||
### 命令响应CommandReply | ||
|
||
**功能**:客户端执行完成服务端下发的命令后,通过CommandReply接口向上汇报执行状态与结果。 | ||
|
||
|
||
|
||
### 上报事件PostEvent | ||
|
||
功能:客户端批量上报带有时间戳的事件日志。 | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,172 @@ | ||
|
||
# SRMP协议 | ||
在分布式系统中,RPC通信尤为重要。SRMP是新生命团队专门为了RPC框架而设计的通信协议,既支持内网高速通信,也能覆盖物联网嵌入式设备。 | ||
|
||
经过十多年实战经验积累以及多方共同讨论,新生命团队([https://newlifex.com](https://newlifex.com))在2016年制订了一种简单而又具有较好扩展性的RPC(Remote Procedure Call)协议。 | ||
|
||
全称:**简易远程消息交换协议**,简称: **SRMP(Simple Remote Messaging Protocol)** | ||
|
||
SRMP主要定位于以下场景: | ||
- 内网高速通信,大吞吐量(>10万tps)、低延迟(<1ms) | ||
|
||
- 外网远程通信,稳定可靠,海量连接(>10万) | ||
|
||
- 物联网硬件设备,容易简单实现协议 | ||
|
||
- 支持TCP/UDP/串口/蓝牙BLE等多种通信方式 | ||
|
||
|
||
|
||
# 协议基础 | ||
## 消息结构 | ||
**协议: 1 Flag + 1 Sequence + 2 Length + N Payload** | ||
- **1个字节标识位,标识请求、响应、错误、单向广播等;** | ||
|
||
- **1个字节序列号,用于请求响应包配对;** | ||
|
||
- **2个字节数据长度N,小端字节序,指示后续负载数据长度(不包含头部4个字节),解决粘包问题;** | ||
|
||
- **N个字节负载数据,数据内容完全由业务决定,最大长度65534=64k-2。** | ||
|
||
|
||
|
||
## 长度扩展 | ||
数据中心内网通信中,负载数据大于等于64k时,数据长度字段填65535(0xFFFF),启用后续4字节扩展长度,最大长度4G(0xFFFFFFFF),此时头部总长度是8字节。 | ||
嵌入式物联网硬件设备建议直接忽略扩展长度,仅需支持4字节头部,限制负载数据小于64k。 | ||
|
||
采用固定2字节表示长度,方便任意语言接入,特别是嵌入式实现,在这一点上完胜变长的七位压缩编码整数。内网高速通信可实现8字节头部扩容,而物联网嵌入式设备则可以直接不考虑扩容。 | ||
1字节序列号,主要用于UDP通信、串口通信、无线通信等做请求与响应的匹配,这是多路复用的根基所在。 | ||
|
||
|
||
|
||
## 通信方式 | ||
本协议默认采用请求应答模式,此外还支持异常响应及单向广播,由协议标识位最高两位来定义。 | ||
![image.png](流程图.png) | ||
|
||
1. 客户端向服务端发起RPC请求(Flag=0x01),携带自增序列号Seq,异步等待响应。 | ||
|
||
2. 服务端收到RPC请求后,处理业务并把结果打包成RPC响应(Flag=0x81),使用相同的Seq发回去给客户端。 | ||
|
||
3. 客户端收到响应后,匹配Seq,返回给调用方。 | ||
|
||
4. 服务端处理RPC请求出现异常时,打包RPC异常(Flag=0xC1),使用相同的Seq返回给客户端。 | ||
|
||
5. 客户端收到异常响应后,匹配Seq,向调用方抛出异常。 | ||
|
||
6. 客户端和服务端随时可以向对方发送单向广播(Flag=0x41),接收方无需回复。 | ||
|
||
7. 客户端可以发起RPC请求以及单向广播,服务端仅能回复响应以及发送单向广播而不能主动发起RPC请求。 | ||
|
||
|
||
|
||
# RPC报文 | ||
SRMP主要分为请求、响应、异常和单向广播几种报文。报文由协议头和负载数据体两大部分组成。 | ||
## 请求报文 | ||
RPC请求报文,数据体分为接口名称Action和请求参数Data两部分。1个字节表示名称长度,因此名称最大长度为255字符。4个字节表示请求参数的数据长度,最大4G。 | ||
|
||
如若有扩展需要,可在后面附加多个具有4字节长度的变长数据,整体格式不变。例如,某些场景需要Token令牌或者TraceId追踪,实际上可以作为请求参数一部分来整体封包。 | ||
|
||
请求参数默认采用Json序列化封包,高速接口支持直接以二进制Packet作为参数,绕开序列化的成本开支。强烈建议10000tps以上的接口采用高速Packet传参,此时接口入参只能有一个Packet参数。用户自己对参数进行二进制序列化。 | ||
|
||
在高并发大吞吐系统中,序列化成本占据整体通信耗时的70%以上,远远超过网络开支。 | ||
|
||
| 字节 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | | ||
| --- | --- | --- | --- | --- | --- | --- | --- | --- | | ||
| 1 Flag | 请求0 | 单向0 | 保留 | | | | | | | ||
| 1 Seq | 序列号(0~255) | | | | | | | | | ||
| 2 Length | 数据长度(0~65534) | | | | | | | | | ||
| Body | 名称长度(1字节) | | 接口名称(Action) | | | | | | | ||
| | 数据长度(4字节) | | 请求参数(Data) | | | | | | | ||
|
||
|
||
## 响应报文 | ||
RPC响应报文,数据体分为接口名称Action和结果数据Data两部分。1个字节表示名称长度,因此名称最大长度为255字符。4个字节表示结果数据的数据长度,最大4G。 | ||
|
||
如若有扩展需要,可在后面附加多个具有4字节长度的变长数据,整体格式不变。 | ||
|
||
响应数据默认采用Json序列化封包,高速接口支持直接以二进制Packet作为数据,绕开序列化的成本开支。强烈建议10000tps以上的接口采用Packet作为响应数据,此时接口返回类型必须是Packet。用户自己对数据进行二进制反序列化。 | ||
|
||
| 字节 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | | ||
| --- | --- | --- | --- | --- | --- | --- | --- | --- | | ||
| 1 Flag | 响应1 | 错误0 | 保留 | | | | | | | ||
| 1 Seq | 序列号(0~255) | | | | | | | | | ||
| 2 Length | 数据长度(0~65534) | | | | | | | | | ||
| Body | 名称长度(1字节) | | 接口名称(Action) | | | | | | | ||
| | 数据长度(4字节) | | 结果数据(Data) | | | | | | | ||
|
||
|
||
## 异常报文 | ||
RPC异常响应报文,数据体分为接口名Action、错误码Code和结果数据Data三部分。响应代码固定4字节整数,其它同响应报文。 | ||
|
||
| 字节 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | | ||
| --- | --- | --- | --- | --- | --- | --- | --- | --- | | ||
| 1 Flag | 响应1 | 错误1 | 保留 | | | | | | | ||
| 1 Seq | 序列号(0~255) | | | | | | | | | ||
| 2 Length | 数据长度(0~65534) | | | | | | | | | ||
| Body | 名称长度(1字节) | | 接口名称(Action) | | | | | | | ||
| | 响应代码(Code)(4字节) | | | | | | | | | ||
| | 数据长度(4字节) | | 结果数据(Data) | | | | | | | ||
|
||
|
||
## 单向广播 | ||
RPC单向广播报文,标记位高位填01,其它同请求报文。 | ||
|
||
| 字节 | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | | ||
| --- | --- | --- | --- | --- | --- | --- | --- | --- | | ||
| 1 Flag | 请求0 | 单向1 | 保留 | | | | | | | ||
| 1 Seq | 序列号(0~255) | | | | | | | | | ||
| 2 Length | 数据长度(0~65534) | | | | | | | | | ||
| Body | 名称长度(1字节) | | 接口名称(Action) | | | | | | | ||
| | 数据长度(4字节) | | 请求参数(Data) | | | | | | | ||
|
||
|
||
|
||
# 参数封包 | ||
|
||
客户端请求和服务端响应,其中都有Data数据主体部分(请求参数和结果数据),根据使用场景不同,主要分为Json封包、二进制封包和基础类型封包等方式。SRMP协议强大之处在于,可以混合使用多种封包方式,简单接口使用Json封包和基础类型封包,文件和多媒体传输接口使用二进制封包。 | ||
请求参数对应接口入参,接口返回对应Invoke返回,一般要求两边类型对应。但也不尽然,只要一边能够解析另一边的数据就行。 | ||
|
||
|
||
|
||
## 封包原理 | ||
1. 客户端发起Invoke调用时,接口名称Action和参数对象Data将封包成为请求报文。其中接口名称固定为字符串封包,而请求参数根据使用场景不同,使用不同的封包方式,**最终都是二进制数据**。 | ||
|
||
2. **服务端接口入参,一般使用客户端请求参数相同类型**,但也可以使用完全不同的类型,只要能够解析出来即可。例如,不管请求参数是什么类型,接口入参一定可以使用Byte[]或Packet,此时可就得自己解析二进制数据了。又如,请求参数是复杂对象,使用Json封包,而接口入参使用String,那么将会收到一段Json字符串。 | ||
|
||
3. 服务端接口返回结果数据,一般使用强类型对象,使用Json封包或二进制封包返回,**最终也都是二进制数据**。 | ||
|
||
4. 客户端Invoke收到服务端响应时,**根据TResult解析结果数据Data**,一般使用服务端接口的返回类型。也可以使用Byte[]/Packet/String/IDictionary<String,Object>等类型解析返回数据。 | ||
|
||
5. 如果服务端接口抛出异常,将返回异常报文,并且把异常信息打包在Data中返回。客户端Invoke内部解析异常报文的Code和Data以后,**抛出ApiException异常**。 | ||
|
||
|
||
|
||
## Json封包 | ||
请求参数或结果数据是复杂对象时,默认使用Json序列化。 | ||
```csharp | ||
await _Client.InvokeAsync<Object>("api/info", new { state="abcd", state2=1234 }) | ||
``` | ||
客户端使用以上代码,请求参数Data的封包就是:`{"state":"abcd","state2":1234}`,共30字节。 | ||
|
||
Json封包具有很好的可读性和兼容性,能够满足绝大多数应用场景。它的缺点就是性能损耗,频繁传输较大对象时,大部分时间都耗费在Json序列化之上。 | ||
|
||
|
||
|
||
## 二进制封包 | ||
请求参数或结果数据是极为复杂且需要自定义序列化的对象,或者干脆直接就是传输二进制数据时,使用二进制封包。 | ||
|
||
复杂对象支持**IAccessor接口**时,可通过该接口的Read/Write实现二进制序列化及反序列化; | ||
|
||
参数或结果直接就是**Byte[]**或**Packet**时,直接封包; | ||
|
||
```csharp | ||
await _Client.InvokeAsync<Object>("api/info", new MyAccessor{ state="abcd", state2=1234 }) | ||
``` | ||
客户端使用以上代码,请求参数Data的封包就是:`0461626364D209`,共7个字节,远比Json封包的30字节要小得多。 | ||
二进制封包具有极高的吞吐性能、极小的报文大小。它的缺点就是可读性很差,网络抓包后几乎识别不出来内容。例如文件传输和多媒体传输等场景,就可以优先使用二进制封包。 | ||
|
||
|
||
|
||
## 基础类型封包 | ||
请求参数和结果数据也支持使用简单类型,例如整数、小数、布尔型、时间日期和字符串等,此时统一按照字符串进行封包传输。 | ||
|
Binary file not shown.
Oops, something went wrong.