当前位置:首页 » 《随便一记》 » 正文

【RRQMSocket.RPC】C# RRQMRPC的高级使用设置_若汝棋茗 的博客

23 人参与  2022年04月15日 09:10  分类 : 《随便一记》  评论

点击全文阅读


文章目录

    • 一、序言
        • 本节须知
    • 二、程序集源码、Demo下载
        • 2.1 源码位置
        • 2.2 Demo位置
    • 三、安装
    • 四、序列化选择
        • 4.1 RRQM支持的序列化
        • 4.2 使用预设序列化
        • 4.3 自定义序列化
    • 五、调用反馈类型
        • 5.1 类型说明
        • 5.2 使用
    • 六、获取调用上下文
    • 七、调用超时设置
        • 7.1 计时器设置
        • 7.2 任务取消
        • 7.3 服务任务取消
    • 八、调用服务实例
        • 8.1 服务实例类型
        • 8.2 服务实例类型选择
    • 九、Qos筛选服务


一、序言

本节须知

在学习本节之前,您必须熟悉RRQM中的TcpRpcParser解析器与TcpRpcClient客户端(或其派生类,例如文件传输)的创建,如果您不熟悉,请在下列链接中了解。

  • 【RRQMSocket.RPC】C# 创建基于TCP协议的支持ref和out关键字的RPC

二、程序集源码、Demo下载

2.1 源码位置

RRQMSocket

2.2 Demo位置

RRQMBox

三、安装

安装RRQMSocket.RPC即可,具体步骤详看链接博客。

VS、Unity安装和使用Nuget包

四、序列化选择

从下图(图片来源网络)可以看出,序列化是RPC中至关重要的一个环节,可以说,序列化的优劣,会很大程度的影响RPC调用性能。

在这里插入图片描述

4.1 RRQM支持的序列化

在RRQMRPC中,内置了四种序列化方式,分别为RRQMBinarySystemBinaryJsonXml。这四种方式的特点,就是其序列化的特点。

RRQMBinarySystemBinaryJsonXml
特点序列化方式速度快,数据量小,但是兼容的数据格式也比较有限。仅支持基础类型、自定义实体类、数组、List、字典保真度高,支持接口,抽象类,object,泛型等类型的序列化,但是需要Serializable的标签,且必须是同一类型(或重写映射图谱)兼容性好,可读性强,但是受字符串影响,性能不出众,且数据量受限制兼容性一般,可读性强,同样受字符串影响,性能不出众,且数据量受限制

4.2 使用预设序列化

在RRQMRPC中,选择序列化是非常简单的,且序列化方式完全由调用端决定。

在实际的调用中,通过InvokeOption的参数指定。
在这里插入图片描述
实际上,只需要传入相关参数即可。

InvokeOption invokeOption = new InvokeOption();
invokeOption.SerializationType = RRQMCore.Serialization.SerializationType.RRQMBinary;
//invokeOption.SerializationType = RRQMCore.Serialization.SerializationType.Json;
//invokeOption.SerializationType = RRQMCore.Serialization.SerializationType.SystemBinary;
//invokeOption.SerializationType = RRQMCore.Serialization.SerializationType.Xml;

string returnString = client.Invoke<string>("TestOne", invokeOption, "10");

4.3 自定义序列化

a).定义
想要实现自定义序列化,必须通过重写序列化选择器,实现SerializeParameterDeserializeParameter函数。如果还想留用预设序列化,请按下代码示例即可。

public class MySerializationSelector: SerializationSelector
{
    /// <summary>
    /// 反序列化
    /// </summary>
    /// <param name="serializationType"></param>
    /// <param name="parameterBytes"></param>
    /// <param name="parameterType"></param>
    /// <returns></returns>
    public override object DeserializeParameter(SerializationType serializationType, byte[] parameterBytes, Type parameterType)
    {
        if (parameterBytes == null)
        {
            return parameterType.GetDefault();
        }
        switch (serializationType)
        {
            case SerializationType.RRQMBinary:
                {
                    return SerializeConvert.RRQMBinaryDeserialize(parameterBytes, 0, parameterType);
                }
            case SerializationType.SystemBinary:
                {
                    return SerializeConvert.BinaryDeserialize(parameterBytes, 0, parameterBytes.Length);
                }
            case SerializationType.Json:
                {
                    return JsonConvert.DeserializeObject(Encoding.UTF8.GetString(parameterBytes), parameterType);
                }
            case SerializationType.Xml:
                {
                    return SerializeConvert.XmlDeserializeFromBytes(parameterBytes, parameterType);
                }
            case (SerializationType)4:
                {
                    //此处可自行实现
                    return default;
                }
            default:
                throw new RRQMRPCException("未指定的反序列化方式");
        }
    }

