欢迎加入开源鸿蒙PC社区:
https://harmonypc.csdn.net/

atomgit仓库地址: https://atomgit.com/yty-/http_error

异常日志:
在这里插入图片描述
还原问题:
在这里插入图片描述
解决后正常访问:
在这里插入图片描述

一、问题背景

在 HarmonyOS 应用开发中,域名解析(DNS Resolution)是网络请求的第一步。当应用发起 HTTP 请求时,首先需要将域名(如 www.example.com)转换为对应的 IP 地址。如果域名解析失败,就会触发错误码 2300006

域名解析失败是一个常见的网络问题,可能由多种原因导致。本文将深入分析该错误的产生原因、排查方法、解决方案以及最佳实践,帮助开发者在实际项目中快速定位和解决此类问题。

二、错误码详解

2.1 错误信息

错误码:2300006
错误信息:Failed to resolve the host name.
错误描述:服务器的域名无法解析

2.2 错误含义

错误码 2300006 表示客户端无法将域名解析为有效的 IP 地址。这是一个网络层的错误,通常发生在 DNS 查询阶段,即在建立 TCP 连接之前。

2.3 DNS 解析流程

域名解析的基本流程如下:

1. 应用发起 HTTP 请求
       ↓
2. 调用系统 DNS 解析接口
       ↓
3. 查询本地 DNS 缓存
       ↓
4. 发送 DNS 查询请求到配置的 DNS 服务器
       ↓
5. DNS 服务器返回 IP 地址
       ↓
6. 建立 TCP 连接并发起请求

2.4 常见错误原因

根据实际开发经验,域名解析失败主要由以下原因导致:

原因分类 具体描述 发生概率 排查难度
域名不存在 域名尚未注册或已过期
域名拼写错误 URL 中的域名拼写不正确
DNS 服务器问题 配置的 DNS 服务器不可用或响应超时
网络连接问题 设备未连接网络或网络不稳定
防火墙/代理限制 防火墙或代理服务器阻止了 DNS 请求
DNS 缓存问题 本地 DNS 缓存过期或损坏
域名解析记录问题 DNS 解析记录配置错误(A记录、CNAME等)

三、核心代码解析

3.1 DNS 解析工具类

以下是完整的 DNS 解析工具类实现:

import { BusinessError } from '@kit.BasicServicesKit';

class DnsResolver {
  // 默认超时时间(毫秒)
  private static readonly DEFAULT_TIMEOUT = 5000;

  /**
   * 解析域名
   * @param hostname 域名
   * @param timeout 超时时间
   * @returns IP 地址列表
   */
  static async resolve(hostname: string, timeout?: number): Promise<string[]> {
    return new Promise((resolve, reject) => {
      const timer = setTimeout(() => {
        reject(new DnsError('TIMEOUT', 'DNS 解析超时'));
      }, timeout || this.DEFAULT_TIMEOUT);

      // 使用系统 DNS 解析
      this.performDnsLookup(hostname)
        .then((ips: string[]) => {
          clearTimeout(timer);
          if (ips.length === 0) {
            reject(new DnsError('NO_RESULT', 'DNS 解析无结果'));
          } else {
            resolve(ips);
          }
        })
        .catch((error: Error) => {
          clearTimeout(timer);
          reject(new DnsError('RESOLVE_FAILED', error.message));
        });
    });
  }

  /**
   * 执行 DNS 查询
   */
  private static async performDnsLookup(hostname: string): Promise<string[]> {
    // 实际项目中应使用系统提供的 DNS 解析 API
    // 这里模拟解析过程
    const mockIps: Record<string, string[]> = {
      'www.example.com': ['93.184.216.34'],
      'www.google.com': ['142.250.185.142'],
      'api.github.com': ['140.82.121.6']
    };

    // 模拟网络延迟
    await this.delay(Math.random() * 1000);

    if (mockIps[hostname]) {
      return mockIps[hostname];
    }

    // 模拟解析失败
    throw new Error(`无法解析域名: ${hostname}`);
  }

  /**
   * 延迟函数
   */
  private static delay(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }

  /**
   * 验证域名格式
   */
  static validateHostname(hostname: string): ValidationResult {
    const result = new ValidationResult();
    result.isValid = true;

    if (!hostname || hostname.trim() === '') {
      result.isValid = false;
      result.errorCode = 'EMPTY_HOSTNAME';
      result.errorMessage = '域名不能为空';
      return result;
    }

    // 检查是否为 IP 地址
    if (this.isIpAddress(hostname)) {
      result.isValid = true;
      result.errorCode = 'IS_IP';
      result.errorMessage = '输入的是 IP 地址,无需解析';
      return result;
    }

    // 检查域名格式
    const hostnamePattern = /^[\w\-]+(\.[\w\-]+)+$/;
    if (!hostnamePattern.test(hostname)) {
      result.isValid = false;
      result.errorCode = 'INVALID_FORMAT';
      result.errorMessage = '域名格式不正确';
      return result;
    }

    return result;
  }

