Rpc功能
定义
命名空间:TouchSocket.Dmtp.Rpc
程序集:TouchSocket.Dmtp.dll
一、说明
RPC(Remote Procedure Call)远程过程调用协议,一种通过网络从远程计算机上请求服务,而不需要了解底层网络技术的协议。RPC它假定某些协议的存在,例如TPC/UDP等,为通信程序之间携带信息数据。在OSI网络七层模型中,RPC跨越了传输层和应用层,RPC使得开发,包括网络分布式多程序在内的应用程序更加容易。
过程是什么? 过程就是业务处理、计算任务,更直白的说,就是程序,就是想调用本地方法一样调用远程的过程。
本Rpc是基于Dmtp协议的Rpc组件。其功能包括:
- 支持客户端主动调用服务器。
- 支持服务主动调用客户端。
- 支持客户端之间互相调用。
- 支持绝大多数数据类型及自定义实体类。
- 支持自定义序列化。
- 支持out,ref关键字。
二、使用Rpc服务
2.1 定义服务
- 在服务器端中新建一个类名为MyRpcServer。
- 继承于RpcServer类、或实现IRpcServer。亦或者将服务器声明为瞬时生命的服务,继承TransientRpcServer、或ITransientRpcServer。
- 在该类中写公共方法,并用DmtpRpc属性标签标记。
public partial class MyRpcServer : RpcServer
{
[Description("登录")]//服务描述,在生成代理时,会变成注释。
[DmtpRpc("Login")]//服务注册的函数键,此处为显式指定。默认不传参的时候,为该函数类全名+方法名的全小写。
public bool Login(string account, string password)
{
if (account == "123" && password == "abc")
{
return true;
}
return false;
}
}
public partial class MyRpcServer : TransientRpcServer
{
[Description("登录")]//服务描述,在生成代理时,会变成注释。
[DmtpRpc("Login")]//服务注册的函数键,此处为显式指定。默认不传参的时候,为该函数类全名+方法名的全小写。
public bool Login(string account,string password)
{
if (account=="123"&&password=="abc")
{
return true;
}
return false;
}
}
ITransientRpcServer
和IRpcServer
相比,意为瞬时生命服务,即实现ITransientRpcServer
的服务,在每次被调用时,都会创建一个新的服务实例。其优点为可以直接通过this.CallContext
属性获得调用上下文。其缺点则是每次调用时会多消耗一些性能。
2.2 启动Dmtp并注册Rpc服务
以下仅示例基于Tcp协议Dmtp。其他协议的服务器请看创建Dmtp服务器
更多注册Rpc的方法请看注册Rpc服务
var service = new TcpDmtpService();
var config = new TouchSocketConfig()//配置
.SetListenIPHosts(7789)
.ConfigureContainer(a=>
{
a.AddRpcStore(store =>
{
store.RegisterServer<MyRpcServer>();//注册服务
});
})
.ConfigurePlugins(a =>
{
a.UseDmtpRpc();
})
.SetDmtpOption(new DmtpOption()
{
VerifyToken = "Rpc"//连接验证口令。
});
await service.SetupAsync(config);
await service.StartAsync();
service.Logger.Info($"{service.GetType().Name}已启动");
2.3 调用Rpc
2.3.1 直接调用
直接 调用,则是不使用任何代理,使用字符串和参数直接Call Rpc,使用比较简单。
下列以TcpDmtpClient为例,其他客户端请看创建Dmtp客户端。
var client = new TcpDmtpClient();
await client.SetupAsync(new TouchSocketConfig()
.SetRemoteIPHost("127.0.0.1:7789")
.ConfigurePlugins(a =>
{
a.UseDmtpRpc();
})
.SetDmtpOption(new DmtpOption()
{
VerifyToken = "Rpc"//连接验证口令。
}));
await client.ConnectAsync();
bool result =(bool) client.GetDmtpRpcActor().Invoke(typeof(bool),"Login", InvokeOption.WaitInvoke, "123", "abc");
直接调用时,第一个参数为返回值
类型,当没有返回值时则可以不用。第二个参数为调用键
,调用键默认情况下为服务类的“命名空间+类名+方法名
”的全小写
。但在本案例中直接指定了以“Login”为调用键。第三个参数为调用配置
参数,可设置调用超时时间,取消调用等功能。示例中使用的预设,实际上可以自行new InvokeOption()。后续参数为调用参数
。
或者使用InvokeT
的扩展方法调用。在有返回值时,可以直接泛型传参。
bool result =client.GetDmtpRpcActor().InvokeT<bool>("Login", InvokeOption.WaitInvoke, "123", "abc");
2.3.2 代理调用
代理调用的便捷在于,客户端不用再知道哪些服务可调,也不用再纠结调用的参数类型正不正确,因为这些,代理工具都会替你做好。
详细步骤:
- 生成代理文件
- 将生成的cs文件添加到调用端一起编译。
以上示例,会生成下列代理代码。
【生成的代理】
using System;
using TouchSocket.Core;
using TouchSocket.Sockets;
using TouchSocket.Rpc;
using TouchSocket.Dmtp.Rpc;
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using System.Threading.Tasks;
namespace RpcProxy
{
public interface IMyRpcServer : TouchSocket.Rpc.IRemoteServer
{
///<summary>
///登录
///</summary>
/// <exception cref="System.TimeoutException">调用超时</exception>
/// <exception cref="TouchSocket.Rpc.RpcInvokeException">Rpc调用异常</exception>
/// <exception cref="System.Exception">其他异常</exception>
System.Boolean Login(System.String account, System.String password, IInvokeOption invokeOption = default);
///<summary>
///登录
///</summary>
/// <exception cref="System.TimeoutException">调用超时</exception>
/// <exception cref="TouchSocket.Rpc.RpcInvokeException">Rpc调用异常</exception>
/// <exception cref="System.Exception">其他异常</exception>
Task<System.Boolean> LoginAsync(System.String account, System.String password, IInvokeOption invokeOption = default);
}
public class MyRpcServer : IMyRpcServer
{
public MyRpcServer(IRpcClient client)
{
this.Client = client;
}
public IRpcClient Client { get; private set; }
///<summary>
///登录
///</summary>
/// <exception cref="System.TimeoutException">调用超时</exception>
/// <exception cref="TouchSocket.Rpc.RpcInvokeException">Rpc调用异常</exception>
/// <exception cref="System.Exception">其他异常</exception>
public System.Boolean Login(System.String account, System.String password, IInvokeOption invokeOption = default)
{
if (Client == null)
{
throw new RpcException("IRpcClient为空,请先初始化或者进行赋值");
}
object[] parameters = new object[] { account, password };
System.Boolean returnData = (System.Boolean)Client.Invoke(typeof(System.Boolean), "Login", invokeOption, parameters);
return returnData;
}
///<summary>
///登录
///</summary>
public async Task<System.Boolean> LoginAsync(System.String account, System.String password, IInvokeOption invokeOption = default)
{
if (Client == null)
{
throw new RpcException("IRpcClient为空,请先初始化或者进行赋值");
}
object[] parameters = new object[] { account, password };
return (System.Boolean)await Client.InvokeAsync(typeof(System.Boolean), "Login", invokeOption, parameters);
}
}
public static class MyRpcServerExtensions
{
///<summary>
///登录
///</summary>
/// <exception cref="System.TimeoutException">调用超时</exception>
/// <exception cref="TouchSocket.Rpc.RpcInvokeException">Rpc调用异常</exception>
/// <exception cref="System.Exception">其他异常</exception>
public static System.Boolean Login<TClient>(this TClient client, System.String account, System.String password, IInvokeOption invokeOption = default) where TClient :
TouchSocket.Rpc.IRpcClient
{
object[] parameters = new object[] { account, password };
System.Boolean returnData = (System.Boolean)client.Invoke(typeof(System.Boolean), "Login", invokeOption, parameters);
return returnData;
}
///<summary>
///登录
///</summary>
public static async Task<System.Boolean> LoginAsync<TClient>(this TClient client, System.String account, System.String password, IInvokeOption invokeOption = default) where TClient :
TouchSocket.Rpc.IRpcClient
{
object[] parameters = new object[] { account, password };
return (System.Boolean)await client.InvokeAsync(typeof(System.Boolean), "Login", invokeOption, parameters);
}
}
}
使用代理扩展直接调用。
var client = new TcpDmtpClient();
await client.SetupAsync(new TouchSocketConfig()
.SetRemoteIPHost("127.0.0.1:7789")
.ConfigurePlugins(a =>
{
a.UseDmtpRpc();
})
.SetDmtpOption(new DmtpOption()
{
VerifyToken = "Rpc"//连接验证口令。
}));
await client.ConnectAsync();
bool result = client.GetDmtpRpcActor().Login("123", "abc", InvokeOption.WaitInvoke);//Login是生成的代理扩展方法。可能需要额外添加命名空间。
client.GetDmtpRpcActor()的操作,内部还需要执行字典的查询操作。所以,如果为效率考虑的话,在连接稳定的前提下,可以保存好client.GetDmtpRpcActor()的返回值对象,直接执行Rpc操作。但是需要的注意的是,一旦重新连接,则该对象也需要重新获取。
三、反向Rpc
一般的rpc服务都是客户端发起,服务器响应。但是有时候也需要服务器发起,客户端响应,所以需要反向rpc。
3.1 定义、发布反向Rpc服务
实际上,Dmtp的全称(Duplex Message Transport Protocol双工消息传输协议),Duplex意为双工,则表明,当Dmtp客户端连接到服务以后,拥有与服务器同等的通讯权限与功能。所以客户端发布Rpc服务的步骤和服务器完全一致。即:当客户端和服务器建立连接以后,就不再区分谁是客户端,谁是服务器了。只关心,谁能提供服务,谁在调用服务。
下列就以简单的示例下,由客户端声明服务,服务器调用服务。
具体步骤:
- 在客户端项目中定义Rpc服务,名为
ReverseCallbackServer
。 - 用DmtpRpc标记需要公开的公共方法。
public partial class ReverseCallbackServer : RpcServer
{
[DmtpRpc(true)]//使用方法名作为调用键
public string SayHello(string name)
{
return $"{name},hi";
}
}
【客户端注册发布服务】
var client = new TcpDmtpClient();
await client.SetupAsync(new TouchSocketConfig()
.ConfigureContainer(a =>
{
a.AddConsoleLogger();
})
.ConfigureContainer(a =>
{
a.AddRpcStore(store =>
{
store.RegisterServer<ReverseCallbackServer>();
});
})
.ConfigurePlugins(a =>
{
a.UseDmtpRpc();
})
.SetRemoteIPHost("127.0.0.1:7789")
.SetDmtpOption(new DmtpOption()
{
VerifyToken = "Rpc"//连接验证口令。
}));
await client.ConnectAsync();
client.Logger.Info($"连接成功,Id={client.Id}");
3.2 调用反向Rpc
服务器回调客户端,最终必须通过服务器辅助类客户端(ITcpSessionClient
的派生类),以TcpDmtpService
为例,其辅助客户端为TcpDmtpSessionClient
(或其接口:ITcpDmtpSessionClient
)。
下列示例以TcpDmtpSessionClient为例,其余一致。