.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 开发的核心内容:
- 项目创建:使用 CLI 快速创建 gRPC 项目
- 服务端开发:服务实现、依赖注入、拦截器
- 客户端开发:同步调用、流式调用、工厂模式
- 错误处理:标准错误码和异常处理
- 安全连接:TLS 配置
- 性能优化:连接复用、配置调优
- 测试:单元测试方法
- 集成:与 ASP.NET Core 功能集成
.NET 的 gRPC 支持非常完善,与 ASP.NET Core 深度集成,是企业级微服务开发的好选择。
[!TIP] .NET 8.0 对 gRPC 性能进行了显著优化,包括 AOT 支持和更好的连接管理。建议使用最新版本获得最佳性能。