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

如何解决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.0
    Google.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));
        }   
    }
}

关键修改点

  1. 禁用SocketsHttpHandler:添加AppContext.SetSwitch("System.Net.Http.UseSocketsHttpHandler", false);,强制gRPC使用Grpc.Core的原生TCP通道,该通道对未加密HTTP/2的支持更稳定。
  2. 保留未加密HTTP/2开关:确保环境允许未加密的HTTP/2连接。
  3. 显式指定无加密凭证:配合模拟器的无加密连接要求,避免自动加密逻辑干扰。

另外,也可以尝试将Google.Cloud.PubSub.V1版本降级到3.5.0等早期版本,部分用户反馈旧版本在.NET Framework下对模拟器的兼容性更好,但优先推荐上述通道配置方案。


内容的提问来源于stack exchange,提问作者juanespty

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.05 17:09:52