如何解决C# .NET Framework 4.8无法向Pub/Sub模拟器发消息问题
.NET Framework 4.8 连接Google Pub/Sub模拟器失败问题解决
问题背景
在.NET Framework 4.8环境下向Google Pub/Sub模拟器发送消息时失败,但生产环境中代码可正常运行,且相同代码在.NET 8.0环境下配合gcloud模拟器能正常工作。两者使用相同的NuGet包和gcloud版本。
环境信息
- 使用的NuGet包:
Google.Api.Gax = 4.4.0Google.Cloud.PubSub.V1 = 3.8.0 - gcloud版本:
>gcloud version Google Cloud SDK 455.0.0 beta 2023.11.10 bq 2.0.98 core 2023.11.10 gsutil 5.27 pubsub-emulator 0.8.10 - 模拟器启动命令:
gcloud beta emulators pubsub start --project=my-project --host-port=localhost:8085 - 环境变量设置:
setx PUBSUB_EMULATOR_HOST "localhost:8085"
.NET Framework 4.8 原代码示例
using Google.Api.Gax; using Google.Cloud.PubSub.V1; using System; using System.Threading.Tasks; namespace PubSubDotNetTest { internal class Program { static void Main(string[] args) { AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2Support", true); Subscriber().GetAwaiter().GetResult(); } static async Task Subscriber() { Console.WriteLine("Starting publisher..."); var projectId = "my-project"; var topicId = "my-topic"; PublisherServiceApiClient publisherService = await new PublisherServiceApiClientBuilder { EmulatorDetection = EmulatorDetection.EmulatorOrProduction }.BuildAsync(); TopicName topicName = new TopicName(projectId, topicId); PublisherClient publisher = await new PublisherClientBuilder { TopicName = topicName, EmulatorDetection = EmulatorDetection.EmulatorOrProduction }.BuildAsync(); await publisher.PublishAsync("Hello, Pubsub1"); await publisher.ShutdownAsync(TimeSpan.FromSeconds(15)); } } }
错误信息
代码抛出异常
Unhandled Exception: Grpc.Core.RpcException: Status(StatusCode="Internal", Detail="Bad gRPC response. Response protocol downgraded to HTTP/1.1.") at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Google.Api.Gax.Grpc.ApiCallRetryExtensions.<>c__DisplayClass0_0`2.<WithRetry>b__0.MoveNext() --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Google.Cloud.PubSub.V1.Tasks.ForwardingAwaiter`1.GetResult() in /_/apis/Google.Cloud.PubSub.V1/Google.Cloud.PubSub.V1/Tasks/ForwardingAwaiter.cs:line 41 at Google.Cloud.PubSub.V1.Tasks.Extensions.<>c__DisplayClass5_0`1.<ConfigureAwaitHideErrors>g__Inner|0.MoveNext() in /_/apis/Google.Cloud.PubSub.V1/Google.Cloud.PubSub.V1/Tasks/TaskHelper.cs:line 174 --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at Google.Cloud.PubSub.V1.Tasks.ForwardingAwaiter`1.GetResult() in /_/apis/Google.Cloud.PubSub.V1/Google.Cloud.PubSub.V1/Tasks/ForwardingAwaiter.cs:line 41 at Google.Cloud.PubSub.V1.PublisherClientImpl.<PublishAsync>d__32.MoveNext() in /_/apis/Google.Cloud.PubSub.V1/Google.Cloud.PubSub.V1/PublisherClientImpl.cs:line 247 --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Runtime.CompilerServices.TaskAwaiter`1.GetResult() at PubSubDotNetTest.Program.<Subscriber>d__1.MoveNext() in C:\MyPath\Program.cs:line 37 --- End of stack trace from previous location where exception was thrown --- at System.Runtime.CompilerServices.TaskAwaiter.ThrowForNonSuccess(Task task) at System.Runtime.CompilerServices.TaskAwaiter.HandleNonSuccessAndDebuggerNotification(Task task) at System.Runtime.CompilerServices.TaskAwaiter.GetResult() at PubSubDotNetTest.Program.Main(String[] args) in C:\MyPath\Program.cs:line 12
模拟器控制台日志
[pubsub] Nov 21, 2023 10:37:46 AM io.gapi.emulators.netty.HttpVersionRoutingHandler channelRead [pubsub] INFO: Detected non-HTTP/2 connection. [pubsub] Nov 21, 2023 10:37:46 AM io.gapi.emulators.netty.NotFoundHandler handleRequest [pubsub] INFO: Unknown request URI: /google.pubsub.v1.Publisher/Publish [pubsub] Nov 21, 2023 10:37:46 AM io.gapi.emulators.netty.NotFoundHandler handleRequest [pubsub] INFO: Unknown request URI: /google.pubsub.v1.Publisher/Publish [pubsub] Nov 21, 2023 10:37:46 AM io.gapi.emulators.netty.NotFoundHandler handleRequest [pubsub] INFO: Unknown request URI: /google.pubsub.v1.Publisher/Publish [pubsub] Nov 21, 2023 10:37:47 AM io.gapi.emulators.netty.NotFoundHandler handleRequest [pubsub] INFO: Unknown request URI: /google.pubsub.v1.Publisher/Publish [pubsub] Nov 21, 2023 10:37:52 AM io.gapi.emulators.netty.NotFoundHandler handleRequest [pubsub] INFO: Unknown request URI: /google.pubsub.v1.Publisher/Publish
尝试过的无效方案
- 添加
AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true); - 手动指定模拟器Endpoint和无安全凭证连接
解决方案
.NET Framework 4.8中,默认的gRPC传输层无法正确支持未加密的HTTP/2连接,需要强制使用Grpc.Core的原生通道而非基于SocketsHttpHandler的实现。修改后的代码如下:
using Google.Api.Gax; using Google.Cloud.PubSub.V1; using Grpc.Core; using System; using System.Threading.Tasks; namespace PubSubDotNetTest { internal class Program { static void Main(string[] args) { // 禁用SocketsHttpHandler,强制使用Grpc.Core原生通道 AppContext.SetSwitch("System.Net.Http.UseSocketsHttpHandler", false); // 开启未加密HTTP/2支持 AppContext.SetSwitch("System.Net.Http.SocketsHttpHandler.Http2UnencryptedSupport", true); Subscriber().GetAwaiter().GetResult(); } static async Task Subscriber() { Console.WriteLine("Starting publisher..."); var projectId = "my-project"; var topicId = "my-topic"; PublisherClient publisher = await new PublisherClientBuilder { TopicName = new TopicName(projectId, topicId), EmulatorDetection = EmulatorDetection.EmulatorOrProduction, // 显式配置无加密凭证 ChannelCredentials = ChannelCredentials.Insecure }.BuildAsync(); await publisher.PublishAsync("Hello, Pubsub1"); await publisher.ShutdownAsync(TimeSpan.FromSeconds(15)); } } }
关键修改点
- 禁用SocketsHttpHandler:添加
AppContext.SetSwitch("System.Net.Http.UseSocketsHttpHandler", false);,强制gRPC使用Grpc.Core的原生TCP通道,该通道对未加密HTTP/2的支持更稳定。 - 保留未加密HTTP/2开关:确保环境允许未加密的HTTP/2连接。
- 显式指定无加密凭证:配合模拟器的无加密连接要求,避免自动加密逻辑干扰。
另外,也可以尝试将Google.Cloud.PubSub.V1版本降级到3.5.0等早期版本,部分用户反馈旧版本在.NET Framework下对模拟器的兼容性更好,但优先推荐上述通道配置方案。
内容的提问来源于stack exchange,提问作者juanespty
相关产品推荐
相关产品推荐

