如何使用gRpc实现双向通信?

3
多年来,由于各种原因,我们所在的公司已经大量投资使用Windows Communication Foundation(WCF)。目前,我们正在将主要开发平台从Visual Studio 2015升级到Visual Studio 2022。遗憾的是,我们发现我们的WCF客户端和服务器应用程序需要比我们预期的更多的工作才能将它们升级到Visual Studio 2022和更新的.NET框架;它们当然不像以前那样流畅。我们计划保留使用WCF的现有应用程序,但正在考虑其他新应用程序的选项。似乎被推广的主要选项是gRPC,然而,根据我目前了解到的情况,对于我们想要做的某些事情,它似乎是一个非常糟糕的替代品。对于我们,WCF中一些关键的价值在于:
  • 单向函数调用(即只要我们知道调用已传递,我们就不需要等待直到其完成)。我了解到这可以通过gRPC实现。
  • 完全双向通信(即客户端可以对服务器进行调用,反之亦然;服务器可以对客户端进行调用)。我了解到使用“流”可以近似实现此功能,但不容易。

为了说明,考虑涉及三种应用程序类型的简化情况:

  • 声音请求者。一个应用程序,请求在不同位置播放不同的声音,并更改声音参数。
  • 声音播放器。可以播放请求的声音并更改其参数的应用程序。它可以通知声音已完成。
  • 声音处理代理。位于声音请求者和声音播放器之间的应用程序。

我们的WCF实现将声音处理代理作为WCF服务器,声音请求者和声音播放器应用程序作为连接到服务器的客户端。这非常适合我们;代理不需要知道要连接哪些播放器和请求者,它们连接到它(并且可以随时这样做)。代理只提供“编排”服务。下面是一个简化的序列图,以说明可能进行的调用类型:

enter image description here

我的问题是:“如果使用gRpc实现,我们如何在SoundPlayer上实现调用(例如CreateSound,PlaySound等)?”如果我理解正确,由于SoundPlayer是客户端(而不是服务器),我们需要将这些实现为从播放器到服务器的调用的“返回”流。有更简单的方法吗? (我知道我们可以将SoundPlayer作为服务器,让SoundHandlerProxy作为客户端连接-但这意味着代理需要知道它要连接的所有播放器,我们宁愿避免这种情况。) 或者,除了gRpc之外,我们还可以迁移到其他东西(最好是未来至少十年稳定的东西)吗?

不确定这是否符合您的要求,但请查看grpc-dotnet GitHub上的“Racer”示例。它是用于Web应用程序的,但发送双向消息的原则仍然适用。 - Matthew Watson
谢谢@MatthewWatson。我已经看了你指给我的例子。我认为这大致是我自己迄今为止所达到的方法——即使用流。我的问题在于,我需要我的服务器(SoundHandlerProxy)能够调用客户端(SoundPlayer)上的许多不同函数。在这种情况下,我需要设置许多不同的流或使用单个流,但构建一些方式来说明我想要客户端执行哪个“函数”。两者都可能,但远非使用WCF时那么容易。感谢您的意见。 - AndrewCh
你想要实现的函数类似于服务器可以在客户端调用该函数,客户端可以选择执行该方法,并且指定使用c#语言。我的理解是否符合您的想法? - Jiayao
在您当前的WCF应用程序中,您特别使用了https://learn.microsoft.com/en-us/dotnet/framework/wcf/feature-details/one-way-services和https://learn.microsoft.com/en-us/dotnet/framework/wcf/feature-details/duplex-services方法吗? - Eric Anderson
你好@EricAnderson,很抱歉回复慢了--我一直不在。回答你的问题:(1)是的,我们对于单向函数调用使用“IsOneWay=true”。我们可以确定函数调用已经被传递,但除此之外我们没有得到任何函数调用的响应(这对我们来说没问题)。(2)是的,我们正在使用“CallbackContract”。其他信息:通常我们使用NetTcpBinding(启用可靠会话和有序设置),偶尔使用NetNamedPipeBinding。 - AndrewCh
1个回答

