创建rpc服务
一、说明
RPC(Remote Procedure Call)远程过程调用协议,一种通过网络从远程计算机上请求服务,而不需要了解底层网络技术的协议。RPC它假定某些协议的存在,例如TPC/UDP等,为通信程序之间携带信息数据。在OSI网络七层模型中,RPC跨越了传输层和应用层,RPC使得开发,包括网络分布式多程序在内的应用程序更加容易。
过程是什么? 过程就是业务处理、计算任务,更直白的说,就是程序,就是想调用本地方法一样调用远程的过程。
TouchRpc支持服务器与客户端互相调用,也支持客户端之间相互调用。
二、定义服务
- 在被调用端中新建一个类名为MyRpcServer。
- 继承于RpcServer类、或实现IRpcServer。亦或者将服务器声明为瞬时生命的服务,继承TransientRpcServer、或ITransientRpcServer。
- 在该类中写公共方法,并用TouchRpc属性标签标记。
public class MyRpcServer : RpcServer
{
[Description("登录")]//服务描述,在生成代理时,会变成注释。
[TouchRpc("Login")]//服务注册的函数键,此处为显式指定。默认不传参的时候,为该函数类全名+方法名的全小写。
public bool Login(string account,string password)
{
if (account=="123"&&password=="abc")
{
return true;
}
return false;
}
}
public class MyRpcServer : TransientRpcServer
{
[Description("登录")]//服务描述,在生成代理时,会变成注释。
[TouchRpc("Login")]//服务注册的函数键,此处为显式指定。默认不传参的时候,为该函数类全名+方法名的全小写。
public bool Login(string account,string password)
{
if (account=="123"&&password=="abc")
{
return true;
}
return false;
}
}
瞬时生命的服务,最大的特点就是,每个请求,都会创建一个新的服务类对象。然后可以通过this.CallContext直接访问当前的调用上下文。
三、启动Rpc服务器
以下仅示例基于Tcp协议TouchRpc。其他协议的服务器请看创建TouchRpc服务器
var service =new TcpTouchRpcService();
TouchSocketConfig config= new TouchSocketConfig()//配置
.SetListenIPHosts(new IPHost[] { new IPHost(7789) })
.ConfigureRpcStore(a=>
{
a.RegisterServer<MyRpcServer>();//注册服务
})
.SetVerifyToken("TouchRpc");
service.Setup(config)
.Start();
service.Logger.Info($"{service.GetType().Name}已启动");
四、调用Rpc
4.1 直接调用
直接调用,则是不使用任何代理,使用字符串和参数直接Call Rpc,使用比较简单。
下列以TcpTouchRpcClient为例,其他客户端一模一样。
TcpTouchRpcClient client = new TcpTouchRpcClient();
client.Setup(new TouchSocketConfig()
.SetRemoteIPHost("127.0.0.1:7789")
.SetVerifyToken("TouchRpc"));
client.Connect();
//直接调用时,第一个参数为调用键
//第二个参数为调用配置参数,可设置调用超时时间,取消调用等功能。示例中使用的预设,实际上可以自行new InvokeOption();
//后续参数为调用参数。
//泛型为返回值类型。
bool result = client.Invoke<bool>("Login", InvokeOption.WaitInvoke, 123, "abc");
4.2、代理调用
代理调用的便捷在于,客户端不用再知道哪些服务可调,也不用再纠结调用的参数类型正不正确,因为这些,代理工具都会替你做好。
详细步骤:
- 生成代理文件
- 将生成的cs文件添加到调用端一起编译。
以上示例,会生成下列代理代码。
【生成的代理】
using TouchSocket.Rpc;
using System.Threading.Tasks;
namespace RpcProxy
{
public interface IMyRpcServer : 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为空,请先初始化或者进行赋值");
}
if (Client.TryCanInvoke?.Invoke(Client) == false)
{
throw new RpcException("Rpc无法执行。");
}
object[] parameters = new object[] { account, password };
System.Boolean returnData = Client.Invoke<System.Boolean>("Login", invokeOption, parameters);
return returnData;
}
///<summary>
///登录
///</summary>
public Task<System.Boolean> LoginAsync(System.String account, System.String password, IInvokeOption invokeOption = default)
{
if (Client == null)
{
throw new RpcException("IRpcClient为空,请先初始化或者进行赋值");
}
if (Client.TryCanInvoke?.Invoke(Client) == false)
{
throw new RpcException("Rpc无法执行。");
}
object[] parameters = new object[] { account, password };
return Client.InvokeAsync<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
{
if (client.TryCanInvoke?.Invoke(client) == false)
{
throw new RpcException("Rpc无法执行。");
}
object[] parameters = new object[] { account, password };
System.Boolean returnData = client.Invoke<System.Boolean>("Login", invokeOption, parameters);
return returnData;
}
///<summary>
///登录
///</summary>
public static Task<System.Boolean> LoginAsync<TClient>(this TClient client, System.String account, System.String password, IInvokeOption invokeOption = default) where TClient :
TouchSocket.Rpc.IRpcClient
{
if (client.TryCanInvoke?.Invoke(client) == false)
{
throw new RpcException("Rpc无法执行。");
}
object[] parameters = new object[] { account, password };
return client.InvokeAsync<System.Boolean>("Login", invokeOption, parameters);
}
}
}
使用代理扩展直接调用。
TcpTouchRpcClient client = new TcpTouchRpcClient();
client.Setup(new TouchSocketConfig()
.SetRemoteIPHost("127.0.0.1:7789")
.SetVerifyToken("TouchRpc"));
client.Connect();
bool result = client.Login(123, "abc");//Login是扩展方法。可能需要额外添加命名空间。
五、反向Rpc
一般的rpc服务都是客户端发起,服务器响应。但是有时候也需要服务器主动调用客户端,所以需要反向rpc。
5.1 定义、发布反向RPC服务
实际上,所有的Rpc客户端(TcpTouchRpcClient、UdpTouchRpc(不区分客户端)、HttpTouchRpcClient、WSTouchRpcClient)也实现了IRpcParser接口,这意味着反向RPC其实也是RPC,所以,所有操作一模一样。因为当客户端和服务器建立连接以后,就不再区分谁是客户端,谁是服务器了。只关心,谁能提供服务,谁在调用服务。
下列就以简单的示例下,由客户端声明服务,服务器调用服务。
具体步骤:
- 在客户端项目中定义服务
- 用TouchRpc标记
public class ReverseCallbackServer : RpcServer
{
[TouchRpc]
public string SayHello(string name)
{
return $"{name},hi";
}
}
【客户端发布服务】 发布服务,实际上是让TcpTouchRpcClient也拥有提供RPC的能力。
TcpTouchRpcClient client = new TcpTouchRpcClient();
client.Setup(new TouchSocketConfig()
.SetRemoteIPHost("127.0.0.1:7789")
.ConfigureContainer(a =>
{
a.AddConsoleLogger();
a.AddFileLogger();
})
.ConfigureRpcStore(a =>
{
a.RegisterServer<ReverseCallbackServer>();
})
.SetVerifyToken("TouchRpc"));
client.Connect();
5.2 调用反向RPC
服务器回调客户端,最终必须通过服务器辅助类客户端(ISocketClient的派生类),以TcpTouchRpcService为例,其辅助客户端为TcpTouchRpcSocketClient。
因为,TcpTouchRpcSocketClient已实现IRpcClient接口,意味着,反向RPC也可以使用代理调用。所有用法和RPC一致。
下列示例以TcpTouchRpcSocketClient为例,其余一致。
反向RPC也可以使用代理调用。所有用法和RPC一致。
5.2.1 通过服务器直接获取
可以获取所有终端。
foreach (var item in tcpTouchRpcService.GetClients())
{
client.Logger.Info(item.Invoke<string>("ReverseRpcConsoleApp.ReverseCallbackServer.SayHello".ToLower(), InvokeOption.WaitInvoke, "张三"));
}
也可以先筛选ID,然后再调用。
string id = tcpTouchRpcService.GetIDs().FirstOrDefault(a => a.Equals("特定id"));
if (tcpTouchRpcService.TryGetSocketClient(id, out var rpcSocketClient))
{
rpcSocketClient.Invoke<string>("ReverseRpcConsoleApp.ReverseCallbackServer.SayHello".ToLower(), InvokeOption.WaitInvoke, "张三");
}
5.2.2 通过调用上下文获取
具体步骤
- 设置调用上下文
- 上下文的Caller,即为服务器辅助类终端,进行强转即可。
六、客户端互Call RPC
除了正向RPC,反向RPC,TouchRpc还支持客户端之间互Call RPC。服务的定义与Rpc一样。
6.1 互Call RPC
客户端A调用客户端B的方法,需要知道对方的ID。和方法名。然后使用下列函数调用即可。
互Call RPC也支持调用上下文。
客户端互Call的时候,每个请求,都需要服务同意路由,才可以被转发。所以服务器需要做一些允许操作。
internal class MyTouchRpcPlugin : TouchRpcPluginBase
{
protected override void OnRouting(ITouchRpc client, PackageRouterEventArgs e)
{
if (e.RouterType== RouteType.Rpc)
{
e.IsPermitOperation = true;
}
base.OnRouting(client, e);
}
}