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
相关产品推荐
相关产品推荐

