在C#生态中,gRPC是基于HTTP/2的高性能远程过程调用框架,适合微服务之间、前后端之间的高效通信场景。本文将以ASP.NET Core为宿主环境,讲解从零搭建gRPC服务到实现复杂通信的完整流程。

环境准备
首先需要安装.NET 6及以上版本的SDK,同时Visual Studio需要安装ASP.NET和Web开发工作负载。gRPC服务依赖Grpc.AspNetCore NuGet包,后续会自动通过项目模板引入。
创建gRPC服务项目
打开Visual Studio,选择创建新项目,搜索并选择gRPC服务模板,项目名称设置为GrpcDemo.Server,点击创建即可生成基础项目结构。项目默认包含以下核心文件:
- Protos目录:存放proto接口定义文件
- Services目录:存放gRPC服务实现类
- Program.cs:服务启动配置入口
定义proto接口文件
proto文件是gRPC的核心,用于定义服务接口和消息结构,默认使用protobuf语法。我们在Protos目录下新建user.proto文件,内容如下:
syntax = "proto3";
option csharp_namespace = "GrpcDemo.Server.Protos";
package user;
// 定义用户服务
service User {
// 获取用户信息接口
rpc GetUser (GetUserRequest) returns (GetUserResponse);
// 批量获取用户接口(服务端流)
rpc GetUserList (GetUserListRequest) returns (stream GetUserResponse);
// 创建用户接口(客户端流)
rpc CreateUser (stream CreateUserRequest) returns (CreateUserResponse);
}
// 获取用户请求参数
message GetUserRequest {
int32 user_id = 1;
}
// 获取用户响应结果
message GetUserResponse {
int32 id = 1;
string name = 2;
string email = 3;
int32 age = 4;
}
// 批量获取用户请求参数
message GetUserListRequest {
int32 page = 1;
int32 page_size = 2;
}
// 创建用户请求参数
message CreateUserRequest {
string name = 1;
string email = 2;
int32 age = 3;
}
// 创建用户响应结果
message CreateUserResponse {
bool success = 1;
string message = 2;
int32 user_id = 3;
}
之后需要在项目文件GrpcDemo.Server.csproj中添加proto文件的引用配置,确保编译时生成对应的C#代码:
<ItemGroup> <Protobuf Include="Protosuser.proto" GrpcServices="Server" /> </ItemGroup>
实现gRPC服务端逻辑
在Services目录下新建UserService类,继承proto文件生成的User.UserBase基类,实现定义的接口方法:
using Grpc.Core;
using GrpcDemo.Server.Protos;
namespace GrpcDemo.Server.Services;
// 用户服务实现类
public class UserService : User.UserBase
{
// 模拟用户数据存储
private static readonly List<UserModel> _users = new()
{
new UserModel { Id = 1, Name = "张三", Email = "zhangsan@ipipp.com", Age = 25 },
new UserModel = 2, Name = "李四", Email = "lisi@ipipp.com", Age = 28 },
new UserModel { Id = 3, Name = "王五", Email = "wangwu@ipipp.com", Age = 22 }
};
// 实现获取单个用户接口
public override Task<GetUserResponse> GetUser(GetUserRequest request, ServerCallContext context)
{
var user = _users.FirstOrDefault(u => u.Id == request.UserId);
if (user == null)
{
// 抛出gRPC状态异常
throw new RpcException(new Status(StatusCode.NotFound, $"用户ID {request.UserId} 不存在"));
}
return Task.FromResult(new GetUserResponse
{
Id = user.Id,
Name = user.Name,
Email = user.Email,
Age = user.Age
});
}
// 实现服务端流接口:批量获取用户
public override async Task GetUserList(GetUserListRequest request, IServerStreamWriter<GetUserResponse> responseStream, ServerCallContext context)
{
var users = _users
.Skip((request.Page - 1) * request.PageSize)
.Take(request.PageSize)
.ToList();
foreach (var user in users)
{
// 检查客户端是否取消请求
if (context.CancellationToken.IsCancellationRequested)
{
break;
}
await responseStream.WriteAsync(new GetUserResponse
{
Id = user.Id,
Name = user.Name,
Email = user.Email,
Age = user.Age
});
// 模拟流传输间隔
await Task.Delay(100);
}
}
// 实现客户端流接口:创建用户
public override async Task<CreateUserResponse> CreateUser(IAsyncStreamReader<CreateUserRequest> requestStream, ServerCallContext context)
{
int createCount = 0;
// 循环读取客户端发送的流数据
while (await requestStream.MoveNextAsync())
{
var req = requestStream.Current;
// 简单校验邮箱格式
if (!req.Email.Contains("@"))
{
return new CreateUserResponse
{
Success = false,
Message = $"用户 {req.Name} 的邮箱格式不正确",
UserId = 0
};
}
var newUser = new UserModel
{
Id = _users.Max(u => u.Id) + 1,
Name = req.Name,
Email = req.Email,
Age = req.Age
};
_users.Add(newUser);
createCount++;
}
return new CreateUserResponse
{
Success = true,
Message = $"成功创建 {createCount} 个用户",
UserId = _users.Max(u => u.Id)
};
}
// 内部用户模型类
private class UserModel
{
public int Id { get; set; }
public string Name { get; set; }
public string Email { get; set; }
public int Age { get; set; }
}
}
然后在Program.cs中注册gRPC服务,配置代码如下:
var builder = WebApplication.CreateBuilder(args);
// 添加gRPC服务
builder.Services.AddGrpc(options =>
{
// 配置最大接收消息大小
options.MaxReceiveMessageSize = 2 * 1024 * 1024; // 2MB
// 配置最大发送消息大小
options.MaxSendMessageSize = 5 * 1024 * 1024; // 5MB
});
var app = builder.Build();
// 映射gRPC服务
app.MapGrpcService<UserService>();
// 配置HTTP端口
app.MapGet("/", () => "gRPC服务运行中,请使用gRPC客户端访问");
app.Run();
创建gRPC客户端项目
新建一个控制台应用项目,命名为GrpcDemo.Client,通过NuGet安装以下包:
- Grpc.Net.Client
- Google.Protobuf
- Grpc.Tools
将服务端的user.proto文件复制到客户端的Protos目录下,修改项目文件添加proto引用:
<ItemGroup> <Protobuf Include="Protosuser.proto" GrpcServices="Client" /> </ItemGroup>
客户端调用代码示例:
using Grpc.Core;
using Grpc.Net.Client;
using GrpcDemo.Client.Protos;
// 创建gRPC客户端通道,地址为服务端启动地址
using var channel = GrpcChannel.ForAddress("https://localhost:5001");
var client = new User.UserClient(channel);
try
{
// 1. 调用获取单个用户接口
Console.WriteLine("=== 获取单个用户 ===");
var getUserResp = await client.GetUserAsync(new GetUserRequest { UserId = 1 });
Console.WriteLine($"用户ID:{getUserResp.Id},姓名:{getUserResp.Name},邮箱:{getUserResp.Email},年龄:{getUserResp.Age}");
// 2. 调用服务端流接口
Console.WriteLine("n=== 批量获取用户(服务端流)===");
using var call = client.GetUserList(new GetUserListRequest { Page = 1, PageSize = 2 });
await foreach (var user in call.ResponseStream.ReadAllAsync())
{
Console.WriteLine($"用户ID:{user.Id},姓名:{user.Name}");
}
// 3. 调用客户端流接口
Console.WriteLine("n=== 创建用户(客户端流)===");
using var createCall = client.CreateUser();
// 发送多个用户数据
await createCall.RequestStream.WriteAsync(new CreateUserRequest
{
Name = "赵六",
Email = "zhaoliu@ipipp.com",
Age = 30
});
await createCall.RequestStream.WriteAsync(new CreateUserRequest
{
Name = "孙七",
Email = "sunqi@ipipp.com",
Age = 26
});
// 标记客户端流发送完成
await createCall.RequestStream.CompleteAsync();
// 获取服务端响应
var createResp = await createCall;
Console.WriteLine($"创建结果:{createResp.Message},最新用户ID:{createResp.UserId}");
}
catch (RpcException ex)
{
Console.WriteLine($"gRPC调用异常:{ex.Status.Detail}");
}
高级配置技巧
添加gRPC拦截器
拦截器可以实现日志、权限校验、异常处理等通用逻辑,首先定义服务端拦截器:
using Grpc.Core;
using Grpc.Core.Interceptors;
namespace GrpcDemo.Server.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)
{
_logger.LogInformation($"收到请求:{context.Method},请求参数:{request}");
try
{
var response = await continuation(request, context);
_logger.LogInformation($"请求处理完成:{context.Method}");
return response;
}
catch (Exception ex)
{
_logger.LogError(ex, $"请求处理异常:{context.Method}");
throw;
}
}
}
在Program.cs中注册拦截器:
builder.Services.AddGrpc(options =>
{
options.Interceptors.Add<LoggingInterceptor>();
});
配置TLS加密
生产环境建议启用TLS加密,首先生成自签名证书,然后在Program.cs中配置Kestrel使用HTTPS:
builder.WebHost.ConfigureKestrel(options =>
{
options.ListenAnyIP(5001, listenOptions =>
{
listenOptions.UseHttps("server.pfx", "证书密码");
});
});
全局异常处理
可以自定义异常处理器,统一返回gRPC标准错误响应:
builder.Services.AddGrpc(options =>
{
options.EnableDetailedErrors = true; // 开发环境开启详细错误
options.Interceptors.Add<ExceptionInterceptor>();
});
// 异常拦截器实现
public class ExceptionInterceptor : Interceptor
{
public override async Task<TResponse> UnaryServerHandler<TRequest, TResponse>(
TRequest request,
ServerCallContext context,
UnaryServerMethod<TRequest, TResponse> continuation)
{
try
{
return await continuation(request, context);
}
catch (ArgumentException ex)
{
throw new RpcException(new Status(StatusCode.InvalidArgument, ex.Message));
}
catch (Exception ex)
{
throw new RpcException(new Status(StatusCode.Internal, "服务内部错误"));
}
}
}
常见问题排查
| 问题现象 | 可能原因 | 解决方式 |
|---|---|---|
| 客户端调用提示连接失败 | 服务端未启动、端口配置错误、防火墙拦截 | 检查服务端运行状态,确认客户端地址与服务端监听地址一致 |
| 提示proto文件未生成对应代码 | 项目文件中未正确配置Protobuf引用 | 检查csproj文件中Protobuf节点的GrpcServices属性是否正确 |
| 消息大小超过限制报错 | 发送或接收的消息超过默认大小限制 | 在AddGrpc中调整MaxReceiveMessageSize和MaxSendMessageSize配置 |
| 流接口调用无响应 | 未正确标记流结束、客户端或服务端提前取消 | 检查流写入后是否调用CompleteAsync,确认未触发取消令牌 |
C#gRPCASP.NET_Coreprotobuf修改时间:2026-06-08 18:45:53