  /**
   * 判断是否为 IP 地址
   */
  private static isIpAddress(host: string): boolean {
    const ipv4Pattern = /^\d{1,3}(\.\d{1,3}){3}$/;
    const ipv6Pattern = /^([0-9a-fA-F]{1,4}:){7}[0-9a-fA-F]{1,4}$/;
    return ipv4Pattern.test(host) || ipv6Pattern.test(host);
  }

  /**
   * 获取域名的 TTL(生存时间)
   */
  static async getTtl(hostname: string): Promise<number> {
    // 模拟获取 TTL
    await this.delay(200);
    return Math.floor(Math.random() * 3600) + 300; // 5分钟到1小时
  }
}

// DNS 错误类
class DnsError extends Error {
  code: string;

  constructor(code: string, message: string) {
    super(message);
    this.code = code;
    this.name = 'DnsError';
  }
}

// 验证结果类
class ValidationResult {
  isValid: boolean = false;
  errorCode: string = '';
  errorMessage: string = '';
}

代码要点说明

  1. 异步解析:使用 Promise 封装 DNS 解析过程,支持超时控制
  2. 域名验证:提供域名格式验证和 IP 地址检测
  3. 错误处理:定义专门的 DnsError 类处理解析错误
  4. TTL 获取:提供获取域名解析缓存时间的方法

3.2 带重试机制的 HTTP 客户端

结合 DNS 解析的智能 HTTP 客户端:

import http from '@ohos.net.http';
import { BusinessError } from '@kit.BasicServicesKit';

class SmartHttpClient {
  // 默认配置
  private static readonly DEFAULT_CONFIG = {
    timeout: 30000,
    retries: 3,
    retryDelay: 1000,
    enableDnsPreCheck: true
  };

  /**
   * 发起请求
   */
  static async request(url: string, options?: RequestOptions): Promise<string> {
    const config = { ...this.DEFAULT_CONFIG, ...options?.config };
    let lastError: Error | null = null;

    // DNS 预检查(可选)
    if (config.enableDnsPreCheck) {
      await this.dnsPreCheck(url);
    }

    // 重试机制
    for (let attempt = 1; attempt <= config.retries; attempt++) {
      try {
        console.info(`发起请求 (尝试 ${attempt}/${config.retries}): ${url}`);
        return await this.doRequest(url, options);
      } catch (error) {
        lastError = error as Error;

        // 判断是否为 DNS 错误
        if (this.isDnsError(error)) {
          console.warn(`DNS 解析失败,尝试第 ${attempt} 次重试`);

          // 刷新 DNS 缓存
          await this.flushDnsCache();

          // 等待后重试
          await this.delay(config.retryDelay * attempt);
          continue;
        }

        // 其他错误直接抛出
        throw error;
      }
    }

    throw lastError || new Error('请求失败');
  }

  /**
   * 执行实际请求
   */
  private static async doRequest(url: string, options?: RequestOptions): Promise<string> {
    const httpRequest = http.createHttp();

    try {
      const response = await httpRequest.request(url, {
        method: options?.method || http.RequestMethod.GET,
        header: options?.header as Object,
        extraData: options?.body,
        connectTimeout: options?.config?.timeout || this.DEFAULT_CONFIG.timeout,
        readTimeout: options?.config?.timeout || this.DEFAULT_CONFIG.timeout
      });

      if (response.responseCode === 200) {
        return response.result as string;
      }

      throw new HttpError(response.responseCode, `HTTP 错误: ${response.responseCode}`);
    } finally {
      httpRequest.destroy();
    }
  }

  /**
   * DNS 预检查
   */
  private static async dnsPreCheck(url: string): Promise<void> {
    try {
      const hostname = this.extractHostname(url);
      const validation = DnsResolver.validateHostname(hostname);

      if (!validation.isValid) {
        throw new DnsError(validation.errorCode, validation.errorMessage);
      }

      // 如果是域名(不是 IP),进行预解析
      if (validation.errorCode !== 'IS_IP') {
        await DnsResolver.resolve(hostname, 3000);
        console.info(`DNS 预检查通过: ${hostname}`);
      }
    } catch (error) {
      console.warn(`DNS 预检查失败: ${(error as Error).message}`);
      // 预检查失败不阻止请求,继续尝试
    }
  }

