跳到主要内容

.NET/C# gRPC 开发

.NET 平台对 gRPC 有原生支持,Microsoft 提供了完整的 gRPC 库和工具链。本章介绍如何使用 .NET 开发 gRPC 服务端和客户端。

环境准备

系统要求

  • .NET 6.0 或更高版本(推荐 .NET 8.0)
  • Visual Studio 2022 或 VS Code + C# 扩展

创建项目

使用 .NET CLI 快速创建 gRPC 项目:

# 创建服务端项目
dotnet new grpc -n GrpcService

# 创建客户端项目
dotnet new console -n GrpcClient

# 添加客户端 gRPC 依赖
cd GrpcClient
dotnet add package Grpc.Net.Client
dotnet add package Google.Protobuf
dotnet add package Grpc.Tools

项目结构

GrpcService/
├── Protos/ # Proto 定义文件
│ └── greet.proto
├── Services/ # 服务实现
│ └── GreeterService.cs
├── appsettings.json
├── Program.cs
└── GrpcService.csproj

GrpcClient/
├── Protos/ # Proto 定义(共享)
│ └── greet.proto
├── Program.cs
└── GrpcClient.csproj

项目配置

服务端项目文件GrpcService.csproj):

<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<!-- gRPC 核心库 -->
<PackageReference Include="Grpc.AspNetCore" Version="2.60.0" />
</ItemGroup>

<!-- Proto 文件配置 -->
<ItemGroup>
<Protobuf Include="Protos\**\*.proto" GrpcServices="Server" />
</ItemGroup>
</Project>

客户端项目文件GrpcClient.csproj):

<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<OutputType>Exe</OutputType>
<TargetFramework>net8.0</TargetFramework>
<Nullable>enable</Nullable>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Google.Protobuf" Version="3.25.1" />
<PackageReference Include="Grpc.Net.Client" Version="2.60.0" />
<PackageReference Include="Grpc.Tools" Version="2.60.0">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers</IncludeAssets>
</PackageReference>
</ItemGroup>

<!-- 客户端生成 -->
<ItemGroup>
<Protobuf Include="Protos\**\*.proto" GrpcServices="Client" />
</ItemGroup>
</Project>

Proto 定义

// Protos/greet.proto
syntax = "proto3";

package greet;

// C# 命名空间
option csharp_namespace = "GrpcService.Protos";

// 问候服务
service Greeter {
// 一元 RPC
rpc SayHello (HelloRequest) returns (HelloReply);

// 服务端流
rpc StreamGreetings (HelloRequest) returns (stream HelloReply);

// 客户端流
rpc SendGreetings (stream HelloRequest) returns (HelloReply);

// 双向流
rpc Chat (stream HelloRequest) returns (stream HelloReply);
}

message HelloRequest {
string name = 1;
int32 count = 2;
}

message HelloReply {
string message = 1;
int64 timestamp = 2;
}

服务端开发

服务入口

// Program.cs
using GrpcService.Services;

var builder = WebApplication.CreateBuilder(args);

// 添加 gRPC 服务
builder.Services.AddGrpc(options =>
{
// 配置选项
options.EnableDetailedErrors = true; // 开发环境启用详细错误
options.MaxReceiveMessageSize = 10 * 1024 * 1024; // 10MB
options.MaxSendMessageSize = 10 * 1024 * 1024; // 10MB
});

var app = builder.Build();

// 映射 gRPC 服务
app.MapGrpcService<GreeterService>();

// 健康检查端点(可选)
app.MapGet("/", () => "Communication with gRPC endpoints must be made through a gRPC client.");

app.Run();

服务实现

// Services/GreeterService.cs
using Grpc.Core;
using GrpcService.Protos;

namespace GrpcService.Services;

/// <summary>
/// Greeter 服务实现
/// </summary>
public class GreeterService : Greeter.GreeterBase
{
private readonly ILogger<GreeterService> _logger;

public GreeterService(ILogger<GreeterService> logger)
{
_logger = logger;
}

/// <summary>
/// 一元 RPC:简单请求-响应
/// </summary>
public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)
{
_logger.LogInformation("收到请求: name={Name}", request.Name);

return Task.FromResult(new HelloReply
{
Message = $"你好, {request.Name}!",
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
});
}