0

我不是 .Net 专家,所以可能会有些细节错误。但是我认为要点是正确的。

单向服务 在网络传输时并不太有趣,因为它们仍然使用 HTTP 请求/响应。参考 单向样例

当调用操作时,服务在执行服务操作之前返回 HTTP 状态码为 202……调用操作的客户端会一直阻塞,直到收到来自服务的 202 响应。

因此,它们更像是存根功能,并且可以在 gRPC 中轻松模拟。在 gRPC 中,您将定义该方法返回 google.protobuf.Empty,并在服务器端在另一个线程中启动长时间运行的工作,然后立即返回。

service Foo {
  rpc Bar(BarRequest) returns (google.protobuf.Empty);
}

public class FooService : Foo.FooBase
{
    public override Task<Empty> Bar(BarRequest request,
        ServerCallContext context)
    {
        startProcessingInAnotherThread(request);
        return Task.FromResult(new Empty {});
    }
}

全双工通信似乎与双工服务相同。这些通过配置客户端以知道服务器如何到达它(其主机:端口)来工作。然后,客户端将该信息发送到服务器,以便服务器在未来向其发送RPC。这种设计肯定会对安全性产生影响,您应该研究一下您正在使用的安全模型。

在gRPC中,客户端只需向服务器发送一个通道地址字符串,然后服务器创建一个到该地址的通道即可模拟此过程。

service SoundHandler {
  rpc Connect(ConnectRequest) returns (google.protobuf.Empty);
}

message ConnectRequest {
  enum Type {
    TYPE_UNKNOWN = 0;
    TYPE_SOUND_REQUESTER = 1;
    TYPE_SOUND_PLAYER = 2;
  };

  string channelAddress = 1;
  Type type = 2;
}

service SoundPlayer {
  rpc CreateSound(CreateSoundRequest) returns (CreateSoundResponse);
  // ...
}

service SoundEventReceiver {
  rpc SoundFinished(SoundFinishedRequest) returns (SoundFinishedResponse);
}

然后,在服务中,您将为回调创建一个通道到提供的地址。
public class SoundHandlerService : SoundHandler.SoundHandlerBase
{
    public override Task<Empty> Bar(ConnectRequest request,
        ServerCallContext context)
    {
        var channel = GrpcChannel.ForAddress(request.channelAddress);
        if (request.type == TYPE_SOUND_PLAYER) {
          var channel = GrpcChannel.ForAddress(request.channelAddress);
          var client = new SoundPlayer.SoundPlayerClient(channel);
          registerSoundPlayer(channel, client);
        } // ...
        return Task.FromResult(new Empty {});
    }
}

你好Eric,感谢回复。我同意你所建议的一次调用易于仿真,谢谢。至于双工通信,我认为你的建议是“客户端”实际上为返回调用设置自己的服务,以便“服务器”可以回拨。这是对你答案的正确解释吗? - AndrewCh
Eric,有一件事情需要提醒您,关于您提到的安全性。我们考虑的情况是一组机器位于一个完全与互联网(和其他网络)断开连接的专用网络上。我很感激这并不意味着我可以忽略安全性,但它确实大大降低了风险。 - AndrewCh
仅考虑单向调用的仿真,存在一个小问题,即如果由于任何原因而未能传递调用,则调用站点(从“逻辑”调用站点的角度,而不是它的仿真方式)将不会获得失败的通知。我猜可以通过一些努力来解决这个问题。 - AndrewCh
  1. "客户端" 实际上为 "服务器" 设置了自己的服务,以便 "服务器" 可以回调。是的,这就是我所建议的。这与 .Net 在幕后所做的相同。
  2. 是的,安全性可能很好。重要的是要考虑它,并思考当前系统的安全性是如何得到保护的。
  3. 我不明白什么情况会导致 "呼叫未被传递"。对于网络故障,RPC 将失败。
- Eric Anderson

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接