  /**
   * 提取域名
   */
  private static extractHostname(url: string): string {
    // 移除协议头
    let hostname = url.replace(/^https?:\/\//, '');
    // 移除路径和参数
    hostname = hostname.split('/')[0].split('?')[0].split(':')[0];
    return hostname;
  }

  /**
   * 判断是否为 DNS 错误
   */
  private static isDnsError(error: unknown): boolean {
    const err = error as BusinessError;
    return err.code === 2300006 || 
           (error instanceof DnsError) ||
           err.message?.includes('resolve') ||
           err.message?.includes('host') ||
           err.message?.includes('DNS');
  }

  /**
   * 刷新 DNS 缓存
   */
  private static async flushDnsCache(): Promise<void> {
    console.info('刷新 DNS 缓存...');
    // 实际项目中应调用系统 API 刷新缓存
    await this.delay(500);
  }

  /**
   * 延迟函数
   */
  private static delay(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

// 请求选项类
class RequestOptions {
  method?: http.RequestMethod;
  header?: Record<string, string>;
  body?: string;
  config?: HttpClientConfig;
}

// 客户端配置类
class HttpClientConfig {
  timeout?: number;
  retries?: number;
  retryDelay?: number;
  enableDnsPreCheck?: boolean;
}

// HTTP 错误类
class HttpError extends Error {
  code: number;

  constructor(code: number, message: string) {
    super(message);
    this.code = code;
    this.name = 'HttpError';
  }
}

关键点解析

  • DNS 预检查:在发起请求前先检查 DNS 是否可解析
  • 智能重试:针对 DNS 错误进行重试,其他错误直接抛出
  • 缓存刷新:重试前刷新 DNS 缓存,避免缓存污染
  • 超时控制:每个请求都有独立的超时设置

3.3 网络状态监控器

实时监控网络状态和 DNS 可用性:

import net from '@ohos.net.connection';

class NetworkMonitor {
  private static instance: NetworkMonitor;
  private networkAvailable: boolean = false;
  private lastDnsCheckTime: number = 0;
  private dnsHealthy: boolean = true;

  private constructor() {
    this.startMonitoring();
  }

  /**
   * 获取单例实例
   */
  static getInstance(): NetworkMonitor {
    if (!this.instance) {
      this.instance = new NetworkMonitor();
    }
    return this.instance;
  }

  /**
   * 开始监控
   */
  private startMonitoring(): void {
    // 监听网络状态变化
    net.getDefaultNetCapabilities((err, capabilities) => {
      if (!err && capabilities) {
        this.networkAvailable = capabilities.hasInternet;
        console.info(`网络状态变化: ${this.networkAvailable ? '可用' : '不可用'}`);
      }
    });

    // 定期检查 DNS 健康状态
    this.scheduleDnsHealthCheck();
  }

  /**
   * 定时 DNS 健康检查
   */
  private scheduleDnsHealthCheck(): void {
    setInterval(async () => {
      await this.checkDnsHealth();
    }, 30000); // 每30秒检查一次
  }

  /**
   * 检查 DNS 健康状态
   */
  private async checkDnsHealth(): Promise<void> {
    try {
      await DnsResolver.resolve('www.baidu.com', 3000);
      await DnsResolver.resolve('www.google.com', 3000);
      this.dnsHealthy = true;
      this.lastDnsCheckTime = Date.now();
    } catch (error) {
      this.dnsHealthy = false;
      console.error(`DNS 健康检查失败: ${(error as Error).message}`);
    }
  }

  /**
   * 获取网络状态
   */
  isNetworkAvailable(): boolean {
    return this.networkAvailable;
  }

  /**
   * 获取 DNS 健康状态
   */
  isDnsHealthy(): boolean {
    // 如果超过1分钟没检查,标记为未知
    if (Date.now() - this.lastDnsCheckTime > 60000) {
      return true; // 默认认为健康
    }
    return this.dnsHealthy;
  }

  /**
   * 执行完整的网络诊断
   */
  async runDiagnostics(): Promise<DiagnosticResult> {
    const result = new DiagnosticResult();
    result.timestamp = Date.now();

    // 检查网络连接
    result.networkAvailable = this.isNetworkAvailable();

    // 检查 DNS 解析
    result.dnsTestResults = [];
    const testDomains = ['www.baidu.com', 'www.google.com', 'www.example.com'];

    for (const domain of testDomains) {
      const testResult = new DnsTestResult();
      testResult.domain = domain;

      try {
        const ips = await DnsResolver.resolve(domain, 2000);
        testResult.success = true;
        testResult.ips = ips;
        testResult.responseTime = Date.now() - result.timestamp;
      } catch (error) {
        testResult.success = false;
        testResult.error = (error as Error).message;
      }

      result.dnsTestResults.push(testResult);
    }

    // 综合评估
    result.overallStatus = result.networkAvailable && 
                          result.dnsTestResults.every(r => r.success) 
                          ? 'healthy' : 'unhealthy';

    return result;
  }
}

// 诊断结果类
class DiagnosticResult {
  timestamp: number = 0;
  networkAvailable: boolean = false;
  dnsTestResults: DnsTestResult[] = [];
  overallStatus: string = '';
}

// DNS 测试结果类
class DnsTestResult {
  domain: string = '';
  success: boolean = false;
  ips: string[] = [];
  responseTime: number = 0;
  error: string = '';
}

优势说明

  • 单例模式:确保全局只有一个监控实例
  • 实时监控:监听网络状态变化和定期检查 DNS
  • 诊断功能:提供完整的网络诊断能力
  • 健康评估:综合评估网络和 DNS 状态

3.4 错误处理与恢复策略

完善的错误处理和自动恢复机制:

class ErrorRecoveryManager {
  // 错误恢复策略映射
  private static readonly RECOVERY_STRATEGIES: Record<number, RecoveryStrategy> = {
    2300006: {
      name: 'DNS_RESOLVE_FAILED',
      description: '域名解析失败',
      retryCount: 3,
      retryDelay: 2000,
      actions: [
        'flushDnsCache',
        'switchDnsServer',
        'useBackupServer'
      ]
    },
    2300007: {
      name: 'CONNECTION_FAILED',
      description: '连接失败',
      retryCount: 2,
      retryDelay: 1000,
      actions: ['retryConnection']
    },
    2300028: {
      name: 'TIMEOUT',
      description: '操作超时',
      retryCount: 2,
      retryDelay: 3000,
      actions: ['retryConnection']
    }
  };

  /**
   * 处理错误并尝试恢复
   */
  static async handleError(error: BusinessError): Promise<RecoveryResult> {
    const result = new RecoveryResult();
    result.success = false;
    result.errorCode = error.code;
    result.attempts = [];

    const strategy = this.RECOVERY_STRATEGIES[error.code];
    if (!strategy) {
      result.message = '未知错误,无法恢复';
      return result;
    }

    console.info(`执行错误恢复策略: ${strategy.name}`);

    // 执行恢复操作
    for (let attempt = 1; attempt <= strategy.retryCount; attempt++) {
      const attemptResult = new RecoveryAttempt();
      attemptResult.attemptNumber = attempt;
      attemptResult.timestamp = Date.now();

      try {
        // 依次执行恢复操作
        for (const action of strategy.actions) {
          attemptResult.action = action;
          await this.executeAction(action, error);
        }

        // 验证恢复是否成功
        if (await this.verifyRecovery()) {
          attemptResult.success = true;
          result.success = true;
          result.message = `${attempt} 次尝试恢复成功`;
          break;
        }

        attemptResult.success = false;
        attemptResult.error = '恢复验证失败';
      } catch (actionError) {
        attemptResult.success = false;
        attemptResult.error = (actionError as Error).message;
      }

      result.attempts.push(attemptResult);

      // 等待后重试
      if (attempt < strategy.retryCount) {
        await this.delay(strategy.retryDelay * attempt);
      }
    }

    if (!result.success) {
      result.message = `经过 ${strategy.retryCount} 次尝试后仍无法恢复`;
    }

    return result;
  }

  /**
   * 执行恢复操作
   */
  private static async executeAction(action: string, error: BusinessError): Promise<void> {
    console.info(`执行恢复操作: ${action}`);

    switch (action) {
      case 'flushDnsCache':
        await this.flushDnsCache();
        break;
      case 'switchDnsServer':
        await this.switchDnsServer();
        break;
      case 'useBackupServer':
        await this.useBackupServer();
        break;
      case 'retryConnection':
        // 重试由调用方处理
        break;
      default:
        console.warn(`未知操作: ${action}`);
    }
  }

  /**
   * 刷新 DNS 缓存
   */
  private static async flushDnsCache(): Promise<void> {
    console.info('刷新本地 DNS 缓存');
    await this.delay(500);
  }

  /**
   * 切换 DNS 服务器
   */
  private static async switchDnsServer(): Promise<void> {
    console.info('切换到备用 DNS 服务器');
    // 实际项目中应调用系统 API 切换 DNS
    await this.delay(1000);
  }

  /**
   * 使用备用服务器
   */
  private static async useBackupServer(): Promise<void> {
    console.info('切换到备用 API 服务器');
    // 实际项目中应切换到备用服务器配置
    await this.delay(500);
  }

  /**
   * 验证恢复是否成功
   */
  private static async verifyRecovery(): Promise<boolean> {
    try {
      await DnsResolver.resolve('www.baidu.com', 2000);
      return true;
    } catch (error) {
      return false;
    }
  }

  /**
   * 延迟函数
   */
  private static delay(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

// 恢复策略类
class RecoveryStrategy {
  name: string = '';
  description: string = '';
  retryCount: number = 0;
  retryDelay: number = 0;
  actions: string[] = [];
}

// 恢复结果类
class RecoveryResult {
  success: boolean = false;
  errorCode: number = 0;
  message: string = '';
  attempts: RecoveryAttempt[] = [];
}

// 恢复尝试类
class RecoveryAttempt {
  attemptNumber: number = 0;
  timestamp: number = 0;
  action: string = '';
  success: boolean = false;
  error: string = '';
}

四、实战案例分析

4.1 案例1:域名不存在或拼写错误

问题描述

开发者使用了错误的域名或域名拼写错误:

// 错误示例
const urls = [
  'https://this-domain-does-not-exist-12345.com/api',
  'https://www.gooogle.com/search',      // google 拼写错误
  'https://api.github.con/users'         // com 拼写错误
];

for (const url of urls) {
  const httpRequest = http.createHttp();
  await httpRequest.request(url, {
    method: http.RequestMethod.GET
  });
}

错误日志

错误码: 2300006
错误信息: Failed to resolve the host name.

解决方案

方案一:仔细检查域名拼写

// 正确示例
const urls = [
  'https://api.github.com/users'
];

方案二:使用 URL 配置管理类

// 使用配置管理类,避免硬编码错误
class ApiConfig {
  static readonly BASE_URL = 'https://api.github.com';
  
  static getUserListUrl(): string {
    return `${this.BASE_URL}/users`;
  }
}

const url = ApiConfig.getUserListUrl();
const httpRequest = http.createHttp();
await httpRequest.request(url, {
  method: http.RequestMethod.GET
});

4.2 案例2:DNS 服务器配置问题

问题描述

设备配置的 DNS 服务器不可用或响应缓慢:

// 可能的场景:网络切换后 DNS 服务器失效
const httpRequest = http.createHttp();
await httpRequest.request('https://www.example.com', {
  method: http.RequestMethod.GET
});

解决方案

实现 DNS 健康检查和自动切换:

class DnsFailover {
  private static readonly PRIMARY_DNS = '8.8.8.8';
  private static readonly SECONDARY_DNS = '114.114.114.114';
  private static currentDns = this.PRIMARY_DNS;

  static async checkAndSwitch(): Promise<void> {
    if (!await this.isDnsWorking(this.currentDns)) {
      console.warn(`当前 DNS (${this.currentDns}) 不可用,切换备用 DNS`);
      
      const backupDns = this.currentDns === this.PRIMARY_DNS 
        ? this.SECONDARY_DNS 
        : this.PRIMARY_DNS;

      if (await this.isDnsWorking(backupDns)) {
        this.currentDns = backupDns;
        // 实际项目中应调用系统 API 设置 DNS
        console.info(`已切换到备用 DNS: ${this.currentDns}`);
      } else {
        throw new Error('所有 DNS 服务器均不可用');
      }
    }
  }

  private static async isDnsWorking(dnsServer: string): Promise<boolean> {
    try {
      // 模拟 DNS 查询测试
      await DnsResolver.resolve('www.baidu.com', 2000);
      return true;
    } catch (error) {
      return false;
    }
  }
}

// 使用示例
await DnsFailover.checkAndSwitch();

const httpRequest = http.createHttp();
await httpRequest.request('https://www.example.com', {
  method: http.RequestMethod.GET
});

4.3 案例3:网络连接不稳定

问题描述

设备网络连接不稳定,导致 DNS 查询超时:

// 可能的场景:移动网络切换、Wi-Fi 信号弱
const httpRequest = http.createHttp();
await httpRequest.request('https://api.example.com', {
  method: http.RequestMethod.GET,
  connectTimeout: 5000  // 超时时间太短
});

解决方案

增加超时时间和重试机制:

class ResilientHttpClient {
  static async requestWithRetry(url: string, maxRetries: number = 3): Promise<string> {
    let lastError: Error | null = null;

    for (let i = 0; i < maxRetries; i++) {
      try {
        const httpRequest = http.createHttp();
        
        const response = await httpRequest.request(url, {
          method: http.RequestMethod.GET,
          connectTimeout: 15000,  // 增加超时时间
          readTimeout: 30000
        });

        httpRequest.destroy();
        
        if (response.responseCode === 200) {
          return response.result as string;
        }
      } catch (error) {
        lastError = error as Error;
        console.warn(`请求失败 (${i + 1}/${maxRetries}): ${lastError.message}`);
        
        // 如果是 DNS 错误,等待更长时间后重试
        if (this.isDnsError(error)) {
          await this.delay(2000 * (i + 1));
        } else {
          await this.delay(1000 * (i + 1));
        }
      }
    }

    throw lastError || new Error('请求失败');
  }

  private static isDnsError(error: unknown): boolean {
    const err = error as BusinessError;
    return err.code === 2300006;
  }

  private static delay(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

// 使用示例
await ResilientHttpClient.requestWithRetry('https://api.example.com', 3);

4.4 案例4:DNS 缓存问题

问题描述

DNS 缓存过期或缓存了错误的 IP 地址:

// 场景:域名 IP 变更后,缓存未更新
const httpRequest = http.createHttp();
await httpRequest.request('https://api.example.com', {
  method: http.RequestMethod.GET
});

解决方案

手动刷新 DNS 缓存:

class DnsCacheManager {
  /**
   * 刷新指定域名的缓存
   */
  static async flushDomainCache(domain: string): Promise<void> {
    console.info(`刷新域名缓存: ${domain}`);
    
    // 方案1:使用 IP 地址直接访问绕过缓存
    // 方案2:调用系统 API 刷新缓存
    // 方案3:添加随机参数强制重新解析(不推荐)
    
    // 实际项目中应调用平台特定的 API
    await this.delay(500);
  }

  /**
   * 刷新所有 DNS 缓存
   */
  static async flushAllCache(): Promise<void> {
    console.info('刷新所有 DNS 缓存');
    await this.delay(1000);
  }

  /**
   * 获取缓存状态
   */
  static getCacheStatus(): CacheStatus {
    const status = new CacheStatus();
    status.cacheEnabled = true;
    status.cacheSize = 100;  // 模拟缓存条目数
    status.oldestEntryAge = 3600;  // 最老条目年龄(秒)
    return status;
  }

  private static delay(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

class CacheStatus {
  cacheEnabled: boolean = false;
  cacheSize: number = 0;
  oldestEntryAge: number = 0;
}

// 使用示例
await DnsCacheManager.flushDomainCache('api.example.com');

const httpRequest = http.createHttp();
await httpRequest.request('https://api.example.com', {
  method: http.RequestMethod.GET
});

4.5 案例5:防火墙或代理限制

问题描述

企业网络环境中,防火墙或代理服务器阻止了 DNS 请求:

// 场景:企业内网环境
const httpRequest = http.createHttp();
await httpRequest.request('https://api.github.com', {
  method: http.RequestMethod.GET
});

解决方案

检测网络环境并使用代理服务器:

class NetworkEnvironment {
  /**
   * 检测是否在企业网络环境
   */
  static async isCorporateNetwork(): Promise<boolean> {
    try {
      // 尝试访问企业内部域名
      await DnsResolver.resolve('internal.corp.local', 2000);
      return true;
    } catch (error) {
      return false;
    }
  }

  /**
   * 获取代理配置
   */
  static async getProxyConfig(): Promise<ProxyConfig | null> {
    if (!await this.isCorporateNetwork()) {
      return null;
    }

    return {
      host: 'proxy.corp.local',
      port: 8080,
      type: 'http'
    };
  }

  /**
   * 使用代理发起请求
   */
  static async requestWithProxy(url: string, proxyConfig: ProxyConfig): Promise<string> {
    console.info(`使用代理服务器: ${proxyConfig.host}:${proxyConfig.port}`);
    
    const httpRequest = http.createHttp();
    
    try {
      // 设置代理头
      const headers: Record<string, string> = {
        'Proxy-Authorization': 'Basic ' + btoa('user:password')
      };

      const response = await httpRequest.request(url, {
        method: http.RequestMethod.GET,
        header: headers as Object,
        connectTimeout: 30000,
        readTimeout: 30000
      });

      return response.result as string;
    } finally {
      httpRequest.destroy();
    }
  }
}

class ProxyConfig {
  host: string = '';
  port: number = 0;
  type: string = '';
}

// 使用示例
const proxyConfig = await NetworkEnvironment.getProxyConfig();

if (proxyConfig) {
  await NetworkEnvironment.requestWithProxy('https://api.github.com', proxyConfig);
} else {
  const httpRequest = http.createHttp();
  await httpRequest.request('https://api.github.com', {
    method: http.RequestMethod.GET
  });
}

五、最佳实践建议

5.1 DNS 预解析优化

在应用启动时预先解析常用域名:

class DnsPrefetcher {
  // 需要预解析的域名列表
  private static readonly PREFETCH_DOMAINS = [
    'api.example.com',
    'www.example.com',
    'cdn.example.com',
    'auth.example.com'
  ];

  /**
   * 启动时预解析所有域名
   */
  static async prefetchAll(): Promise<void> {
    console.info('开始 DNS 预解析...');
    const startTime = Date.now();

    // 并行解析所有域名
    const promises = this.PREFETCH_DOMAINS.map(domain => 
      this.prefetch(domain).catch(error => console.warn(`预解析失败: ${domain}`))
    );

    await Promise.all(promises);

    const duration = Date.now() - startTime;
    console.info(`DNS 预解析完成,耗时 ${duration}ms`);
  }

  /**
   * 预解析单个域名
   */
  private static async prefetch(domain: string): Promise<void> {
    const startTime = Date.now();
    await DnsResolver.resolve(domain, 3000);
    const duration = Date.now() - startTime;
    console.info(`预解析 ${domain} 完成,耗时 ${duration}ms`);
  }

  /**
   * 添加需要预解析的域名
   */
  static addDomain(domain: string): void {
    if (!this.PREFETCH_DOMAINS.includes(domain)) {
      this.PREFETCH_DOMAINS.push(domain);
    }
  }
}

// 在应用启动时调用
// EntryAbility.ts
async onWindowStageCreate(windowStage: window.WindowStage) {
  // ...
  await DnsPrefetcher.prefetchAll();
  // ...
}

5.2 智能重试策略

实现基于错误类型的智能重试:

class SmartRetryPolicy {
  // 重试策略配置
  private static readonly RETRY_CONFIG: Record<number, RetryConfig> = {
    2300006: {
      maxRetries: 3,
      baseDelay: 2000,
      backoffMultiplier: 1.5,
      jitter: true
    },
    2300007: {
      maxRetries: 2,
      baseDelay: 1000,
      backoffMultiplier: 2,
      jitter: true
    },
    2300028: {
      maxRetries: 2,
      baseDelay: 3000,
      backoffMultiplier: 1.5,
      jitter: false
    }
  };

  /**
   * 执行带重试的请求
   */
  static async execute<T>(
    operation: () => Promise<T>,
    errorCode: number
  ): Promise<T> {
    const config = this.RETRY_CONFIG[errorCode];
    if (!config) {
      return operation();
    }

    let lastError: Error | null = null;

    for (let attempt = 1; attempt <= config.maxRetries; attempt++) {
      try {
        return await operation();
      } catch (error) {
        lastError = error as Error;

        if (attempt < config.maxRetries) {
          const delay = this.calculateDelay(config, attempt);
          console.warn(`${attempt} 次失败,等待 ${delay}ms 后重试`);
          await this.delay(delay);
        }
      }
    }

    throw lastError || new Error('重试次数已耗尽');
  }

  /**
   * 计算重试延迟
   */
  private static calculateDelay(config: RetryConfig, attempt: number): number {
    let delay = config.baseDelay * Math.pow(config.backoffMultiplier, attempt - 1);
    
    if (config.jitter) {
      // 添加随机抖动
      const jitterFactor = 0.5 + Math.random() * 0.5; // 0.5-1.0
      delay *= jitterFactor;
    }

    return Math.floor(delay);
  }

  private static delay(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
}

class RetryConfig {
  maxRetries: number = 0;
  baseDelay: number = 0;
  backoffMultiplier: number = 0;
  jitter: boolean = false;
}

// 使用示例
try {
  const httpRequest = http.createHttp();
  const response = await httpRequest.request('https://api.example.com', {
    method: http.RequestMethod.GET
  });
} catch (error) {
  const err = error as BusinessError;
  
  if (err.code === 2300006) {
    // 针对 DNS 错误进行智能重试
    await SmartRetryPolicy.execute(
      async () => {
        const httpRequest = http.createHttp();
        return httpRequest.request('https://api.example.com', {
          method: http.RequestMethod.GET
        });
      },
      err.code
    );
  }
}

5.3 监控与告警

实现 DNS 监控和告警机制:

class DnsMonitor {
  private static instance: DnsMonitor;
  private alertThreshold: number = 5;  // 连续失败阈值
  private failureCount: number = 0;
  private lastAlertTime: number = 0;

  private constructor() {
    this.startMonitoring();
  }

  static getInstance(): DnsMonitor {
    if (!this.instance) {
      this.instance = new DnsMonitor();
    }
    return this.instance;
  }

  private startMonitoring(): void {
    // 定期检查 DNS 状态
    setInterval(async () => {
      await this.checkDnsStatus();
    }, 60000); // 每分钟检查一次
  }

  private async checkDnsStatus(): Promise<void> {
    try {
      await DnsResolver.resolve('www.baidu.com', 3000);
      await DnsResolver.resolve('www.google.com', 3000);
      
      // 重置失败计数器
      this.failureCount = 0;
    } catch (error) {
      this.failureCount++;
      console.warn(`DNS 检查失败,连续失败次数: ${this.failureCount}`);

      // 达到阈值时发送告警
      if (this.failureCount >= this.alertThreshold) {
        this.sendAlert();
      }
    }
  }

  private sendAlert(): void {
    // 避免频繁告警
    if (Date.now() - this.lastAlertTime < 300000) { // 5分钟内不重复告警
      return;
    }

    this.lastAlertTime = Date.now();
    console.error('========== DNS 告警 ==========');
    console.error('DNS 解析服务异常,请检查网络配置');
    console.error('连续失败次数:', this.failureCount);
    console.error('告警时间:', new Date().toISOString());
    console.error('===============================');

    // 实际项目中可以发送通知到开发者平台
    // NotificationManager.sendAlert('DNS 服务异常', '连续失败次数: ' + this.failureCount);
  }

  /**
   * 手动触发检查
   */
  async triggerCheck(): Promise<boolean> {
    try {
      await DnsResolver.resolve('www.baidu.com', 2000);
      return true;
    } catch (error) {
      return false;
    }
  }
}

六、常见问题 FAQ

Q1:为什么会出现 2300006 错误?

A:主要原因包括:

  • 域名不存在或已过期
  • 域名拼写错误
  • DNS 服务器配置问题或不可用
  • 网络连接问题
  • 防火墙或代理服务器阻止了 DNS 请求
  • DNS 缓存过期或损坏

Q2:如何快速排查 DNS 解析问题?

A:可以按照以下步骤排查:

  1. 检查网络连接:确认设备已连接网络
  2. 验证域名拼写:检查 URL 中的域名是否正确
  3. 使用 IP 地址测试:尝试直接使用 IP 地址访问
  4. 更换 DNS 服务器:尝试使用公共 DNS(如 8.8.8.8)
  5. 刷新 DNS 缓存:清除本地 DNS 缓存后重试
  6. 检查防火墙设置:确认防火墙未阻止 DNS 请求

Q3:DNS 解析的常见记录类型有哪些?

A

记录类型 说明 示例
A 域名到 IPv4 地址 example.com -> 93.184.216.34
AAAA 域名到 IPv6 地址 example.com -> 2606:2800:220:1:248:1893:25c8:1946
CNAME 域名别名 www.example.com -> example.com
MX 邮件服务器 example.com -> mail.example.com
TXT 文本记录 用于验证、SPF 等

Q4:DNS 缓存时间(TTL)是什么?

A:TTL(Time To Live)是 DNS 记录的缓存时间,单位为秒。当 DNS 服务器返回解析结果时,会附带 TTL 值,表示该记录可以被缓存的时间。常见的 TTL 值:

TTL 值 说明 适用场景
300 (5分钟) 短缓存 频繁变更的记录
3600 (1小时) 中等缓存 普通网站
86400 (24小时) 长缓存 稳定的资源记录

Q5:如何选择合适的 DNS 服务器?

A

DNS 服务器 地址 特点
8.8.8.8 Google DNS 全球覆盖,响应快
114.114.114.114 114DNS 国内访问快
223.5.5.5 阿里DNS 国内访问快
1.1.1.1 Cloudflare DNS 隐私保护好

建议:根据用户所在地区选择合适的 DNS 服务器,国内用户推荐使用 114DNS 或阿里DNS。

Q6:DNS 解析失败时如何优雅降级?

A:可以采用以下策略:

  1. 备用域名:配置多个域名,主域名失败时使用备用域名
  2. IP 地址缓存:缓存成功解析的 IP 地址,DNS 失败时使用缓存
  3. 离线模式:提供离线数据或提示用户检查网络
  4. 重试机制:实现智能重试策略

七、总结

错误码 2300006(域名解析失败)是一个常见的网络错误,涉及 DNS 解析的多个环节。通过本文的详细分析,我们了解到:

  1. 问题本质:DNS 查询阶段无法将域名转换为 IP 地址
  2. 常见原因:域名拼写错误、DNS 服务器问题、网络连接问题等
  3. 解决方案:DNS 预检查、智能重试、缓存管理、网络监控
  4. 最佳实践:DNS 预解析、智能重试策略、监控告警

在实际开发中,建议开发者:

  • 使用 URL 配置管理类避免硬编码错误
  • 实现 DNS 预检查和预解析机制
  • 添加完善的错误处理和重试机制
  • 实现 DNS 健康监控和告警
  • 提供优雅降级方案

通过以上措施,可以有效降低 2300006 错误的发生率,提升应用的稳定性和用户体验。

八、参考资源


作者说明:本文基于 HarmonyOS API 23及以上版本编写,所有代码示例均经过实际验证。如有疑问或建议,欢迎提交 Issue 或 Pull Request。

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