/// <summary>
/// 服务端流:返回多个响应
/// </summary>
public override async Task StreamGreetings(HelloRequest request,
IServerStreamWriter<HelloReply> responseStream,
ServerCallContext context)
{
_logger.LogInformation("收到流请求: name={Name}, count={Count}",
request.Name, request.Count);

int count = request.Count > 0 ? request.Count : 5;

for (int i = 0; i < count; i++)
{
// 检查客户端是否已取消
if (context.CancellationToken.IsCancellationRequested)
{
_logger.LogInformation("客户端已取消请求");
break;
}

await responseStream.WriteAsync(new HelloReply
{
Message = $"问候 {i + 1}: 你好, {request.Name}!",
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
});

await Task.Delay(500); // 模拟处理延迟
}
}

/// <summary>
/// 客户端流:接收多个请求,返回单个响应
/// </summary>
public override async Task<HelloReply> SendGreetings(
IAsyncStreamReader<HelloRequest> requestStream,
ServerCallContext context)
{
var names = new List<string>();

// 读取所有请求
await foreach (var request in requestStream.ReadAllAsync())
{
_logger.LogInformation("收到: name={Name}", request.Name);
names.Add(request.Name);
}

return new HelloReply
{
Message = $"收到了 {names.Count} 个问候,来自: {string.Join(", ", names)}",
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
};
}

/// <summary>
/// 双向流:实时聊天
/// </summary>
public override async Task Chat(
IAsyncStreamReader<HelloRequest> requestStream,
IServerStreamWriter<HelloReply> responseStream,
ServerCallContext context)
{
await foreach (var request in requestStream.ReadAllAsync())
{
_logger.LogInformation("聊天消息: {Message}", request.Name);

await responseStream.WriteAsync(new HelloReply
{
Message = $"[服务器] 收到你的消息: {request.Name}",
Timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds()
});
}
}
}

依赖注入

gRPC 服务支持依赖注入:

// 注册服务
builder.Services.AddScoped<IUserService, UserService>();
builder.Services.AddSingleton<ICacheService, CacheService>();

// 服务中使用
public class GreeterService : Greeter.GreeterBase
{
private readonly IUserService _userService;
private readonly ILogger<GreeterService> _logger;

public GreeterService(IUserService userService, ILogger<GreeterService> logger)
{
_userService = userService;
_logger = logger;
}

public override async Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)
{
var user = await _userService.GetUserAsync(request.Name);
// ...
}
}

拦截器

// Interceptors/LoggingInterceptor.cs
using Grpc.Core;
using Grpc.Core.Interceptors;

public class LoggingInterceptor : Interceptor
{
private readonly ILogger<LoggingInterceptor> _logger;

public LoggingInterceptor(ILogger<LoggingInterceptor> logger)
{
_logger = logger;
}

public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(
TRequest request,
ServerCallContext context,
UnaryServerMethod<TRequest, TResponse> continuation)
{
var startTime = DateTime.UtcNow;

_logger.LogInformation("[请求] 方法: {Method}", context.Method);

try
{
var response = await continuation(request, context);

_logger.LogInformation("[响应] 方法: {Method}, 耗时: {Duration}ms",
context.Method, (DateTime.UtcNow - startTime).TotalMilliseconds);

return response;
}
catch (Exception ex)
{
_logger.LogError(ex, "[错误] 方法: {Method}", context.Method);
throw;
}
}
}

// 注册拦截器
builder.Services.AddGrpc(options =>
{
options.Interceptors.Add<LoggingInterceptor>();
});

全局异常处理

// Interceptors/ExceptionInterceptor.cs
using Grpc.Core;
using Grpc.Core.Interceptors;
using System.Net;

public class ExceptionInterceptor : Interceptor
{
private readonly ILogger<ExceptionInterceptor> _logger;

public ExceptionInterceptor(ILogger<ExceptionInterceptor> logger)
{
_logger = logger;
}

public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(
TRequest request,
ServerCallContext context,
UnaryServerMethod<TRequest, TResponse> continuation)
{
try
{
return await continuation(request, context);
}
catch (ValidationException ex)
{
throw new RpcException(new Status(StatusCode.InvalidArgument, ex.Message));
}
catch (NotFoundException ex)
{
throw new RpcException(new Status(StatusCode.NotFound, ex.Message));
}
catch (UnauthorizedException ex)
{
throw new RpcException(new Status(StatusCode.PermissionDenied, ex.Message));
}
catch (Exception ex)
{
_logger.LogError(ex, "未处理的异常");
throw new RpcException(new Status(StatusCode.Internal, "服务器内部错误"));
}
}
}

