You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

C# gRPC客户端调用远程服务遇Unimplemented错误排查与解决

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;
            }
        }
    }
}

一、代码核心错误排查

  1. 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 });
    
  2. 冗余的HTTP2明文开关
    启用System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport仅适用于HTTP明文场景,而远程调用用的是HTTPS加密连接,此配置完全多余,建议删除:

    // 删除该行
    AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true);
    
  3. 异常处理不合理
    捕获RpcException后直接抛出InnerException会丢失gRPC原生的状态码和错误详情,不利于问题排查,建议直接返回gRPC错误信息。


二、远程gRPC调用的额外配置要求

  1. 服务器端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;
    });
    
  2. 服务器端需启用gRPC-Web
    客户端配置中启用了UseGrpcWeb,服务器端必须同步配置支持gRPC-Web,否则会通信失败:

    // 服务器端添加中间件
    app.UseGrpcWeb(new GrpcWebOptions { DefaultEnabled = true });
    app.MapGrpcService<GreeterService>().EnableGrpcWeb();
    
  3. 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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 02:44:53