跳到主要内容
版本:1.5.0

gRPC 支持

Fory 可以为定义了服务的 schema 生成 C# gRPC 配套代码。生成的代码使用标准 gRPC 客户端、服务基类、 方法描述符、元数据、截止时间、取消机制和状态码,而请求和响应对象使用 Fory 而非 protobuf 进行序列化。

当 RPC 两端均由同一份 Fory IDL、protobuf IDL 或 FlatBuffers IDL 生成,并且双方都需要 Fory 编码的消息体时,请使用此模式。对于必须由通用 protobuf 客户端、反射工具或需要 protobuf 消息字节的组件使用的 API,请采用标准的 protobuf gRPC 代码生成方式。

添加依赖​

Apache.Fory 包不会引入 gRPC 依赖。请在编译或运行生成的服务配套代码的应用中添加 gRPC 包。

服务端项目:

<ItemGroup>
<PackageReference Include="Apache.Fory" Version="1.5.0" />
<PackageReference Include="Grpc.AspNetCore" Version="2.71.0" />
</ItemGroup>

客户端项目:

<ItemGroup>
<PackageReference Include="Apache.Fory" Version="1.5.0" />
<PackageReference Include="Grpc.Core.Api" Version="2.71.0" />
<PackageReference Include="Grpc.Net.Client" Version="2.71.0" />
</ItemGroup>

Grpc.Core.Api 是生成的配套代码所使用的 API 接口。服务端和客户端应用可以照常选择所需的 gRPC 托管包或传输包。

定义服务​

服务定义可以来自 Fory IDL、protobuf IDL 或 FlatBuffers 的 rpc_service 定义。Fory IDL 服务如下所示:

package demo.greeter;
option csharp_namespace = "Demo.Greeter";

message HelloRequest {
string name = 1;
}

message HelloReply {
string reply = 1;
}

service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
}

使用 --grpc 生成 C# 模型和 gRPC 配套代码:

foryc service.fdl --csharp_out=./Generated --grpc

对于该 schema,C# 生成器会输出:

文件用途
Demo/Greeter/Service.csFory 模型类型和 schema 模块
Demo/Greeter/GreeterGrpc.csgRPC 服务基类、客户端和描述符
Service.cs 中的 ServiceForyModule生成类型的 Fory 注册模块
GreeterGrpc.cs 中的 Greeter.GreeterBase服务端实现的基类
GreeterGrpc.cs 中的 Greeter.GreeterClient用于发起 gRPC 调用的客户端存根

实现服务端​

继承生成的 Greeter.GreeterBase 类,并通过标准的 ASP.NET Core gRPC 托管方式映射该服务:

using System.Threading.Tasks;
using Demo.Greeter;
using Grpc.Core;

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddGrpc();

var app = builder.Build();
app.MapGrpcService<GreeterService>();
app.Run();

public sealed class GreeterService : Greeter.GreeterBase
{
public override Task<HelloReply> SayHello(
HelloRequest request,
ServerCallContext context)
{
return Task.FromResult(new HelloReply
{
Reply = "Hello, " + request.Name,
});
}
}

生成的服务配套代码所使用的 schema 模块会注册生成的请求和响应类型,因此服务实现无需进行自定义 序列化器注册。

创建客户端​

通过 Grpc.Net.Client 调用器使用生成的客户端:

using Demo.Greeter;
using Grpc.Net.Client;

using GrpcChannel channel = GrpcChannel.ForAddress("https://localhost:5001");
var client = new Greeter.GreeterClient(channel.CreateCallInvoker());

HelloReply reply = await client.SayHelloAsync(
new HelloRequest { Name = "Fory" });
Console.WriteLine(reply.Reply);

生成的客户端还提供同步一元方法和标准的 gRPC 流式调用形式。

流式 RPC​

Fory 服务定义可以使用与 gRPC 相同的流式调用形式:

service Greeter {
rpc SayHello (HelloRequest) returns (HelloReply);
rpc LotsOfReplies (HelloRequest) returns (stream HelloReply);
rpc LotsOfGreetings (stream HelloRequest) returns (HelloReply);
rpc Chat (stream HelloRequest) returns (stream HelloReply);
}

生成的 C# 服务方法遵循 gRPC C# 约定:

IDL 形式服务端方法客户端方法
rpc A (Req) returns (Res)Task<Res> A(Req request, ServerCallContext context)Res A(...) 和 AsyncUnaryCall<Res> AAsync(...)
rpc A (Req) returns (stream Res)Task A(Req request, IServerStreamWriter<Res> responseStream, ...)AsyncServerStreamingCall<Res> A(...)
rpc A (stream Req) returns (Res)Task<Res> A(IAsyncStreamReader<Req> requestStream, ...)AsyncClientStreamingCall<Req, Res> A(...)
rpc A (stream Req) returns (stream Res)Task A(IAsyncStreamReader<Req> requestStream, IServerStreamWriter<Res> ...)AsyncDuplexStreamingCall<Req, Res> A(...)

