Net 网络
ZProRx 包括Socket 和Http的封装支持。
Socket
使用Topic进行消息的发送与订阅,分为客户端与Server端两种情况。 数据包使用的共通的NetPackage
结构,其模板使用可以用于扩展支持自定义的数据。
public class NetPackage<T, TErrorEnum> // where TErrorEnum : System.Enum
{
private ZProperty<T> data = new ZProperty<T>();
private ZProperty<TErrorEnum> status = new ZProperty<TErrorEnum>();
private ZProperty<string> msg = new ZProperty<string>();
private Dictionary<string, string> headers = new Dictionary<string, string>();
}
Demo如下:
var propObser = ZPropertySocket.ReceivePackage<TestPropData>("topic/multisubscribe");
propObser.Subscribe(str =>
{
//... do something
}).AddTo(disposables);
ZPropertySocket.PostPackage<TestPropData>("topic/multisubscribe", data).Subscribe();
注意: 客户端订阅一个消息时,会加上ClientId为Topic的一部分。 从Socket层来说,其ClientId是透明的。需要应用进行封装与控制。
接口格式如下:
-
发送消息 Post[Package] 用于异步发送,支持错误返回,即发送失败。比如网络异常等。其返回值不表示接收者已经接收。 Send[Package] 用于同步发送,并接收返回值。
-
接收消息 Receive[Raw/Package/RawPackage(SocketPackageHub)/LowSocketPackage][AndResponse]
消息类型如下:
- Raw 发送普通字符串
- Package 是ZP格式的数据包,格式如下,如果其支持Response时,内部是使用串行返回的。
- PackageAndResponse 表示接收到之后可以进行返回。返回值支持
ZNull
,其不会占用传输的Size,但其表达接收到返回值,常常用于回应。 - RawPackage 即接收
SocketPackage
包,是指带有ClientID的Package包。如果其支持Response,那么内部是使用的并行返回的。 - RawPackageAndResponse 接收端可以获取到
SocketPackageHub
其包括ISocketPackage
原始数据包,和封装的NetPackage包,可以获取额外的客户端内容。比如ClientId等。
public interface ISocketPackage
{
string Key { get; }
string Value { get; set; }
string Topic { get; }
}
- LowSocketPackage 以接口的返回
ISocketPackage
包,用户可以根据需求自己进行Package的解析,当然与需要发送端协商一致。 这里要注意与上面的RawPackage 和 RawPackageAndResponse 返回的是不同的,即返回的直接的ISocketPackage
包,需要自行进行数据包的解析。
SocketObservable
以上可以看到ZProRX 对于Socket的处理也是反映式的。
其中使用的核心类如下(对于调用者是透明的) Socket[RawRequest/Request/Package/RawPackage(SocketPackage)]Observable
- Request 用于通用的请求
- Package 用于Receive请求
返回IObservable,其好处是,支持取消功能,调用Dispose方法会进行反注册等取消操作。 支持Retry/Delay等Operator 相关的操作
T 参数
支持接收Package的API,都对应的四套模板用于处理不同的参数形式(接收以及返回的数据格式),基本包括:
无
其中无参数的即以IRawDataRef
去接收数值,返回IObservable,应用根据 GetData方法获取到对应的数据。
(IRawDataPref rawData, ISocketPackage rawPack) => {
Assert.IsTrue(string.Compare(rawPack.Key, "1000") == 0);
//throw new Exception("");
//throw new ZNetMultiException<TestErrorEnum>(TestErrorEnum.Error1);
//get data from IRawDataRef
var data = rawData.GetData<TestPropData>();
Assert.IsTrue(string.Compare(data.name.Value, "testobjectname") == 0);
//return result
return true;
});
- <T, TResult>
- <T, TError,TResult>
以上不同的层次,可以进一步明确接收参数、回应返回值参数、回应可能出现的错误 <T, TResult> 时对应可能的错误值为 ZNetErrorEnum
<T, TError,TResult> 时,错误值为自定义参数类型,通过[MultiEnum] 方法对ZNetErrorEnum进行兼容。
注意 对于无参数的方式,其优点是有很强的通用性,但是以牺牲是一定的性能(需要转换多次)以及可读性。 对于明确返回值类型以及错误类型的还是推荐使用对应的模板参数。
SendPackage的API要简化许多,这里只有<T, TResult> 和<T, TError,TResult>两个版本。 对于没有明确返回错误模板参数的,在处理中返回自定义错误也是可以的,这时要使用 SendPackage2<T, TResult>
进行接收返回值,其可以接收所有异常,其异常类型为ZNetException。 SendPackage<T, TResult>
其只能接收并抛出 ZNetErrorEnum 异常值,如果Server端返回其它异常,Server端会直接抛出,而Client端无法接收到返回值了。 当然SendPackage2<T, TResult>
也同样是有一定的性能损耗。
//test for common TError
disp = ZPropertySocket.ReceivePackageAndResponse<TestPropData, bool>("topic/responseCustomError", null).
Subscribe( //< TestPropData, bool> support return
(TestPropData a1) => {
Assert.IsTrue(string.Compare(a1.name.Value, "testobjectname") == 0);
//throw new Exception("");
throw new ZNetMultiException<TestErrorEnum>(TestErrorEnum.Error1);
return true;
}).AddTo(disposables);
ZPropertySocket.SendPackage2<TestPropData, bool>("topic/responseCustomError", data)
.Subscribe(bRet =>
{
taskEnd.Value = true;
//Assert.IsTrue(bRet);
}, error =>
{
//Assert.IsTrue((error as ZNetException<ZNetErrorEnum>).Error == ZNetErrorEnum.ActionError);
Assert.IsTrue(error.IsMultiError<TestErrorEnum>(TestErrorEnum.Error1));
taskEnd.Value = true;
});
TestErrorEnum 为自定义的枚举错误类型。
推荐使用ZException进行统一接收以及Rx的Catch操作。以下是Ignore一个异常。
.CatchIgnore((ZException ex) =>
{
Assert.IsTrue(ex.IsMultiError<TestErrorEnum>(TestErrorEnum.Error1));
Assert.IsTrue(true);
taskEnd.Value = true;
})
以下是Catch一个异常并返回新的数据流。其实是新建立了一个分支。
.Catch<bool, ZException>((e) => {
return Observable.Return(true);
})
安全
在TTPServer端 ValidatingMqttClients的方法中进行相关的安全Token验证。Token来至ZRoom的Token (在调用 JoinRoom API返回) /api/v1/matrix/arch/users/{vroomid} [TODO] 目前未对应
Retry支持
Rx规范对应,Net API Retry的支持,要支持Retry,其内部原理是实现进行重新订阅,需要支持Retry的Observable的实际动作应该在SubscribeCore 方法中完成。
Receive API 不支持Retry
Reactive
以下都是ZRropertySocket.Socket/Net Receive[Raw/Package/RawPackage(SocketPackage)/LowRawPackage][AndResponse]
如下所示:对于Receive Package 支持的反映式的处理方法。
主要的订阅包括:
IObservable<SocketPackageHub<T>>
IRawPackageObservable<IRawDataPref>
IRawPackageObservable<T, TError>
IRawPackageObservable<T>
RawPackage 的支持
包括的订阅:
IObservable<SocketPackageHub<T>>
IRawPackageObservable<IRawDataPref>
IRawPackageObservable<T, TError>
IRawPackageObservable<T>
Dispose
- 对于Receive操作一般要保持长时间的Active状态,所以一般是Channel/Room/APP退出时进行调用,可以与对应的Extension进行关联
- 对于Send/Post 操作,一般是短时间进行,可以不进行Dispose。但对于些有返回值的Send操作,需要可以进行Dispose操作。其实内部在接收返回值时,会定义一个Result的临时的Topic进行接收。这个是会自动在接收到消息之后进行反注册的。但推荐还是进行Dispose操作,使用using。
Web Request Support
- 对于Recevie + [AndResponse]的接收API, 其返回值类型还支持错误值处理,即通过Server端返回错误值,目前支持
ZNetErrorEnum
通用Enum类型,同时支持自定义的Enum类型。这里使用的复合Enum类实现,MultiEnum<ZNetErrorEnum, TErrorEnum>
如果Recevie 端(Server)需要返回给错误信息给调用者,通过ThrowZNetException
类的异常。
如下参考的错误定义,Base从ZNetErrorEnum.MaxError
开始定义。
public enum RoomErrorEnum
{
BaseError = ZNetErrorEnum.MaxError + 0x100,
UnsupportedUnit,
NotFindUnit,
NotFindRoom,
NotInService,
}
主要用法是
[TODO] 错误码要与HTTP 标准码进行关联,目前还未设计
注意事项
对网络等返回Observable的API是不支await的,因为接收到个消息后,不会触发Completed事件。 如: ZPropertyNet.Post/Get/Put
如果一定直接使用它们,并需要进行Await的话,需要调用Fetch操作。 对于整体ZPropertySocket 的API也都是同样的问题。 类似的还有一些带有连续的消息,也都只有Completed时才能Await返回。
相关链接:
项目开源地址:https://github.com/bennychao/ZProRx.Lib
下载安装:(ZP.Lib.Server 是ZRroRx的核心库,后续其它功能库陆续开放中)
- .Net CLI
>dotnet add package ZP.Lib.Server --version 1.0.3
- 或 VS 2019 Nuget 包管理中搜索"ZP.Lib.Server"并安装(推荐)
ZProRx.Lib Unity Package 1.0.3 (用于在Untiy中使用)下载地址:
https://github.com/bennychao/ZProRx.Lib/releases/
https://github.com/bennychao/ZProRx.Lib/Publish/ZProRx.Lib.package
下一节
【预告】【ZProRx 重装上阵】 第五回 反映式