C# gRPC客户端调用远程服务遇Unimplemented错误排查与解决
我在C#客户端调用服务器gRPC服务时遇到异常:Grpc.Core.RpcException: Status(StatusCode="Unimplemented", Detail="Service is unimplemented")。本地调用正常,但使用远程地址https://100.100.22.19:443/grpcSample/时触发该错误。以下是代码排查、远程调用的额外配置要求及解决方案:
附相关代码
.proto定义
syntax = "proto3"; option csharp_namespace = "GrpcServiceSample"; package greet; // The greeting service definition. service Greeter { // Sends a greeting rpc SayHello (HelloRequest) returns (HelloReply); } // The request message containing the user's name. message HelloRequest { string name = 1; } // The response message containing the greetings. message HelloReply { string message = 1; }
客户端项目配置代码
using ClientSample.Service; using grpcSample; using Microsoft.OpenApi.Models; var builder = WebApplication.CreateBuilder(args); // Add services to the container. builder.Services.AddControllers(); // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); builder.Services.AddGrpcClient<Greeter.GreeterClient>(); builder.Services.AddCors(options => { options.AddPolicy("cors", policy => { policy.AllowAnyMethod() .AllowAnyHeader() .AllowAnyOrigin() .WithExposedHeaders("Grpc-Status", "Grpc-Message", "Grpc-Encoding", "Grpc-Accept-Encoding"); }); }); builder.Services.AddGrpc().AddJsonTranscoding(); builder.Services.AddGrpcSwagger(); builder.Services.AddSwaggerGen(c => { c.SwaggerDoc("v1", new OpenApiInfo { Title = "gRPC transcoding", Version = "v1" }); }); var app = builder.Build(); // Configure the HTTP request pipeline. if (app.Environment.IsDevelopment() || app.Environment.IsProduction()) { app.UseSwagger(); app.UseSwaggerUI(); app.UseDeveloperExceptionPage(); } app.UseHttpsRedirection(); app.UseRouting(); app.UseGrpcWeb(new GrpcWebOptions { DefaultEnabled = true }); app.UseCors(); app.MapControllers(); app.Run();
客户端调用代码
using Grpc.Core; using Grpc.Net.Client; using grpcSample; using Microsoft.AspNetCore.Mvc; namespace ClientSample.Controllers { [Route("api/[controller]")] [ApiController] public class GrpcController : ControllerBase { [HttpGet] public async Task<IActionResult> Index() { try { AppContext.SetSwitch( "System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true); var httpHandler = new HttpClientHandler() { ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator }; var channel = GrpcChannel.ForAddress("https://100.100.22.19:443/grpcSample/" , new GrpcChannelOptions { HttpHandler = httpHandler }); var client = new Greeter.GreeterClient(channel); var response = await client.SayHelloAsync(new HelloRequest { Name = ".NET 7.0" }); return Ok(response); } catch (RpcException ex) { throw ex.InnerException; } } } }
一、代码核心错误排查
gRPC通道地址路径错误
远程地址末尾的/grpcSample/属于冗余路径,gRPC服务的路由由package+service名自动生成,手动拼接路径会导致服务器无法匹配到目标服务。
错误写法:var channel = GrpcChannel.ForAddress("https://100.100.22.19:443/grpcSample/", new GrpcChannelOptions { HttpHandler = httpHandler });修正后:
var channel = GrpcChannel.ForAddress("https://100.100.22.19:443", new GrpcChannelOptions { HttpHandler = httpHandler });冗余的HTTP2明文开关
启用System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport仅适用于HTTP明文场景,而远程调用用的是HTTPS加密连接,此配置完全多余,建议删除:// 删除该行 AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);异常处理不合理
捕获RpcException后直接抛出InnerException会丢失gRPC原生的状态码和错误详情,不利于问题排查,建议直接返回gRPC错误信息。
二、远程gRPC调用的额外配置要求
服务器端gRPC路径前缀适配(若存在)
如果服务器端确实配置了/grpcSample作为统一路径前缀,客户端需通过AddGrpcClient的配置指定,而非拼接在通道地址中:builder.Services.AddGrpcClient<Greeter.GreeterClient>(options => { options.Address = new Uri("https://100.100.22.19:443"); // 若服务器有路径前缀,需设置通道的HttpHandler options.ChannelOptionsActions.Add(channelOptions => { channelOptions.HttpHandler = new HttpClientHandler { ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator }; }); }).ConfigurePrimaryHttpMessageHandler(() => { // 适配gRPC-Web模式 var handler = new GrpcWebHandler(GrpcWebMode.GrpcWebText, new HttpClientHandler { ServerCertificateCustomValidationCallback = HttpClientHandler.DangerousAcceptAnyServerCertificateValidator }); return handler; });服务器端需启用gRPC-Web
客户端配置中启用了UseGrpcWeb,服务器端必须同步配置支持gRPC-Web,否则会通信失败:// 服务器端添加中间件 app.UseGrpcWeb(new GrpcWebOptions { DefaultEnabled = true }); app.MapGrpcService<GreeterService>().EnableGrpcWeb();HTTPS证书规范
当前使用DangerousAcceptAnyServerCertificateValidator跳过证书验证仅适用于测试环境,生产环境必须使用受信任的SSL证书,或配置合法的证书验证逻辑。
三、修正后的客户端调用代码示例
using Grpc.Core; using grpcSample; using Microsoft.AspNetCore.Mvc; namespace ClientSample.Controllers { [Route("api/[controller]")] [ApiController] public class GrpcController : ControllerBase { private readonly Greeter.GreeterClient _greeterClient; // 通过依赖注入获取gRPC客户端,符合ASP.NET Core规范 public GrpcController(Greeter.GreeterClient greeterClient) { _greeterClient = greeterClient; } [HttpGet] public async Task<IActionResult> Index() { try { var response = await _greeterClient.SayHelloAsync(new HelloRequest { Name = ".NET 7.0" }); return Ok(response); } catch (RpcException ex) { // 返回gRPC原生错误状态和详情 return StatusCode((int)ex.StatusCode, ex.Status.Detail); } } } }
内容的提问来源于stack exchange,提问作者Alireza Siavashi