服务端实现可以直接使用生成的流式方法形式:

using System.Collections.Generic;
using System.Threading.Tasks;
using Demo.Greeter;
using Grpc.Core;

public sealed class GreeterService : Greeter.GreeterBase
{
public override async Task LotsOfReplies(
HelloRequest request,
IServerStreamWriter<HelloReply> responseStream,
ServerCallContext context)
{
foreach (string reply in new[]
{
"Hello, " + request.Name,
"Welcome, " + request.Name,
})
{
await responseStream.WriteAsync(new HelloReply { Reply = reply });
}
}

public override async Task<HelloReply> LotsOfGreetings(
IAsyncStreamReader<HelloRequest> requestStream,
ServerCallContext context)
{
List<string> names = new();
while (await requestStream.MoveNext(context.CancellationToken))
{
names.Add(requestStream.Current.Name);
}

return new HelloReply { Reply = string.Join(", ", names) };
}

public override async Task Chat(
IAsyncStreamReader<HelloRequest> requestStream,
IServerStreamWriter<HelloReply> responseStream,
ServerCallContext context)
{
while (await requestStream.MoveNext(context.CancellationToken))
{
await responseStream.WriteAsync(new HelloReply
{
Reply = "Hello, " + requestStream.Current.Name,
});
}
}
}

生成的客户端返回标准的 gRPC 流式调用对象:

using System;
using System.Threading;
using System.Threading.Tasks;
using Demo.Greeter;
using Grpc.Core;

using AsyncServerStreamingCall<HelloReply> replies =
client.LotsOfReplies(new HelloRequest { Name = "Fory" });
while (await replies.ResponseStream.MoveNext(CancellationToken.None))
{
Console.WriteLine(replies.ResponseStream.Current.Reply);
}

using AsyncClientStreamingCall<HelloRequest, HelloReply> greetings =
client.LotsOfGreetings();
await greetings.RequestStream.WriteAsync(new HelloRequest { Name = "Ada" });
await greetings.RequestStream.WriteAsync(new HelloRequest { Name = "Grace" });
await greetings.RequestStream.CompleteAsync();
HelloReply summary = await greetings.ResponseAsync;
Console.WriteLine(summary.Reply);

using AsyncDuplexStreamingCall<HelloRequest, HelloReply> chat = client.Chat();
Task readTask = Task.Run(async () =>
{
while (await chat.ResponseStream.MoveNext(CancellationToken.None))
{
Console.WriteLine(chat.ResponseStream.Current.Reply);
}
});
await chat.RequestStream.WriteAsync(new HelloRequest { Name = "Fory" });
await chat.RequestStream.CompleteAsync();
await readTask;

生成的描述符会为 gRPC 路径保留 IDL 中服务和方法的确切名称。

生成的模块名称​

C# schema 模块名来自源文件名(不含扩展名),而不是来自 csharp_namespace,也不是来自 gRPC 服务名称。

例如:

Schema 输入模型文件Schema 模块
service.fdlService.csServiceForyModule
order-events.fdlOrderEvents.csOrderEventsForyModule
greeter.fdlGreeter.csGreeterForyModule
Greeter.fdlGreeter.csGreeterForyModule

名为 Greeter 的 gRPC 服务仍会生成服务配套文件 GreeterGrpc.cs,不会改变 schema 模块名。 这样,多个 schema 文件可以指向同一个 C# 命名空间而不会发生冲突。生成器不会生成基于命名空间或 服务名称派生的模块别名。

gRPC 运行时行为​

生成的服务代码仅替换请求和响应的序列化方式。所有标准的 gRPC 运维功能仍由 gRPC 技术栈负责:

  • 截止时间和取消
  • TLS 和身份验证
  • 名称解析和负载均衡
  • 客户端和服务端拦截器
  • 状态码和元数据
  • 通道池和生命周期管理

故障排查​

缺少 Grpc.Core 类型​

添加 Grpc.Core.Api,或添加会以传递依赖方式引入它的服务端或客户端包。生成的 Fory 服务文件 会导入 gRPC API,但 Apache.Fory 刻意不依赖 gRPC。

Protobuf 客户端无法解码服务​

Fory gRPC 配套代码不会对消息使用 protobuf 编码格式。请为 Fory 生成的服务使用 Fory 生成的客户端, 或为通用 protobuf 客户端提供单独的 protobuf 服务端点。