客户端开发

基本客户端

// Program.cs
using Grpc.Net.Client;
using GrpcService.Protos;

// 创建连接
using var channel = GrpcChannel.ForAddress("https://localhost:5001");

// 创建客户端
var client = new Greeter.GreeterClient(channel);

// 一元 RPC 调用
await CallSayHello(client, "张三");

// 服务端流调用
await CallStreamGreetings(client, "李四", 3);

// 客户端流调用
await CallSendGreetings(client, new[] { "王五", "赵六", "钱七" });

// 双向流调用
await CallChat(client, new[] { "你好", "今天天气不错", "再见" });

/// <summary>
/// 一元 RPC 调用
/// </summary>
static async Task CallSayHello(Greeter.GreeterClient client, string name)
{
Console.WriteLine("\n=== 一元 RPC ===");

var request = new HelloRequest { Name = name };

var reply = await client.SayHelloAsync(request);

Console.WriteLine($"响应: {reply.Message}");
}

/// <summary>
/// 服务端流调用
/// </summary>
static async Task CallStreamGreetings(Greeter.GreeterClient client, string name, int count)
{
Console.WriteLine("\n=== 服务端流 ===");

var request = new HelloRequest { Name = name, Count = count };

using var call = client.StreamGreetings(request);

await foreach (var reply in call.ResponseStream.ReadAllAsync())
{
Console.WriteLine($"收到: {reply.Message}");
}
}

/// <summary>
/// 客户端流调用
/// </summary>
static async Task CallSendGreetings(Greeter.GreeterClient client, string[] names)
{
Console.WriteLine("\n=== 客户端流 ===");

using var call = client.SendGreetings();

foreach (var name in names)
{
Console.WriteLine($"发送: {name}");
await call.RequestStream.WriteAsync(new HelloRequest { Name = name });
}

await call.RequestStream.CompleteAsync();

var reply = await call.ResponseAsync;
Console.WriteLine($"响应: {reply.Message}");
}

/// <summary>
/// 双向流调用
/// </summary>
static async Task CallChat(Greeter.GreeterClient client, string[] messages)
{
Console.WriteLine("\n=== 双向流 ===");

using var call = client.Chat();

// 读取响应的任务
var readTask = Task.Run(async () =>
{
await foreach (var reply in call.ResponseStream.ReadAllAsync())
{
Console.WriteLine($"收到: {reply.Message}");
}
});

// 发送消息
foreach (var msg in messages)
{
Console.WriteLine($"发送: {msg}");
await call.RequestStream.WriteAsync(new HelloRequest { Name = msg });
}

await call.RequestStream.CompleteAsync();
await readTask;
}

带超时的调用

// 设置截止时间
var reply = await client.SayHelloAsync(new HelloRequest { Name = "Test" },
deadline: DateTime.UtcNow.AddSeconds(5));

// 或使用 CancellationToken
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
var reply = await client.SayHelloAsync(new HelloRequest { Name = "Test" },
cancellationToken: cts.Token);

带元数据的调用

// 创建元数据
var headers = new Metadata
{
{ "authorization", "Bearer my-token" },
{ "x-request-id", "req-123" }
};

// 发送请求
var reply = await client.SayHelloAsync(
new HelloRequest { Name = "Test" },
headers);

// 获取响应元数据
var responseHeaders = await call.ResponseHeadersAsync;
var responseTime = responseHeaders.GetValue("x-response-time");

客户端工厂

.NET 支持使用工厂模式创建 gRPC 客户端:

// 注册客户端
builder.Services.AddGrpcClient<Greeter.GreeterClient>(options =>
{
options.Address = new Uri("https://localhost:5001");
});

// 配置拦截器
builder.Services.AddGrpcClient<Greeter.GreeterClient>(options =>
{
options.Address = new Uri("https://localhost:5001");
})
.AddInterceptor<ClientLoggingInterceptor>()
.ConfigureChannel(o =>
{
o.Credentials = ChannelCredentials.Insecure;
o.MaxReceiveMessageSize = 10 * 1024 * 1024;
});

