---
id: createandcallrpc
title: 创建rpc服务
---

## 一、说明

RPC（Remote Procedure Call）远程过程调用协议，一种通过网络从远程计算机上请求服务，而不需要了解底层网络技术的协议。RPC它假定某些协议的存在，例如TPC/UDP等，为通信程序之间携带信息数据。在OSI网络七层模型中，RPC跨越了传输层和应用层，RPC使得开发，包括网络分布式多程序在内的应用程序更加容易。

过程是什么？ 过程就是业务处理、计算任务，更直白的说，就是程序，就是想调用本地方法一样调用远程的过程。

TouchRpc支持服务器与客户端互相调用，也支持客户端之间相互调用。

## 二、定义服务

1. 在**被调用**端中新建一个类名为**MyRpcServer**。
2. 继承于RpcServer类、或实现IRpcServer。亦或者将服务器声明为**瞬时生命**的服务，继承TransientRpcServer、或ITransientRpcServer。
3. 在该类中写**公共方法**，并用**TouchRpc**属性标签标记。

```csharp
public class MyRpcServer : RpcServer
{
    [Description("登录")]//服务描述，在生成代理时，会变成注释。
    [TouchRpc("Login")]//服务注册的函数键，此处为显式指定。默认不传参的时候，为该函数类全名+方法名的全小写。
    public bool Login(string account,string password)
    {
        if (account=="123"&&password=="abc")
        {
            return true;
        }

        return false;
    }
}
```


```csharp
public class MyRpcServer : TransientRpcServer
{
    [Description("登录")]//服务描述，在生成代理时，会变成注释。
    [TouchRpc("Login")]//服务注册的函数键，此处为显式指定。默认不传参的时候，为该函数类全名+方法名的全小写。
    public bool Login(string account,string password)
    {
        if (account=="123"&&password=="abc")
        {
            return true;
        }

        return false;
    }
}
```

:::tip 提示

**瞬时生命**的服务，最大的特点就是，每个请求，都会创建一个新的服务类对象。然后可以通过**this.CallContext**直接访问当前的调用上下文。

:::  


## 三、启动Rpc服务器

以下仅示例基于Tcp协议TouchRpc。其他协议的服务器请看[创建TouchRpc服务器](./createtouchrpcservice.mdx)

```csharp
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为例，其他客户端一模一样。

```csharp
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、代理调用

代理调用的便捷在于，客户端不用再知道哪些服务可调，也不用再纠结调用的参数类型正不正确，因为这些，代理工具都会替你做好。

详细步骤：

1. [生成代理文件](./generateproxydescription.mdx)
2. 将生成的cs文件添加到调用端一起编译。

:::info 备注

以上示例，会生成下列代理代码。

:::  
【生成的代理】

```csharp
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);
        }
    }
}

```

使用代理扩展直接调用。

```csharp
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**，所以，所有操作一模一样。因为当客户端和服务器建立连接以后，就不再区分谁是客户端，谁是服务器了。只关心，**谁能提供服务，谁在调用服务**。

下列就以简单的示例下，由客户端声明服务，服务器调用服务。

具体步骤：

1. 在**客户端项目**中定义服务
2. 用**TouchRpc**标记

```csharp
public class ReverseCallbackServer : RpcServer
{
    [TouchRpc]
    public string SayHello(string name)
    {
        return $"{name},hi";
    }
}
```

**【客户端发布服务】**
发布服务，实际上是让TcpTouchRpcClient也拥有提供RPC的能力。

```csharp
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为例，其余一致。

:::tip 提示

反向RPC也可以使用代理调用。所有用法和RPC一致。

:::  

### 5.2.1 通过服务器直接获取

可以获取所有终端。

```csharp
foreach (var item in tcpTouchRpcService.GetClients())
{
    client.Logger.Info(item.Invoke<string>("ReverseRpcConsoleApp.ReverseCallbackServer.SayHello".ToLower(), InvokeOption.WaitInvoke, "张三"));
}
```

也可以先筛选ID，然后再调用。

```csharp
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 通过调用上下文获取

具体步骤

1. 设置调用上下文
2. 上下文的Caller，即为服务器辅助类终端，进行强转即可。


## 六、客户端互Call RPC

除了正向RPC，反向RPC，TouchRpc还支持**客户端**之间互Call RPC。服务的定义与Rpc一样。

### 6.1 互Call RPC

客户端A调用客户端B的方法，需要知道对方的**ID**。和**方法名**。然后使用下列函数调用即可。

:::tip 提示

互Call RPC也支持调用上下文。

:::  

:::caution 服务器注意

客户端互Call的时候，每个请求，都需要服务同意路由，才可以被转发。所以服务器需要做一些允许操作。

:::  


```csharp
internal class MyTouchRpcPlugin : TouchRpcPluginBase
{
    protected override void OnRouting(ITouchRpc client, PackageRouterEventArgs e)
    {
        if (e.RouterType== RouteType.Rpc)
        {
            e.IsPermitOperation = true;
        }
        base.OnRouting(client, e);
    }
}
```

