如何让 gRPC 在 Unity 游戏客户端

分享于2022年07月17日 c# grpc grpc-web rust unity3d 问答
【问题标题】:如何让 gRPC 在 Unity 游戏客户端 (c#/.NET) 和基于 Tonic (rust) 的服务器之间工作(How to get gRPC working between a Unity game client (c#/.NET) and Tonic (rust) based server)
【发布时间】:2022-07-08 14:52:56
【问题描述】:

所以我有一个使用 Tonic crate 用 Rust 编写的 gRPC 服务器。我已经编写了所有原型文件,我可以使用基于 Tonic 的客户端和 grpcurl 发出请求,因此服务器端没有问题。

我有一个基于 Unity 3D 的游戏,我想用作客户端。 Unity 使用 C#/.NET/Mono,所以理论上它应该很容易让 gRPC 客户端工作。事实证明这并不容易。

根据互联网(尤其是 https://grpc.io/blog/grpc-csharp-future/ ),我们应该使用 Grpc.Net.Client 包。所以我使用非常有用的 NuGetForUnity 工具将它放到了 Unity 中。

我收到了以下错误,它至少提供了很多信息

PlatformNotSupportedException:gRPC 需要对不支持 gRPC over HTTP/2 的 .NET 实现进行额外配置。必须使用 GrpcChannelOptions.HttpHandler 指定 HTTP 提供程序。配置的 HTTP 提供程序必须支持 HTTP/2 或配置为使用 gRPC-Web。详情请见 https://aka.ms/aspnet/grpc/netstandard

经过一些谷歌搜索后,我发现以下链接清楚地表明 Unity/Xamarin 和其他不支持 Grpc.Net.Client。

https://github.com/grpc/grpc-dotnet/issues/1309#issuecomment-850303082

https://docs.microsoft.com/en-us/aspnet/core/grpc/netstandard?view=aspnetcore-6.0

总结:

不支持 HTTP/2 的 .NET 实现,例如 UWP、Xamarin 和 Unity,可以使用 gRPC-Web 作为替代方案。

这听起来很简单,所以我导入了 Grpc.Net.Client.Web 并按照文章中的建议将其连接起来。

至少这次请求通过了,但是 gRPC 服务器开始导致“Connection reset by peer”错误。

破解 tcpdump,我意识到 gRPC web 发出了 Tonic 不喜欢的 HTTP 1.x 请求。

我在 Tonic 中找到了 'accept_http1' config option ,它具有以下描述并设置为“true”。

接受 http1 请求仅在开发启用 grpc-web 的服务时有用。如果此设置设置为 true,但服务未正确配置为处理 grpc-web 请求,您的服务器可能会返回令人困惑(但正确)的协议错误。

这样做之后,服务器至少会接受连接,但我开始收到错误:

(StatusCode="Cancelled", Detail="No grpc-status found on response.")

我看到了这个问题 https://github.com/grpc/grpc-dotnet/issues/1164 ,但它根本没有帮助。

似乎 Tonic 不喜欢 gRPC-web 的 Grpc.Net.Client.Web 实现,或者只是不正确地支持它。

然后我花了很长时间试图通过使用 TLS 来强制 gRPC-Web 使用 HTTP/2(我什至不确定这是否有意义),但我无法让 TLS 握手与 Tonic 服务器一起工作。我不断收到模糊的信息:

TlsException:握手失败 - 错误代码:UNITYTLS_INTERNAL_ERROR,验证结果:(一些大数字)

我很确定这是因为 Tonic 期待 TLS 1.3,而 Unity 使用的 Mono 版本的最大 TLS 版本为 1.2(甚至最近才支持)。我不知道如何配置 Tonic/Rustls 以接受较低版本的 TLS。

在仔细阅读文档后我也意识到,如果我使用 gRPC-web,我会失去很多好的 gRPC 功能,例如流式传输,所以我决定避免使用这种方法。

有谁知道将自定义 HTTP 客户端插入 gRPC 库的方法,该库可以完成 Unity 不具备的所有功能(例如 HTTP/2 和 TLS 1.3)?如果没有,是否有人知道可以让我至少让 RPC 正常工作的解决方法?

谢谢!


【解决方案1】:

我不确定这是否是理想的答案,但它至少适用于我的情况。我最终使用 Grpc.Core 而不是推荐的 Grpc.Net.Client 来让它全部工作

虽然 Grpc.Core is deprecated 仍然有效,但维护期已延长至 2023 年 5 月。

它功能齐全,所以主要问题是维护期结束时无法获得安全更新,但希望届时 Unity 将有一个 HTTP/2 实现!

我怀疑它是可行的,因为它是一个原生库的包装器,它内置了自己的 HTTP 实现,这意味着我们可以绕过 Unity 的旧实现。这条评论证实了这种怀疑: Why does Xamarin Android fails to send GRPC/Http2 requests?

很多接口都排成一行,所以从代码的角度来看,我唯一要做的就是从项目中删除 Grpc.Net.Client,添加 Grpc.Core 然后替换

var channel = Grpc.Net.Client.GrpcChannel.ForAddress("http://127.0.0.1:50051");

 var channel = new Grpc.Core.Channel("127.0.0.1:50051", ChannelCredentials.Insecure);

但是,有一个很大的警告。本机库作为 Grpc.Core Nuget 包的一部分提供,不会自动安装在正确的位置。您可以通过手动下载 nuget file ,将 nuget 文件解压缩为 zip 文件并检查 runtimes 文件夹来查看它们。

目前只有以下平台的预编译二进制文件

找到所需平台的二进制库并将其复制到 Unity 项目的“Assets/Plugins”目录中。您可能需要在 Unity 中选择它们,并确保所有架构和平台都设置正确。

您还需要确保文件的基本名称是“grpc_csharp_ext”。例如 Grpc.Core.M1 项目中的二进制文件名为“grpc_csharp_ext.arm64.dylib”,需要重命名为“grpc_csharp_ext.dylib”,否则会报错:

DllNotFoundException:grpc_csharp_ext 程序集:类型:成员:(null)

此方法不适用于其他平台。如果您愿意投入工作,您也许可以为其他平台编译自己的版本!

来源在 https://github.com/grpc/grpc/tree/master/src/csharp

我希望这对某人有所帮助,我花了很长时间才找到!