--- id: rpcother sidebar_position: 6 title: 其他技巧 sidebar_label: f.其他技巧 --- ## 一、说明 > RPC服务是无状态的,即只知道当前服务被调用,但无法得知是被谁调用,这个问题给日志记录、RPC回调等带来了很多麻烦事。但是,Touch的RPC支持调用上下文获取。在上下文中可以获得调用者(`ICaller`)信息等。 ## 二、通过标签参数获取 **步骤:** 1. RPC标签需要传入`MethodFlags.IncludeCallContext`参数。 2. 定义的服务的第一个参数必须是`ICallContext`或其派生类。 3. 最后获得其Caller属性即可得到调用者。 ```csharp public class MyRpcServer : RpcServer { [Description("登录")] [TouchRpc(MethodFlags = MethodFlags.IncludeCallContext)]//使用调用上才文 public bool Login(ICallContext callContext,string account,string password) { if (callContext.Caller is TcpTouchRpcSocketClient) { Console.WriteLine("TcpTouchRpc请求"); } if (account=="123"&&password=="abc") { return true; } return false; } } ``` ## 三、通过瞬时生命周期获取 步骤: 1. 继承TransientRpcServer或者实现ITransientRpcServer接口。 ```csharp public class MyRpcServer : TransientRpcServer { [Description("登录")] [TouchRpc] public bool Login(string account,string password) { if ( this.CallContext.Caller is TcpTouchRpcSocketClient) { Console.WriteLine("TcpTouchRpc请求"); } if (account=="123"&&password=="abc") { return true; } return false; } } ``` ## 调用反馈类型 RPC在调用时,的调用状态有三种状态可选,分别为:`OnlySend`、`WaitSend`、`WaitInvoke`。区别是: | OnlySend | WaitSend | WaitInvoke | | --- | --- | --- | | 仅发送RPC请求,在TCP底层协议下,能保证发送成功,但是不反馈服务器**任何状态**,也不会取得**返回值**、**异常**等信息。在UDP底层协议下,不保证发送成功,仅仅是具有请求动作而已。 | 发送RPC请求,并且等待收到状态返回,能保证RPC请求顺利到达服务,但是不能得知RPC服务是否成功执行,也不会取得**返回值**、**异常**等信息 | 发送RPC请求,且返回所有信息,包括是否成功调用,执行后的**返回值**或**异常**等信息。 | #### 使用 同样的,在InvokeOption中可以直接赋值使用。 ```csharp InvokeOption invokeOption = new InvokeOption(); invokeOption.FeedbackType = FeedbackType.WaitInvoke; //invokeOption.FeedbackType = FeedbackType.OnlySend; //invokeOption.FeedbackType = FeedbackType.WaitSend; string returnString = client.Invoke("TestOne", invokeOption, "10"); ``` ***注意:假如IInvokeOption使用的是InvokeOption的话,在new的时候,应该对其他参数也进行设置(因为它是结构体)。*** ## 说明 RPC服务在被调用是,可以使用实现**IRpcActionFilter**的**特性(Attribute)**,进行相关AOP操作。 ## 声明特性 ```csharp public class MyRpcActionFilterAttribute : RpcActionFilterAttribute, IRpcActionFilter { public override void Executing(ICallContext callContext, ref InvokeResult invokeResult) { if (callContext.Caller is TcpTouchRpcSocketClient client) { client.Logger.Info($"即将执行RPC-{callContext.MethodInstance.Name}"); } base.Executing(callContext, ref invokeResult); } public override void Executed(ICallContext callContext, ref InvokeResult invokeResult) { if (callContext.Caller is TcpTouchRpcSocketClient client) { client.Logger.Info($"执行RPC-{callContext.MethodInstance.Name}完成,状态={invokeResult.Status}"); } base.Executed(callContext, ref invokeResult); } public override void ExecutException(ICallContext callContext, ref InvokeResult invokeResult, Exception exception) { if (callContext.Caller is TcpTouchRpcSocketClient client) { client.Logger.Info($"执行RPC-{callContext.MethodInstance.Name}异常,信息={invokeResult.Message}"); } base.ExecutException(callContext, ref invokeResult, exception); } } ``` ## 使用 ```csharp [Description("性能测试")] [TouchRpc] [MyRpcActionFilter] public int Performance(int a) { return a; } ``` ## 一、序列化选择 从下图(图片来源[网络](https://www.jianshu.com/p/7d6853140e13))可以看出,序列化是RPC中至关重要的一个环节,可以说,序列化的优劣,会很大程度的影响RPC调用性能。 #### 1.1 支持的序列化 在TouchRpc中,内置了四种序列化方式,分别为`FastBinary`、`Json`、`Xml`、`SystemBinary`。这四种方式的特点,就是其序列化的特点。 | | FastBinary | Json | Xml | SystemBinary | | --- | --- | --- | --- | --- | | 特点 | 序列化方式速度快,数据量小,但是兼容的数据格式也比较有限。仅支持基础类型、自定义实体类、数组、List、字典 | 兼容性好,可读性强,但是受字符串影响,性能不出众,且数据量受限制 | 兼容性一般,可读性强,同样受字符串影响,性能不出众,且数据量受限制 | 序列化速度快。但是兼容性低。且要求类必须一致,不然需要重新指定图根。 | #### 1.2 使用预设序列化 在TouchRpc中,选择序列化是非常简单的,且序列化方式完全由`调用端`决定。 在实际的调用中,通过`InvokeOption`的参数指定。 实际上,只需要传入相关参数即可。 ```csharp InvokeOption invokeOption = new InvokeOption(); invokeOption.SerializationType = SerializationType.FastBinary; //invokeOption.SerializationType = RRQMCore.Serialization.SerializationType.Json; //invokeOption.SerializationType = RRQMCore.Serialization.SerializationType.Xml; string returnString = client.Invoke("TestOne", invokeOption, "10"); ``` #### 1.3 自定义序列化 **a).定义** 想要实现自定义序列化,必须通过重写序列化选择器,实现`SerializeParameter`和`DeserializeParameter`函数。如果还想留用预设序列化,请按下代码示例即可。 ```csharp /// /// 序列化选择器 /// public class MySerializationSelector : SerializationSelector { /// /// 反序列化 /// /// /// /// /// public override object DeserializeParameter(SerializationType serializationType, byte[] parameterBytes, Type parameterType) { if (parameterBytes == null) { return parameterType.GetDefault(); } switch (serializationType) { case SerializationType.FastBinary: { return SerializeConvert.FastBinaryDeserialize(parameterBytes, 0, parameterType); } case SerializationType.SystemBinary: { return SerializeConvert.BinaryDeserialize(parameterBytes, 0, parameterBytes.Length); } case SerializationType.Json: { return Encoding.UTF8.GetString(parameterBytes).FromJson(parameterType); } case SerializationType.Xml: { return SerializeConvert.XmlDeserializeFromBytes(parameterBytes, parameterType); } default: throw new RpcException("未指定的反序列化方式"); } } /// /// 序列化参数 /// /// /// /// public override byte[] SerializeParameter(SerializationType serializationType, object parameter) { if (parameter == null) { return null; } switch (serializationType) { case SerializationType.FastBinary: { return SerializeConvert.FastBinarySerialize(parameter); } case SerializationType.SystemBinary: { return SerializeConvert.BinarySerialize(parameter); } case SerializationType.Json: { return SerializeConvert.JsonSerializeToBytes(parameter); } case SerializationType.Xml: { return SerializeConvert.XmlSerializeToBytes(parameter); } default: throw new RpcException("未指定的序列化方式"); } } } ``` **b).使用** 首先在`解析器`和`客户端`**配置**中赋值解析器。 ```csharp var config = new TouchSocketConfig()//配置 .SetSerializationSelector(new MySerializationSelector()); ``` 然后,因为赋值时是`SerializationType`的枚举类型,所以执行强制类型转换即可。 ```csharp InvokeOption invokeOption = new InvokeOption(); invokeOption.SerializationType = (RRQMCore.Serialization.SerializationType)5; ``` ## 一、说明 调用RPC,不能无限制等待,必须要有计时器,或者任务取消的功能。 #### 1.1 计时器设置 直接对`InvokeOption`的`Timeout` 属性赋值即可,单位为`毫秒`。 ```csharp InvokeOption invokeOption = new InvokeOption(); invokeOption.Timeout = 1000 * 10;//10秒后无反应,则抛出RRQMTimeoutException异常 string returnString = client.Invoke("TestOne", invokeOption, "10"); ``` #### 1.2 任务取消 在RPC调用时,计时器是一个好的选择,但是还不够完美,有时候我们希望能手动终结某个调用任务。这时候,计时器就不堪重任,需要能主动取消任务的功能。熟悉.net的小伙伴都知道,CancellationToken是具备这个功能的。同样的,只需要对`InvokeOption`的`CancellationToken` 赋值即可。 ```csharp InvokeOption invokeOption = new InvokeOption(); CancellationTokenSource tokenSource = new CancellationTokenSource(); invokeOption.CancellationToken = tokenSource.Token; //tokenSource.Cancel();//调用时取消任务 string returnString = client.Invoke("TestOne", invokeOption, "10"); ``` #### 1.3 服务任务取消 实际上7.2的取消任务,仅仅能实现让客户端取消请求,但是服务器并不知道,如果想让服务器也感知任务消息,就必须依托于调用上下文。 此处的取消,有可能是调用者主动取消。也有可能是调用者已经掉线。 ```csharp public class ElapsedTimeRpcServer : ServerProvider { [Description("测试可取消的调用")] [RRQMRPC(MethodFlags.IncludeCallContext)] public bool DelayInvoke(ICallContext serverCallContext,int tick)//同步服务 { for (int i = 0; i < tick; i++) { Thread.Sleep(100); if (serverCallContext.TokenSource.IsCancellationRequested) { Console.WriteLine("客户端已经取消该任务!"); return false;//实际上在取消时,客户端得不到该值 } } return true; } } ```