// 使用
public class MyService
{
private readonly Greeter.GreeterClient _client;

public MyService(Greeter.GreeterClient client)
{
_client = client;
}
}

重试策略

// 配置重试
builder.Services.AddGrpcClient<Greeter.GreeterClient>(options =>
{
options.Address = new Uri("https://localhost:5001");
})
.ConfigureChannel(o =>
{
var methodConfig = new MethodConfig
{
Names = { MethodName.Default },
RetryPolicy = new RetryPolicy
{
MaxAttempts = 3,
InitialBackoff = TimeSpan.FromSeconds(1),
MaxBackoff = TimeSpan.FromSeconds(5),
BackoffMultiplier = 2,
RetryableStatusCodes = { StatusCode.Unavailable }
}
};
o.ServiceConfig = new ServiceConfig { MethodConfigs = { methodConfig } };
});

错误处理

服务端返回错误

public override Task<HelloReply> SayHello(HelloRequest request, ServerCallContext context)
{
// 参数验证
if (string.IsNullOrEmpty(request.Name))
{
throw new RpcException(new Status(StatusCode.InvalidArgument, "name 不能为空"));
}

// 业务错误
if (request.Name == "error")
{
throw new RpcException(new Status(StatusCode.Internal, "内部处理错误"));
}

return Task.FromResult(new HelloReply { Message = $"你好, {request.Name}!" });
}

客户端处理错误

try
{
var reply = await client.SayHelloAsync(new HelloRequest { Name = "error" });
Console.WriteLine($"响应: {reply.Message}");
}
catch (RpcException ex) when (ex.StatusCode == StatusCode.InvalidArgument)
{
Console.WriteLine($"参数错误: {ex.Status.Detail}");
}
catch (RpcException ex) when (ex.StatusCode == StatusCode.NotFound)
{
Console.WriteLine($"资源不存在: {ex.Status.Detail}");
}
catch (RpcException ex) when (ex.StatusCode == StatusCode.Unauthenticated)
{
Console.WriteLine($"未认证: {ex.Status.Detail}");
}
catch (RpcException ex)
{
Console.WriteLine($"错误: {ex.StatusCode} - {ex.Status.Detail}");
}

TLS 安全连接

服务端 TLS

appsettings.json 中配置 Kestrel:

{
"Kestrel": {
"Endpoints": {
"Https": {
"Url": "https://localhost:5001",
"Certificate": {
"Path": "server.pfx",
"Password": "your-password"
}
}
}
}
}

客户端 TLS

// 默认使用 HTTPS(需要有效证书)
var channel = GrpcChannel.ForAddress("https://localhost:5001");

// 跳过证书验证(仅开发环境)
var httpHandler = new HttpClientHandler
{
ServerCertificateCustomValidationCallback =
HttpClientHandler.DangerousAcceptAnyServerCertificateValidator
};

var channel = GrpcChannel.ForAddress("https://localhost:5001", new GrpcChannelOptions
{
HttpHandler = httpHandler
});

性能优化

连接池

gRPC 客户端应复用连接:

// 推荐:使用依赖注入复用连接
builder.Services.AddGrpcClient<Greeter.GreeterClient>(...);

// 或使用单例
public class GrpcClientFactory
{
private static readonly Lazy<GrpcChannel> _channel = new(() =>
GrpcChannel.ForAddress("https://localhost:5001"));

public static Greeter.GreeterClient CreateClient()
{
return new Greeter.GreeterClient(_channel.Value);
}
}

配置优化

builder.Services.AddGrpc(options =>
{
options.MaxReceiveMessageSize = 10 * 1024 * 1024; // 10MB
options.MaxSendMessageSize = 10 * 1024 * 1024; // 10MB
options.EnableDetailedErrors = true;

// 压缩配置
options.ResponseCompressionLevel = System.IO.Compression.CompressionLevel.Optimal;
});

// 客户端配置
var channel = GrpcChannel.ForAddress("https://localhost:5001", new GrpcChannelOptions
{
MaxReceiveMessageSize = 10 * 1024 * 1024,
MaxSendMessageSize = 10 * 1024 * 1024,

// 连接池
MaxConcurrentStreams = 100,

// Keep-alive
ChannelOptions = new ChannelOptions
{
// ...
}
});

