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

gRPC C# DNS解析失败求助:域名未找到

问题:gRPC服务连接时DNS解析失败

昨日突然出现gRPC连接失败问题(未修改代码或操作系统),错误信息如下:

Unhandled exception. Grpc.Core.RpcException: Status(StatusCode="Unavailable", Detail="DNS resolution failed for https://0.0.0.0:3000: C-ares status is not ARES_SUCCESS qtype=A name=https://0.0.0.0:3000 is_balancer=0: Domain name not found"

所有服务从配置文件获取主机/端口,配置格式如下:

{
    "Services": {
        "Common": {
            "SSL": {
                "UseSSL": true,
                "CertificateChain": "server.crt",
                "PrivateKey": "server.pem"          
            }
        },
        "GrpcService": {
            "Hostname": "0.0.0.0",
            "Port": 3000
        },
    }
}

服务器代码

var host = config.GetValue<string>("Services:CameraIO:Hostname");
var port = config.GetValue<int>("Services:CameraIO:Port");
var hostBuilder = new HostBuilder()
    .ConfigureServices((hostContext, services) =>
    {
        Server server;
        if (config.GetValue<bool>("Services:Common:SSL:UseSSL"))
            server = new Server
            {
                Services = { CameraIOService.BindService(new CameraIOServer()) },
                Ports = { new ServerPort(host, port, Util.GetServerCertificates(config)) }
            };
        else
            server = new Server
            {
                Services = { CameraIOService.BindService(new CameraIOServer()) },
                Ports = { new ServerPort(host, port, ServerCredentials.Insecure) }
            };
        services.AddSingleton(server);
        services.AddSingleton<IHostedService, ServiceHost>();
    });
Console.WriteLine($"Starting RPC CameraIO Server on {host}:{port}");
hostBuilder.RunConsoleAsync();

客户端代码

if (config.GetValue<bool>("Services:Common:SSL:UseSSL"))
{
    var sslChannel = new Channel("https://" + config.GetValue<string>("Services:CameraIO:Hostname"),
        config.GetValue<int>("Services:CameraIO:Port"),
        Util.GetClientCertificate(config));
    client = new CameraIOService.CameraIOServiceClient(sslChannel);
}
else
{
    var host = "http://" + config.GetValue<string>("Services:CameraIO:Hostname");
    var port = config.GetValue<int>("Services:CameraIO:Port");
    var channel = GrpcChannel.ForAddress($"{host}:{port}");
    client = new CameraIOService.CameraIOServiceClient(channel);
}
logger = new LoggingClient<CameraIOClient>();

SSL凭证工具方法

/// <summary>
/// Get Server SSL Credentials
/// </summary>
/// <param name="config"></param>
/// <returns></returns>
public static SslServerCredentials GetServerCertificates(IConfiguration config)
{
    var ccPath = config.GetValue<string>("Services:Common:SSL:CertificateChain");
    var pkPath = config.GetValue<string>("Services:Common:SSL:PrivateKey");
    if (!File.Exists(ccPath))
        throw new FileNotFoundException(
            $"Could not find Certificate Chain File!\nCurrent Path: {AppDomain.CurrentDomain.BaseDirectory}\nLooking for File: {ccPath}");

    if (!File.Exists(pkPath))
        throw new FileNotFoundException(
            $"Could not find Private Key File!\nCurrent Path: {AppDomain.CurrentDomain.BaseDirectory}Looking for File: {pkPath}");
    var kcp = new KeyCertificatePair(
        File.ReadAllText(ccPath),
        File.ReadAllText(pkPath));
    return new SslServerCredentials(new List<KeyCertificatePair> { kcp });
}

/// <summary>
/// Get Client SSL Credentials
/// </summary>
/// <param name="config"></param>
/// <returns></returns>
public static SslCredentials GetClientCertificate(IConfiguration config)
{
    var ccPath = config.GetValue<string>("Services:Common:SSL:CertificateChain");
    if (!File.Exists(ccPath))
        throw new FileNotFoundException(
            $"Could not find Certificate Chain File!\nCurrent Path: {AppDomain.CurrentDomain.BaseDirectory}\nLooking for File: {ccPath}");
    return new SslCredentials(
        File.ReadAllText(ccPath));
}

Main.cs主方法

if (File.Exists("imgui.ini")) File.Delete("imgui.ini");
var config = new ConfigurationBuilder().AddJsonFile("settings.json").AddEnvironmentVariables().Build();
var loggingService = new LoggingServerHost(config);
var cameraIOService = new CameraIOServerHost(config);
var metricServer = new MetricServerHost(config);
var gui = new CoreGUI.CoreGui();

var lst = Task.Run(loggingService.Start);
var ciost = Task.Run(cameraIOService.Start);
var mst = Task.Run(metricServer.Start);

var metricClient = new MetricClient();

if (lst.Status == TaskStatus.Running)
    RunningServices.Add("Logging Server");
else
    Console.WriteLine("Could not start Logging Server!");

if (ciost.Status == TaskStatus.Running)
    RunningServices.Add("CameraIO Server");
else
    Console.WriteLine("Could not start CameraIO Server!");

if (mst.Status == TaskStatus.Running)
    RunningServices.Add("Metric Server");
else
    Console.WriteLine("Could not start Metric Server");

metricClient.PushMetric(new Metric
    { ServiceName = "Core", Name = "running_services", Value = string.Join(",", RunningServices) });

if (config.GetValue<bool>("Services:Core:StartUI")) gui.Start();

Console.ReadLine();

证书生成批处理脚本

@echo off
title Certificate Generation Script

if exist certs\ rd /q /s certs\
if exist request\ rd /q /s request\

mkdir certs
mkdir request

set OPENSSL_BIN=openssl
set EXPIRE_DAYS=7
set PASSPHRASE=password
set BIT_LENGTH=4096

set CRT_PATH=certs
set REQ_PATH=request

set CAC_SUBJ=""
set SSR_SUBJ=""

echo Generating Root Certificate Authority Key...
%OPENSSL_BIN% genrsa -passout pass:%PASSPHRASE% -des3 -out %CRT_PATH%\root.key %BIT_LENGTH%

echo Generating Root CA Certificate...
%OPENSSL_BIN% req -passin pass:%PASSPHRASE% -new -x509 -days %EXPIRE_DAYS% -key %CRT_PATH%\root.key -out %CRT_PATH%\root.crt -subj %CAC_SUBJ%

echo Generating Server Key...
%OPENSSL_BIN% genrsa -passout pass:%PASSPHRASE% -des3 -out %CRT_PATH%\server.key %BIT_LENGTH%

echo Generating Server Signing Request...
%OPENSSL_BIN% req -passin pass:%PASSPHRASE% -new -key %CRT_PATH%\server.key -out %REQ_PATH%\server.csr -subj %SSR_SUBJ%

echo Self-Signing Server Certificate...
%OPENSSL_BIN% x509 -req -passin pass:%PASSPHRASE% -days %EXPIRE_DAYS% -extensions server_ssl -extfile openssl-ext.cnf -in %REQ_PATH%\server.csr -CA %CRT_PATH%\root.crt -CAkey %CRT_PATH%\root.key -set_serial 01 -out %CRT_PATH%\server.crt

echo Removing Passphrase from Server Key...
%OPENSSL_BIN% rsa -passin pass:%PASSPHRASE% -in %CRT_PATH%\server.key -out %CRT_PATH%\server.key

echo Exporting PKCS12 Keys...
%OPENSSL_BIN% pkcs12 -export -in %CRT_PATH%\root.crt -inkey %CRT_PATH%\root.key -out %CRT_PATH%\root.pfx -passout pass:%PASSPHRASE% -passin pass:%PASSPHRASE% -name "<My Company> Root CA Certificate"
%OPENSSL_BIN% pkcs12 -export -in %CRT_PATH%\server.crt -inkey %CRT_PATH%\server.key -out %CRT_PATH%\server.pfx -passout pass:%PASSPHRASE% -passin pass:%PASSPHRASE% -name "<My Application> Self-Signed Server Certificate"

echo Generating PEM Version of Key...
type %CRT_PATH%\server.key > %CRT_PATH%\server.pem
type %CRT_PATH%\server.crt >> %CRT_PATH%\server.pem

rd /q /s request\

问题分析与解决方案

1. 客户端Channel构造参数错误(核心问题)

错误日志显示正在解析https://0.0.0.0:3000这个域名,这是因为客户端创建SSL Channel时错误地将https://前缀加到了主机名前。gRPC的Channel构造函数第一个参数是目标地址(不需要协议前缀),或者应该使用GrpcChannel.ForAddress方法(带协议前缀)。

修改客户端SSL分支代码:

if (config.GetValue<bool>("Services:Common:SSL:UseSSL"))
{
    // 方法1:去掉https前缀,直接传主机+端口
    var hostname = config.GetValue<string>("Services:CameraIO:Hostname");
    var port = config.GetValue<int>("Services:CameraIO:Port");
    var sslChannel = new Channel($"{hostname}:{port}", Util.GetClientCertificate(config));
    client = new CameraIOService.CameraIOServiceClient(sslChannel);

    // 或者方法2:使用GrpcChannel.ForAddress(推荐)
    // var channel = GrpcChannel.ForAddress($"https://{hostname}:{port}", new GrpcChannelOptions
    // {
    //     Credentials = Util.GetClientCertificate(config)
    // });
    // client = new CameraIOService.CameraIOServiceClient(channel);
}

2. 监听地址与客户端连接地址不匹配

配置中服务器监听的是0.0.0.0(表示监听所有网卡),但客户端不能直接连接0.0.0.0,这是一个通配符地址,不是可访问的目标地址。客户端应该使用实际的服务器IP(如127.0.0.1、局域网IP或公网IP)或者localhost。

修改配置文件中的客户端连接地址(或者在客户端代码中替换0.0.0.0为实际地址):

"GrpcService": {
    "Hostname": "localhost", // 或实际IP
    "Port": 3000
}

3. 证书过期可能性

证书生成脚本中设置EXPIRE_DAYS=7,证书有效期仅7天,昨日突然出现问题很可能是证书过期了。重新运行证书生成脚本,更新证书文件即可。

4. 额外验证点

  • 确认服务器确实在运行,且监听的端口是3000
  • 检查防火墙是否允许3000端口的通信
  • 确认客户端能ping通服务器的实际IP

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 17:20:26