Ruby与C#的gRPC通信报错:服务未实现问题排查与解决
我开发了一个C#版本的gRPC服务器,其proto文件定义如下:
package xxx.common; option csharp_namespace = "Xxx.ComCon"; service PeerController { ... }
对应的C#实现代码:
namespace Xxx.ComCon; public partial class PeerControllerImpl : Peer { ... }
随后我将该proto文件复制到Ruby项目中,使用protoc生成了存根代码:
require 'grpc' require 'PeerController_pb' module Xxx module Common module PeerController
并编写了GrpcClient类:
class GrpcClient def initialize(peer) @stub = Xxx::Common::PeerController::Stub.new("#{peer.server.private_ip}:#{peer.port}", :this_channel_is_insecure) end def say_hello request = Xxx::Common::IdentifiedRequest.new(name: "RoR calling") response = @stub.say_hello(request) response.message end end
执行GrpcClient调用时,C#服务器抛出错误:
Service 'xxx.common.PeerController' is unimplemented.
我怀疑问题出在C#中命名空间Xxx.ComCon下的PeerControllerImpl实现,与Ruby调用方的xxx.common.PeerController命名空间不匹配,是否确实如此?该如何解决这个问题?
我原本预期服务器能接收请求并返回消息,以下是SayHello方法的实现:
public override Task<SuccessReply> SayHello(IdentifiedRequest request, ServerCallContext context) { var metadata = context.RequestHeaders; var clientId = metadata.GetValue("client-id"); return Task.FromResult(new SuccessReply { Success = true, Message = $"Hello from PeerController to: " + request.Name, }); }
你的怀疑方向正确,问题核心是gRPC服务标识不匹配。
gRPC服务的唯一标识由proto定义的package + service name决定,也就是xxx.common.PeerController。但你的C#实现存在关键问题,导致服务器无法关联到正确的服务:
1. 错误的基类继承
当protoc为C#生成gRPC代码时,针对service PeerController会生成名为PeerControllerBase的抽象基类(而非Peer)。你的PeerControllerImpl继承Peer,会导致服务器无法识别该实现对应的是xxx.common.PeerController服务。
2. 解决步骤
修正C#服务实现的继承关系
确保你的实现继承自动生成的PeerControllerBase类:namespace Xxx.ComCon; public partial class PeerControllerImpl : PeerControllerBase { public override Task<SuccessReply> SayHello(IdentifiedRequest request, ServerCallContext context) { var metadata = context.RequestHeaders; var clientId = metadata.GetValue("client-id"); return Task.FromResult(new SuccessReply { Success = true, Message = $"Hello from PeerController to: " + request.Name, }); } }确认服务器服务注册逻辑
启动C# gRPC服务器时,必须使用生成的服务描述符绑定你的实现:var server = new Server { Services = { PeerController.BindService(new PeerControllerImpl()) }, Ports = { new ServerPort("0.0.0.0", 50051, ServerCredentials.Insecure) } }; server.Start();这里的
PeerController是protoc生成的包含服务元数据的类,BindService方法会将你的实现与xxx.common.PeerController服务标识关联起来。Ruby客户端无需修改
Ruby生成的Xxx::Common::PeerController::Stub完全对应proto中的xxx.common.PeerController服务,这部分是正确的。
完成以上修改后,C#服务器就能正确识别Ruby客户端的请求,匹配到对应的服务实现,解决"服务未实现"的错误。
内容的提问来源于stack exchange,提问作者Rodrigo Botafogo