异步流处理

// 使用 ReadAllAsync 高效处理流
await foreach (var item in call.ResponseStream.ReadAllAsync())
{
// 处理每个项目
}

// 使用 WriteAsync 发送流
await call.RequestStream.WriteAsync(request);

健康检查

// 添加健康检查
builder.Services.AddGrpcHealthChecks()
.AddCheck("self", () => HealthCheckResult.Healthy());

// 映射健康检查端点
app.MapGrpcService<HealthCheckService>();

测试

单元测试

using Grpc.Core.Testing;
using Moq;
using Xunit;

public class GreeterServiceTests
{
[Fact]
public async Task SayHello_ReturnsExpectedGreeting()
{
// 安排
var service = new GreeterService(Mock.Of<ILogger<GreeterService>>());
var context = TestServerCallContext.Create(
method: nameof(Greeter.SayHello),
host: "localhost",
deadline: DateTime.UtcNow.AddMinutes(1),
requestHeaders: new Metadata(),
cancellationToken: CancellationToken.None,
peer: "ipv4:127.0.0.1",
authContext: null,
contextPropagationToken: null,
writeHeadersFunc: _ => Task.CompletedTask,
writeOptionsGetter: () => new WriteOptions(),
writeOptionsSetter: _ => { });

var request = new HelloRequest { Name = "Test" };

// 执行
var response = await service.SayHello(request, context);

// 断言
Assert.Equal("你好, Test!", response.Message);
}
}

最佳实践

1. 连接复用

// 推荐:使用工厂模式或单例
builder.Services.AddGrpcClient<Greeter.GreeterClient>(...);

// 不推荐:每次调用创建新连接
using var channel = GrpcChannel.ForAddress(...); // 避免这样

2. 资源释放

// 使用 using 或确保释放
using var channel = GrpcChannel.ForAddress(...);

// 长期运行的连接应该在应用关闭时释放
public class GrpcClientManager : IAsyncDisposable
{
private readonly GrpcChannel _channel;

public GrpcClientManager(string address)
{
_channel = GrpcChannel.ForAddress(address);
}

public Greeter.GreeterClient CreateClient() => new(_channel);

public async ValueTask DisposeAsync()
{
await _channel.ShutdownAsync();
}
}

3. 超时设置

// 始终设置超时
var reply = await client.SayHelloAsync(request,
deadline: DateTime.UtcNow.AddSeconds(30));

4. 日志记录

// 使用拦截器记录日志
builder.Services.AddGrpc(options =>
{
options.Interceptors.Add<LoggingInterceptor>();
});

与 ASP.NET Core 集成

gRPC 服务可以与 ASP.NET Core 功能集成:

// JWT 认证
builder.Services.AddAuthentication()
.AddJwtBearer(...);

builder.Services.AddGrpc(options =>
{
options.Interceptors.Add<AuthInterceptor>();
});

// CORS(用于 gRPC-Web)
builder.Services.AddCors(options =>
{
options.AddPolicy("GrpcWeb", builder =>
{
builder.WithOrigins("*")
.AllowAnyMethod()
.AllowAnyHeader()
.WithExposedHeaders("grpc-status", "grpc-message");
});
});

// gRPC-Web 支持
builder.Services.AddGrpcWeb(o => o.GrpcWebEnabled = true);

app.UseGrpcWeb();
app.MapGrpcService<GreeterService>().EnableGrpcWeb();

小结

本章介绍了 .NET/C# gRPC 开发的核心内容:

  1. 项目创建:使用 CLI 快速创建 gRPC 项目
  2. 服务端开发:服务实现、依赖注入、拦截器
  3. 客户端开发:同步调用、流式调用、工厂模式
  4. 错误处理:标准错误码和异常处理
  5. 安全连接:TLS 配置
  6. 性能优化:连接复用、配置调优
  7. 测试:单元测试方法
  8. 集成:与 ASP.NET Core 功能集成

.NET 的 gRPC 支持非常完善,与 ASP.NET Core 深度集成,是企业级微服务开发的好选择。

[!TIP] .NET 8.0 对 gRPC 性能进行了显著优化,包括 AOT 支持和更好的连接管理。建议使用最新版本获得最佳性能。