    /// <summary>
    /// 序列化参数
    /// </summary>
    /// <param name="serializationType"></param>
    /// <param name="parameter"></param>
    /// <returns></returns>
    public override byte[] SerializeParameter(SerializationType serializationType, object parameter)
    {
        if (parameter == null)
        {
            return null;
        }
        switch (serializationType)
        {
            case SerializationType.RRQMBinary:
                {
                    return SerializeConvert.RRQMBinarySerialize(parameter, true);
                }
            case SerializationType.SystemBinary:
                {
                    return SerializeConvert.BinarySerialize(parameter);
                }
            case SerializationType.Json:
                {
                    return Encoding.UTF8.GetBytes(JsonConvert.SerializeObject(parameter));
                }
            case SerializationType.Xml:
                {
                    return SerializeConvert.XmlSerializeToBytes(parameter);
                }
            case (SerializationType)4:
                {
                    //此处可自行实现
                    return default;
                }
            default:
                throw new RRQMRPCException("未指定的序列化方式");
        }
    }
}

b).使用
首先在解析器客户端配置中赋值解析器。
在这里插入图片描述

然后,因为赋值时是SerializationType的枚举类型,所以执行强制类型转换即可。

InvokeOption invokeOption = new InvokeOption();
invokeOption.SerializationType = (RRQMCore.Serialization.SerializationType)4;

五、调用反馈类型

5.1 类型说明

RRQMRPC的调用状态有三种状态可选,分别为:OnlySendWaitSendWaitInvoke。区别是:

OnlySendWaitSendWaitInvoke
仅发送RPC请求,在TCP底层协议下,能保证发送成功,但是不反馈服务器任何状态,也不会取得返回值异常等信息。在UDP底层协议下,不保证发送成功,仅仅是具有请求动作而已。发送RPC请求,并且等待收到状态返回,能保证RPC请求顺利到达服务,但是不能得知RPC服务是否成功执行,也不会取得返回值异常等信息发送RPC请求,且返回所有信息,包括是否成功调用,执行后的返回值异常等信息。

5.2 使用

同样的,在InvokeOption中可以直接赋值使用。

InvokeOption invokeOption = new InvokeOption();

invokeOption.FeedbackType = FeedbackType.WaitInvoke;
//invokeOption.FeedbackType = FeedbackType.OnlySend;
//invokeOption.FeedbackType = FeedbackType.WaitSend;

string returnString = client.Invoke<string>("TestOne", invokeOption, "10");

六、获取调用上下文

RPC服务是无状态的,即只知道当前服务被调用,但无法得知是被谁调用,这个问题给日志记录、RPC回调等带来了很多麻烦事。但是,RRQMRPC支持调用上下文获取。在上下文中可以获得调用者(ICaller)、MethodInvoker等。

步骤:

  1. RRQMRPC标签需要传入IncludeCallContext参数。
  2. 定义的服务的第一个参数必须是IServerCallContext或其派生类。
  3. 最后获得其Caller属性即可得到调用者。
public class GetCallerRpcServer : ServerProvider
{
    [RRQMRPC(MethodFlags.IncludeCallContext)]
    public string GetCallerID(IServerCallContext callContext)
    {
        if (callContext.Caller is RpcSocketClient socketClient)
        {
            return socketClient.ID;
        }
        return null;
    }

    [RRQMRPC(MethodFlags.IncludeCallContext)]
    public string GetCallerID_2(RpcServerCallContext callContext)
    {
        if (callContext.Caller is RpcSocketClient socketClient)
        {
            return socketClient.ID;
        }
        return null;
    }
}

七、调用超时设置

调用RPC,不能无限制等待,必须要有计时器,或者任务取消的功能。

7.1 计时器设置

直接对InvokeOptionTimeout 属性赋值即可,单位为毫秒

InvokeOption invokeOption = new InvokeOption();

invokeOption.Timeout = 1000 * 10;//10秒后无反应,则抛出RRQMTimeoutException异常

string returnString = client.Invoke<string>("TestOne", invokeOption, "10");

7.2 任务取消

在RPC调用时,计时器是一个好的选择,但是还不够完美,有时候我们希望能手动终结某个调用任务。这时候,计时器就不堪重任,需要能主动取消任务的功能。熟悉.net的小伙伴都知道,CancellationToken是具备这个功能的。同样的,只需要对InvokeOptionCancellationToken 赋值即可。

InvokeOption invokeOption = new InvokeOption();

CancellationTokenSource tokenSource = new CancellationTokenSource();
invokeOption.CancellationToken = tokenSource.Token;

//tokenSource.Cancel();//调用时取消任务

string returnString = client.Invoke<string>("TestOne", invokeOption, "10");

7.3 服务任务取消

实际上7.2的取消任务,仅仅能实现让客户端取消请求,但是服务器并不知道,如果想让服务器也感知任务消息,就必须依托于调用上下文。

public class ElapsedTimeRpcServer : ServerProvider
{
    [Description("测试可取消的调用")]
    [RRQMRPC(MethodFlags.IncludeCallContext)]
    public bool DelayInvoke(IServerCallContext serverCallContext,int tick)//同步服务
    {

        for (int i = 0; i < tick; i++)
        {
            Thread.Sleep(100);
            if (serverCallContext.TokenSource.IsCancellationRequested)
            {
                Console.WriteLine("客户端已经取消该任务!");
                return false;//实际上在取消时,客户端得不到该值
            }
        }
        return true;
    }
}

八、调用服务实例

调用的服务,必须由承载服务的实例参与完成,但是,实例的不同,其调用结果大相径庭。

例如:在TestInstanceRpcServer服务中定义了Count属性和Increment函数。每次调用IncrementCount会递增,然后返回Count值。如果某个客户端连续调用两次,会得到什么值呢?亦或者,某两个客户端,各调用一次,会得到什么值呢?

public class TestInstanceRpcServer : ServerProvider
{
    public int Count { get; set; }

    [RRQMRPC]
    public int Increment()//同步服务
    {
        return ++Count;
    }
}

想必你心里已经有了答案,但是,我要告诉你,你都答案是错误的!Error!Error!

因为,答案既是你想的,也不是你想的。

8.1 服务实例类型

实际上,RRQMRPC中,支持三种服务实例类型,分别为GlobalInstanceCustomInstanceNewInstance,三者的区别如下。

GlobalInstanceCustomInstanceNewInstance
全局实例,即所有客户端,调用一个实例,在上述案例中,Count值会一直递增。用户拥有实例,即一个RPC连接调用一个实例,在上述案例中,Count会在同一客户端调用时递增,在不同客户端中重新计数。新实例,即每次调用,都使用新实例,在上述案例中,获得的Count会一直等于1。

8.2 服务实例类型选择

服务类型,实际实际上还是由InvokeOption设置。

InvokeOption invokeOption = new InvokeOption();

invokeOption.InvokeType = RRQMSocket.RPC.InvokeType.CustomInstance;
//invokeOption.InvokeType = RRQMSocket.RPC.InvokeType.GlobalInstance;
//invokeOption.InvokeType = RRQMSocket.RPC.InvokeType.NewInstance;

string returnString = client.Invoke<string>("TestOne", invokeOption, "10");

九、Qos筛选服务

默认情况下,客户端获取代理、发现服务是服务器上定义的全部内容。但是有时候我们希望不同的客户端或不同ProxyToken获取到的服务是不一样的,这时候,就可以在服务端进行判断,然后决定服务筛选。

  1. 首先,继承TcpRpcParser(或者UdpRpcParser)
  2. 然后,重写GetProxyInfoGetRegisteredMethodItems两个函数。
class QosTcpRpcParser : TcpRpcParser
{
    /// <summary>
    /// 在获取代理时筛选,
    /// 仅筛选代理代码功能,并不能决定服务能不能调用
    /// </summary>
    /// <param name="proxyToken"></param>
    /// <param name="caller"></param>
    /// <returns></returns>
    public override RpcProxyInfo GetProxyInfo(string proxyToken, ICaller caller)
    {
        RpcProxyInfo rpcProxy= base.GetProxyInfo(proxyToken, caller);
        if (proxyToken.StartsWith("RPC"))
        {
            RpcProxyInfo proxyInfo = new RpcProxyInfo()
            {
                AssemblyName=this.NameSpace+".dll",
                Status = 1,//1表示成功,2表示失败
                Version = this.RPCVersion.ToString()
            };

            string ser = proxyToken.Replace("RPC", string.Empty);

            proxyInfo.Codes = new List<CellCode>(this.Codes.Where(a =>a.CodeType== CodeType.ClassArgs|| a.Name.Contains(ser)));

            return proxyInfo;
        }
        else
        {
            return new RpcProxyInfo() { Status = 2, Message = "你不配拥有代理文件" };//1表示成功,2表示失败
        }
    }

    /// <summary>
    /// 在客户端发现服务时调用,
    /// 决定该客户端能不能调用某个服务(或服务函数)
    /// </summary>
    /// <param name="proxyToken"></param>
    /// <param name="caller"></param>
    /// <returns></returns>
    public override List<MethodItem> GetRegisteredMethodItems(string proxyToken, ICaller caller)
    {
        if (proxyToken.StartsWith("RPC"))
        {
            string ser = proxyToken.Replace("RPC", string.Empty);

            //全部服务
            List<MethodItem> methodItems = this.MethodStore.GetAllMethodItem();

            return new List<MethodItem>(methodItems.Where(m => m.ServerName.Contains(ser)));
        }
        else
        {
            return new List<MethodItem>();
        }
    }
}


上述逻辑主要实现,当客户端的ProxyToken不是RPC开头时,返回错误消息(你不配拥有代理文件),当客户端的ProxyToken是RPC开头时,再次筛选服务。例如:当输入RPCElapsedTimeRpcServer时,仅代理ElapsedTimeRpcServer服务。诸如此类。

> 例如:当用户输入RPC

在这里插入图片描述


点击全文阅读


本文链接:http://m.zhangshiyu.com/post/38063.html

调用  服务  序列化  
<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1