网络质量评估:HarmonyOS PC开发中带宽与延迟检测

网络质量评估:HarmonyOS PC开发中带宽与延迟检测

知己知彼,百战不殆------了解网络质量才能优化用户体验

一、为什么需要网络质量评估?

知道"有网"和"没网"只是第一步,更精细的问题是:网络有多好?

同样的"有网"状态,WiFi千兆光纤和电梯里的微弱信号,用户体验天差地别。如果应用能感知网络质量,就能:

智能降级:弱网环境下降低图片质量、减少请求频率、关闭实时推送,避免用户等待超时。

预加载决策:网络好时提前加载下一页数据、预缓存视频,网络差时暂停预加载节省流量。

故障预警:网络质量持续下降时,提前提示用户可能遇到问题,而不是等到请求失败才发现。

性能优化:根据网络质量选择最优CDN节点、调整请求并发数、选择合适的协议。

用户体验报告:收集用户的网络质量数据,分析问题原因,优化产品策略。

鸿蒙系统提供了网络质量评估API,结合自定义检测方案,可以全面评估网络质量。

二、核心原理:网络质量指标体系

2.1 核心质量指标

#mermaid-svg-f20TpanFhtZEzUZU{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}@keyframes edge-animation-frame{from{stroke-dashoffset:0;}}@keyframes dash{to{stroke-dashoffset:0;}}#mermaid-svg-f20TpanFhtZEzUZU .edge-animation-slow{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 50s linear infinite;stroke-linecap:round;}#mermaid-svg-f20TpanFhtZEzUZU .edge-animation-fast{stroke-dasharray:9,5!important;stroke-dashoffset:900;animation:dash 20s linear infinite;stroke-linecap:round;}#mermaid-svg-f20TpanFhtZEzUZU .error-icon{fill:#552222;}#mermaid-svg-f20TpanFhtZEzUZU .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-f20TpanFhtZEzUZU .edge-thickness-normal{stroke-width:1px;}#mermaid-svg-f20TpanFhtZEzUZU .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-f20TpanFhtZEzUZU .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-f20TpanFhtZEzUZU .edge-thickness-invisible{stroke-width:0;fill:none;}#mermaid-svg-f20TpanFhtZEzUZU .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-f20TpanFhtZEzUZU .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-f20TpanFhtZEzUZU .marker{fill:#333333;stroke:#333333;}#mermaid-svg-f20TpanFhtZEzUZU .marker.cross{stroke:#333333;}#mermaid-svg-f20TpanFhtZEzUZU svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-f20TpanFhtZEzUZU p{margin:0;}#mermaid-svg-f20TpanFhtZEzUZU .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-f20TpanFhtZEzUZU .cluster-label text{fill:#333;}#mermaid-svg-f20TpanFhtZEzUZU .cluster-label span{color:#333;}#mermaid-svg-f20TpanFhtZEzUZU .cluster-label span p{background-color:transparent;}#mermaid-svg-f20TpanFhtZEzUZU .label text,#mermaid-svg-f20TpanFhtZEzUZU span{fill:#333;color:#333;}#mermaid-svg-f20TpanFhtZEzUZU .node rect,#mermaid-svg-f20TpanFhtZEzUZU .node circle,#mermaid-svg-f20TpanFhtZEzUZU .node ellipse,#mermaid-svg-f20TpanFhtZEzUZU .node polygon,#mermaid-svg-f20TpanFhtZEzUZU .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-f20TpanFhtZEzUZU .rough-node .label text,#mermaid-svg-f20TpanFhtZEzUZU .node .label text,#mermaid-svg-f20TpanFhtZEzUZU .image-shape .label,#mermaid-svg-f20TpanFhtZEzUZU .icon-shape .label{text-anchor:middle;}#mermaid-svg-f20TpanFhtZEzUZU .node .katex path{fill:#000;stroke:#000;stroke-width:1px;}#mermaid-svg-f20TpanFhtZEzUZU .rough-node .label,#mermaid-svg-f20TpanFhtZEzUZU .node .label,#mermaid-svg-f20TpanFhtZEzUZU .image-shape .label,#mermaid-svg-f20TpanFhtZEzUZU .icon-shape .label{text-align:center;}#mermaid-svg-f20TpanFhtZEzUZU .node.clickable{cursor:pointer;}#mermaid-svg-f20TpanFhtZEzUZU .root .anchor path{fill:#333333!important;stroke-width:0;stroke:#333333;}#mermaid-svg-f20TpanFhtZEzUZU .arrowheadPath{fill:#333333;}#mermaid-svg-f20TpanFhtZEzUZU .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-f20TpanFhtZEzUZU .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-f20TpanFhtZEzUZU .edgeLabel{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-f20TpanFhtZEzUZU .edgeLabel p{background-color:rgba(232,232,232, 0.8);}#mermaid-svg-f20TpanFhtZEzUZU .edgeLabel rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-f20TpanFhtZEzUZU .labelBkg{background-color:rgba(232, 232, 232, 0.5);}#mermaid-svg-f20TpanFhtZEzUZU .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-f20TpanFhtZEzUZU .cluster text{fill:#333;}#mermaid-svg-f20TpanFhtZEzUZU .cluster span{color:#333;}#mermaid-svg-f20TpanFhtZEzUZU div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-f20TpanFhtZEzUZU .flowchartTitleText{text-anchor:middle;font-size:18px;fill:#333;}#mermaid-svg-f20TpanFhtZEzUZU rect.text{fill:none;stroke-width:0;}#mermaid-svg-f20TpanFhtZEzUZU .icon-shape,#mermaid-svg-f20TpanFhtZEzUZU .image-shape{background-color:rgba(232,232,232, 0.8);text-align:center;}#mermaid-svg-f20TpanFhtZEzUZU .icon-shape p,#mermaid-svg-f20TpanFhtZEzUZU .image-shape p{background-color:rgba(232,232,232, 0.8);padding:2px;}#mermaid-svg-f20TpanFhtZEzUZU .icon-shape .label rect,#mermaid-svg-f20TpanFhtZEzUZU .image-shape .label rect{opacity:0.5;background-color:rgba(232,232,232, 0.8);fill:rgba(232,232,232, 0.8);}#mermaid-svg-f20TpanFhtZEzUZU .label-icon{display:inline-block;height:1em;overflow:visible;vertical-align:-0.125em;}#mermaid-svg-f20TpanFhtZEzUZU .node .label-icon path{fill:currentColor;stroke:revert;stroke-width:revert;}#mermaid-svg-f20TpanFhtZEzUZU :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;}#mermaid-svg-f20TpanFhtZEzUZU .primary>*{fill:#4A90E2!important;stroke:#2E5C8A!important;stroke-width:2px!important;color:#fff!important;}#mermaid-svg-f20TpanFhtZEzUZU .primary span{fill:#4A90E2!important;stroke:#2E5C8A!important;stroke-width:2px!important;color:#fff!important;}#mermaid-svg-f20TpanFhtZEzUZU .primary tspan{fill:#fff!important;}#mermaid-svg-f20TpanFhtZEzUZU .warning>*{fill:#F5A623!important;stroke:#C17D10!important;stroke-width:2px!important;color:#fff!important;}#mermaid-svg-f20TpanFhtZEzUZU .warning span{fill:#F5A623!important;stroke:#C17D10!important;stroke-width:2px!important;color:#fff!important;}#mermaid-svg-f20TpanFhtZEzUZU .warning tspan{fill:#fff!important;}#mermaid-svg-f20TpanFhtZEzUZU .info>*{fill:#7ED321!important;stroke:#5BA318!important;stroke-width:2px!important;color:#fff!important;}#mermaid-svg-f20TpanFhtZEzUZU .info span{fill:#7ED321!important;stroke:#5BA318!important;stroke-width:2px!important;color:#fff!important;}#mermaid-svg-f20TpanFhtZEzUZU .info tspan{fill:#fff!important;} 网络质量评估
带宽指标
延迟指标
稳定性指标
下行带宽

下载速度
上行带宽

上传速度
可用带宽

当前可利用带宽
RTT往返时延

请求响应时间
DNS解析时延

域名解析时间
TCP连接时延

握手时间
丢包率

数据包丢失比例
抖动

延迟波动幅度
重传率

需要重传的比例

2.2 质量等级划分

等级 带宽 延迟 丢包率 用户体验
优秀 >10Mbps <50ms <0.1% 流畅无感知
良好 5-10Mbps 50-100ms 0.1-0.5% 基本流畅
一般 1-5Mbps 100-200ms 0.5-1% 可接受
较差 0.5-1Mbps 200-500ms 1-3% 明显卡顿
很差 <0.5Mbps >500ms >3% 几乎不可用

2.3 检测方法对比

方法 原理 优点 缺点 适用场景
下载测速 下载已知大小文件,计算速度 直观准确 消耗流量 初始评估
Ping检测 发送小包测量RTT 开销小 可能被限制 持续监测
请求采样 记录实际请求的性能 真实数据 需要积累 日常运行
系统API 获取系统网络信息 无额外开销 信息有限 快速评估

三、代码实战:三种检测方案

场景一:带宽测速实现

通过下载测试文件测量实际带宽。

typescript 复制代码
import http from '@ohos.net.http';
import { BusinessError } from '@ohos.base';

// 测速结果
interface SpeedTestResult {
  downloadSpeed: number;      // 下载速度(KB/s)
  uploadSpeed: number;        // 上传速度(KB/s)
  latency: number;            // 延迟(ms)
  jitter: number;             // 抖动(ms)
  timestamp: number;          // 测试时间
  testDuration: number;       // 测试耗时(ms)
}

// 测速配置
interface SpeedTestConfig {
  testUrl: string;            // 测试文件URL
  testSize: number;           // 测试文件大小(字节)
  timeout: number;            // 超时时间(ms)
  samples: number;            // 采样次数
  warmupSamples: number;      // 预热次数(不计入结果)
}

// 默认配置
const DEFAULT_SPEED_TEST_CONFIG: SpeedTestConfig = {
  testUrl: 'https://speed.example.com/test.bin',
  testSize: 1024 * 1024,  // 1MB
  timeout: 30000,
  samples: 5,
  warmupSamples: 2
};

// 带宽测速器
class BandwidthTester {
  private config: SpeedTestConfig;
  
  // 回调
  private onProgress: ((progress: number, speed: number) => void) | null = null;
  
  constructor(config: Partial<SpeedTestConfig> = {}) {
    this.config = { ...DEFAULT_SPEED_TEST_CONFIG, ...config };
  }
  
  // 执行测速
  async runTest(): Promise<SpeedTestResult> {
    console.info('开始带宽测速');
  
    let startTime = Date.now();
  
    // 1. 预热
    console.info('预热测试...');
    for (let i = 0; i < this.config.warmupSamples; i++) {
      await this.downloadTestFile();
    }
  
    // 2. 正式测试
    let speeds: number[] = [];
    let latencies: number[] = [];
  
    for (let i = 0; i < this.config.samples; i++) {
      let result = await this.downloadTestFile();
    
      speeds.push(result.speed);
      latencies.push(result.latency);
    
      // 通知进度
      let progress = (i + 1) / this.config.samples;
      let avgSpeed = this.average(speeds);
      this.onProgress?.(progress, avgSpeed);
    
      console.info(`测试 ${i + 1}/${this.config.samples}: ${result.speed.toFixed(2)} KB/s, ${result.latency.toFixed(2)} ms`);
    }
  
    // 3. 计算结果
    let downloadSpeed = this.average(speeds);
    let latency = this.average(latencies);
    let jitter = this.calculateJitter(latencies);
  
    let testDuration = Date.now() - startTime;
  
    let result: SpeedTestResult = {
      downloadSpeed: downloadSpeed,
      uploadSpeed: 0,  // 暂不测上传
      latency: latency,
      jitter: jitter,
      timestamp: Date.now(),
      testDuration: testDuration
    };
  
    console.info(`测速完成: 下载速度 ${downloadSpeed.toFixed(2)} KB/s, 延迟 ${latency.toFixed(2)} ms`);
  
    return result;
  }
  
  // 下载测试文件
  private async downloadTestFile(): Promise<{ speed: number; latency: number }> {
    let httpRequest = http.createHttp();
  
    let startTime = Date.now();
    let firstByteTime = number = 0;
    let downloadedBytes = 0;
  
    try {
      let response = await httpRequest.request(this.config.testUrl, {
        method: http.RequestMethod.GET,
        connectTimeout: this.config.timeout,
        readTimeout: this.config.timeout
      });
    
      // 计算时间
      let endTime = Date.now();
      let totalTime = endTime - startTime;
    
      // 获取下载大小
      if (response.result instanceof ArrayBuffer) {
        downloadedBytes = response.result.byteLength;
      } else if (typeof response.result === 'string') {
        downloadedBytes = response.result.length;
      }
    
      // 计算速度(KB/s)
      let speed = (downloadedBytes / 1024) / (totalTime / 1000);
    
      // 估算延迟(TTFB - Time To First Byte)
      // 简化处理,使用总时间的10%作为延迟估计
      let latency = totalTime * 0.1;
    
      return {
        speed: speed,
        latency: latency
      };
    
    } catch (error) {
      console.error('下载测试失败:', error);
      return {
        speed: 0,
        latency: 9999
      };
    
    } finally {
      httpRequest.destroy();
    }
  }
  
  // 计算平均值
  private average(values: number[]): number {
    if (values.length === 0) return 0;
    return values.reduce((sum, v) => sum + v, 0) / values.length;
  }
  
  // 计算抖动
  private calculateJitter(latencies: number[]): number {
    if (latencies.length < 2) return 0;
  
    let differences: number[] = [];
    for (let i = 1; i < latencies.length; i++) {
      differences.push(Math.abs(latencies[i] - latencies[i - 1]));
    }
  
    return this.average(differences);
  }
  
  // 设置进度回调
  setOnProgress(callback: (progress: number, speed: number) => void): void {
    this.onProgress = callback;
  }
}

// 测速页面
@Entry
@Component
struct SpeedTestPage {
  @State isTesting: boolean = false;
  @State progress: number = 0;
  @State currentSpeed: number = 0;
  @State result: SpeedTestResult | null = null;
  
  private tester: BandwidthTester = new BandwidthTester();
  
  aboutToAppear() {
    this.tester.setOnProgress((progress, speed) => {
      this.progress = progress * 100;
      this.currentSpeed = speed;
    });
  }
  
  // 开始测速
  async startTest() {
    this.isTesting = true;
    this.progress = 0;
    this.currentSpeed = 0;
    this.result = null;
  
    try {
      this.result = await this.tester.runTest();
    } catch (error) {
      console.error('测速失败:', error);
    }
  
    this.isTesting = false;
  }
  
  build() {
    Column() {
      Text('网络带宽测速')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .margin({ bottom: 30 })
    
      // 测速进度
      if (this.isTesting) {
        Column() {
          Progress({ value: this.progress, total: 100, type: ProgressType.Linear })
            .width('80%')
            .height(10)
        
          Text(`当前速度: ${this.currentSpeed.toFixed(2)} KB/s`)
            .fontSize(16)
            .margin({ top: 10 })
        
          Text(`进度: ${this.progress.toFixed(0)}%`)
            .fontSize(14)
            .fontColor('#666')
            .margin({ top: 5 })
        }
        .width('100%')
        .padding(20)
      }
    
      // 测速结果
      if (this.result && !this.isTesting) {
        this.ResultCard()
      }
    
      // 开始按钮
      Button(this.isTesting ? '测速中...' : '开始测速')
        .width('80%')
        .height(50)
        .enabled(!this.isTesting)
        .onClick(() => this.startTest())
        .margin({ top: 30 })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
  }
  
  @Builder
  ResultCard() {
    Column() {
      // 下载速度
      Row() {
        Text('下载速度')
          .fontSize(16)
          .layoutWeight(1)
      
        Text(`${(this.result!.downloadSpeed / 1024).toFixed(2)} Mbps`)
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
          .fontColor('#4A90E2')
      }
      .width('100%')
    
      // 延迟
      Row() {
        Text('网络延迟')
          .fontSize(16)
          .layoutWeight(1)
      
        Text(`${this.result!.latency.toFixed(0)} ms`)
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
          .fontColor(this.getLatencyColor())
      }
      .width('100%')
      .margin({ top: 15 })
    
      // 抖动
      Row() {
        Text('网络抖动')
          .fontSize(16)
          .layoutWeight(1)
      
        Text(`${this.result!.jitter.toFixed(0)} ms`)
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
      }
      .width('100%')
      .margin({ top: 15 })
    
      // 质量评级
      Row() {
        Text('网络质量')
          .fontSize(16)
          .layoutWeight(1)
      
        Text(this.getQualityLevel())
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
          .fontColor(this.getQualityColor())
      }
      .width('100%')
      .margin({ top: 15 })
    
      // 测试耗时
      Text(`测试耗时: ${this.result!.testDuration} ms`)
        .fontSize(12)
        .fontColor('#999')
        .margin({ top: 20 })
    }
    .width('80%')
    .padding(20)
    .backgroundColor('#F5F5F5')
    .borderRadius(10)
  }
  
  private getLatencyColor(): string {
    let latency = this.result?.latency || 0;
    if (latency < 50) return '#7ED321';
    if (latency < 100) return '#F5A623';
    return '#D0021B';
  }
  
  private getQualityLevel(): string {
    let speed = this.result?.downloadSpeed || 0;
    let latency = this.result?.latency || 0;
  
    if (speed > 10240 && latency < 50) return '优秀';
    if (speed > 5120 && latency < 100) return '良好';
    if (speed > 1024 && latency < 200) return '一般';
    if (speed > 512 && latency < 500) return '较差';
    return '很差';
  }
  
  private getQualityColor(): string {
    let level = this.getQualityLevel();
    switch (level) {
      case '优秀': return '#7ED321';
      case '良好': return '#4A90E2';
      case '一般': return '#F5A623';
      case '较差': return '#D0021B';
      default: return '#D0021B';
    }
  }
}

场景二:延迟与丢包检测

通过Ping方式检测网络延迟和丢包情况。

typescript 复制代码
import http from '@ohos.net.http';

// 延迟检测结果
interface LatencyTestResult {
  minLatency: number;         // 最小延迟
  maxLatency: number;         // 最大延迟
  avgLatency: number;         // 平均延迟
  packetLoss: number;         // 丢包率(0-1)
  jitter: number;             // 抖动
  samples: number;            // 采样次数
  timestamp: number;
}

// 延迟检测配置
interface LatencyTestConfig {
  targetUrl: string;          // 目标URL
  count: number;              // 检测次数
  interval: number;           // 检测间隔(ms)
  timeout: number;            // 单次超时(ms)
}

// 默认配置
const DEFAULT_LATENCY_CONFIG: LatencyTestConfig = {
  targetUrl: 'https://api.example.com/ping',
  count: 10,
  interval: 100,
  timeout: 5000
};

// 延迟检测器
class LatencyTester {
  private config: LatencyTestConfig;
  
  // 回调
  private onProgress: ((current: number, total: number, latency: number) => void) | null = null;
  
  constructor(config: Partial<LatencyTestConfig> = {}) {
    this.config = { ...DEFAULT_LATENCY_CONFIG, ...config };
  }
  
  // 执行检测
  async runTest(): Promise<LatencyTestResult> {
    console.info('开始延迟检测');
  
    let latencies: number[] = [];
    let lost = 0;
  
    for (let i = 0; i < this.config.count; i++) {
      let result = await this.ping();
    
      if (result.success) {
        latencies.push(result.latency);
        this.onProgress?.(i + 1, this.config.count, result.latency);
      } else {
        lost++;
        this.onProgress?.(i + 1, this.config.count, -1);
      }
    
      // 等待间隔
      if (i < this.config.count - 1) {
        await this.delay(this.config.interval);
      }
    }
  
    // 计算结果
    let minLatency = latencies.length > 0 ? Math.min(...latencies) : 0;
    let maxLatency = latencies.length > 0 ? Math.max(...latencies) : 0;
    let avgLatency = latencies.length > 0 ? this.average(latencies) : 0;
    let packetLoss = lost / this.config.count;
    let jitter = this.calculateJitter(latencies);
  
    let result: LatencyTestResult = {
      minLatency: minLatency,
      maxLatency: maxLatency,
      avgLatency: avgLatency,
      packetLoss: packetLoss,
      jitter: jitter,
      samples: this.config.count,
      timestamp: Date.now()
    };
  
    console.info(`延迟检测完成: 平均延迟 ${avgLatency.toFixed(2)} ms, 丢包率 ${(packetLoss * 100).toFixed(1)}%`);
  
    return result;
  }
  
  // 单次Ping
  private async ping(): Promise<{ success: boolean; latency: number }> {
    let httpRequest = http.createHttp();
  
    let startTime = Date.now();
  
    try {
      // 发送HEAD请求(更快)
      let response = await httpRequest.request(this.config.targetUrl, {
        method: http.RequestMethod.HEAD,
        connectTimeout: this.config.timeout,
        readTimeout: this.config.timeout
      });
    
      let endTime = Date.now();
      let latency = endTime - startTime;
    
      // 检查响应状态
      if (response.responseCode >= 200 && response.responseCode < 300) {
        return {
          success: true,
          latency: latency
        };
      } else {
        return {
          success: false,
          latency: 0
        };
      }
    
    } catch (error) {
      return {
        success: false,
        latency: 0
      };
    
    } finally {
      httpRequest.destroy();
    }
  }
  
  // 计算平均值
  private average(values: number[]): number {
    if (values.length === 0) return 0;
    return values.reduce((sum, v) => sum + v, 0) / values.length;
  }
  
  // 计算抖动
  private calculateJitter(latencies: number[]): number {
    if (latencies.length < 2) return 0;
  
    let differences: number[] = [];
    for (let i = 1; i < latencies.length; i++) {
      differences.push(Math.abs(latencies[i] - latencies[i - 1]));
    }
  
    return this.average(differences);
  }
  
  // 延迟
  private delay(ms: number): Promise<void> {
    return new Promise(resolve => setTimeout(resolve, ms));
  }
  
  // 设置进度回调
  setOnProgress(callback: (current: number, total: number, latency: number) => void): void {
    this.onProgress = callback;
  }
}

// 持续网络质量监测器
class NetworkQualityMonitor {
  private latencyTester: LatencyTester;
  private isMonitoring: boolean = false;
  private monitorTimer: number = -1;
  private monitorInterval: number = 30000;  // 监测间隔
  
  // 历史数据
  private history: LatencyTestResult[] = [];
  private maxHistory: number = 100;
  
  // 当前质量
  private currentQuality: NetworkQuality = NetworkQuality.UNKNOWN;
  
  // 回调
  private onQualityChange: ((quality: NetworkQuality) => void) | null = null;
  
  constructor() {
    this.latencyTester = new LatencyTester();
  }
  
  // 开始监测
  start(): void {
    if (this.isMonitoring) return;
  
    this.isMonitoring = true;
  
    // 立即执行一次
    this.runMonitor();
  
    // 设置定时监测
    this.monitorTimer = setInterval(() => {
      this.runMonitor();
    }, this.monitorInterval);
  
    console.info('网络质量监测已启动');
  }
  
  // 停止监测
  stop(): void {
    if (!this.isMonitoring) return;
  
    this.isMonitoring = false;
  
    if (this.monitorTimer !== -1) {
      clearInterval(this.monitorTimer);
      this.monitorTimer = -1;
    }
  
    console.info('网络质量监测已停止');
  }
  
  // 执行监测
  private async runMonitor(): Promise<void> {
    try {
      let result = await this.latencyTester.runTest();
    
      // 保存历史
      this.history.push(result);
      if (this.history.length > this.maxHistory) {
        this.history.shift();
      }
    
      // 评估质量
      let quality = this.evaluateQuality(result);
    
      // 检查质量变化
      if (quality !== this.currentQuality) {
        this.currentQuality = quality;
        this.onQualityChange?.(quality);
        console.info(`网络质量变化: ${quality}`);
      }
    
    } catch (error) {
      console.error('网络质量监测失败:', error);
    }
  }
  
  // 评估质量
  private evaluateQuality(result: LatencyTestResult): NetworkQuality {
    // 综合评估延迟和丢包
    let latency = result.avgLatency;
    let packetLoss = result.packetLoss;
  
    // 丢包率优先判断
    if (packetLoss > 0.3) return NetworkQuality.VERY_POOR;
    if (packetLoss > 0.1) return NetworkQuality.POOR;
  
    // 延迟判断
    if (latency < 50) return NetworkQuality.EXCELLENT;
    if (latency < 100) return NetworkQuality.GOOD;
    if (latency < 200) return NetworkQuality.FAIR;
    if (latency < 500) return NetworkQuality.POOR;
    return NetworkQuality.VERY_POOR;
  }
  
  // 获取当前质量
  getCurrentQuality(): NetworkQuality {
    return this.currentQuality;
  }
  
  // 获取历史数据
  getHistory(): LatencyTestResult[] {
    return [...this.history];
  }
  
  // 获取统计摘要
  getSummary(): {
    avgLatency: number;
    avgPacketLoss: number;
    qualityTrend: 'improving' | 'stable' | 'degrading';
  } {
    if (this.history.length === 0) {
      return {
        avgLatency: 0,
        avgPacketLoss: 0,
        qualityTrend: 'stable'
      };
    }
  
    // 计算平均延迟和丢包
    let totalLatency = this.history.reduce((sum, r) => sum + r.avgLatency, 0);
    let totalLoss = this.history.reduce((sum, r) => sum + r.packetLoss, 0);
  
    let avgLatency = totalLatency / this.history.length;
    let avgPacketLoss = totalLoss / this.history.length;
  
    // 分析趋势
    let qualityTrend: 'improving' | 'stable' | 'degrading' = 'stable';
  
    if (this.history.length >= 3) {
      let recent = this.history.slice(-3);
      let latencies = recent.map(r => r.avgLatency);
    
      // 简单趋势判断
      if (latencies[2] < latencies[0] * 0.8) {
        qualityTrend = 'improving';
      } else if (latencies[2] > latencies[0] * 1.2) {
        qualityTrend = 'degrading';
      }
    }
  
    return {
      avgLatency: avgLatency,
      avgPacketLoss: avgPacketLoss,
      qualityTrend: qualityTrend
    };
  }
  
  // 设置回调
  setOnQualityChange(callback: (quality: NetworkQuality) => void): void {
    this.onQualityChange = callback;
  }
}

// 网络质量枚举
enum NetworkQuality {
  UNKNOWN = 'unknown',
  EXCELLENT = 'excellent',
  GOOD = 'good',
  FAIR = 'fair',
  POOR = 'poor',
  VERY_POOR = 'very_poor'
}

// 延迟检测页面
@Entry
@Component
struct LatencyTestPage {
  @State isTesting: boolean = false;
  @State testResult: LatencyTestResult | null = null;
  @State quality: NetworkQuality = NetworkQuality.UNKNOWN;
  @State history: LatencyTestResult[] = [];
  
  private tester: LatencyTester = new LatencyTester();
  private monitor: NetworkQualityMonitor = new NetworkQualityMonitor();
  
  aboutToAppear() {
    this.monitor.setOnQualityChange((quality) => {
      this.quality = quality;
      this.history = this.monitor.getHistory();
    });
  
    this.tester.setOnProgress((current, total, latency) => {
      console.info(`检测进度: ${current}/${total}, 延迟: ${latency} ms`);
    });
  }
  
  aboutToDisappear() {
    this.monitor.stop();
  }
  
  // 执行单次测试
  async runTest() {
    this.isTesting = true;
    this.testResult = await this.tester.runTest();
    this.isTesting = false;
  }
  
  // 开始持续监测
  startMonitor() {
    this.monitor.start();
  }
  
  // 停止监测
  stopMonitor() {
    this.monitor.stop();
  }
  
  build() {
    Column() {
      Text('网络延迟检测')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
    
      // 当前质量
      Row() {
        Text('当前质量:')
          .fontSize(16)
        Text(this.getQualityText())
          .fontSize(16)
          .fontWeight(FontWeight.Bold)
          .fontColor(this.getQualityColor())
          .margin({ left: 10 })
      }
      .margin({ top: 20 })
    
      // 测试结果
      if (this.testResult) {
        Column() {
          Text(`平均延迟: ${this.testResult.avgLatency.toFixed(0)} ms`)
          Text(`最小延迟: ${this.testResult.minLatency.toFixed(0)} ms`)
          Text(`最大延迟: ${this.testResult.maxLatency.toFixed(0)} ms`)
          Text(`丢包率: ${(this.testResult.packetLoss * 100).toFixed(1)}%`)
          Text(`抖动: ${this.testResult.jitter.toFixed(0)} ms`)
        }
        .margin({ top: 20 })
        .alignItems(HorizontalAlign.Start)
      }
    
      // 操作按钮
      Row() {
        Button('单次测试')
          .onClick(() => this.runTest())
          .enabled(!this.isTesting)
      
        Button('开始监测')
          .onClick(() => this.startMonitor())
          .margin({ left: 10 })
      
        Button('停止监测')
          .onClick(() => this.stopMonitor())
          .margin({ left: 10 })
      }
      .margin({ top: 30 })
    }
    .width('100%')
    .padding(20)
  }
  
  private getQualityText(): string {
    switch (this.quality) {
      case NetworkQuality.EXCELLENT: return '优秀';
      case NetworkQuality.GOOD: return '良好';
      case NetworkQuality.FAIR: return '一般';
      case NetworkQuality.POOR: return '较差';
      case NetworkQuality.VERY_POOR: return '很差';
      default: return '未知';
    }
  }
  
  private getQualityColor(): string {
    switch (this.quality) {
      case NetworkQuality.EXCELLENT: return '#7ED321';
      case NetworkQuality.GOOD: return '#4A90E2';
      case NetworkQuality.FAIR: return '#F5A623';
      case NetworkQuality.POOR: return '#D0021B';
      case NetworkQuality.VERY_POOR: return '#D0021B';
      default: return '#999';
    }
  }
}

场景三:请求性能采样

通过记录实际请求的性能数据来评估网络质量。

typescript 复制代码
import http from '@ohos.net.http';

// 请求性能样本
interface RequestSample {
  url: string;
  method: string;
  dnsTime: number;          // DNS解析时间
  connectTime: number;      // TCP连接时间
  ttfb: number;             // Time To First Byte
  downloadTime: number;     // 下载时间
  totalTime: number;        // 总时间
  bytesReceived: number;    // 接收字节数
  success: boolean;         // 是否成功
  timestamp: number;
}

// 性能统计
interface PerformanceStats {
  avgDnsTime: number;
  avgConnectTime: number;
  avgTtfb: number;
  avgDownloadSpeed: number;  // KB/s
  successRate: number;
  sampleCount: number;
  timeRange: {
    start: number;
    end: number;
  };
}

// 请求性能采样器
class RequestPerformanceSampler {
  private samples: RequestSample[] = [];
  private maxSamples: number = 1000;
  
  // 记录请求性能
  recordRequest(
    url: string,
    method: string,
    timings: {
      startTime: number;
      dnsEnd?: number;
      connectEnd?: number;
      firstByteTime: number;
      endTime: number;
    },
    bytesReceived: number,
    success: boolean
  ): void {
    let sample: RequestSample = {
      url: url,
      method: method,
      dnsTime: timings.dnsEnd ? timings.dnsEnd - timings.startTime : 0,
      connectTime: timings.connectEnd && timings.dnsEnd ? 
                   timings.connectEnd - timings.dnsEnd : 0,
      ttfb: timings.firstByteTime - timings.startTime,
      downloadTime: timings.endTime - timings.firstByteTime,
      totalTime: timings.endTime - timings.startTime,
      bytesReceived: bytesReceived,
      success: success,
      timestamp: timings.startTime
    };
  
    this.samples.push(sample);
  
    // 限制样本数量
    if (this.samples.length > this.maxSamples) {
      this.samples.shift();
    }
  }
  
  // 计算统计
  calculateStats(timeWindow?: number): PerformanceStats {
    let now = Date.now();
    let windowStart = timeWindow ? now - timeWindow : 0;
  
    // 过滤时间窗口内的样本
    let filteredSamples = this.samples.filter(s => s.timestamp >= windowStart);
  
    if (filteredSamples.length === 0) {
      return {
        avgDnsTime: 0,
        avgConnectTime: 0,
        avgTtfb: 0,
        avgDownloadSpeed: 0,
        successRate: 0,
        sampleCount: 0,
        timeRange: { start: now, end: now }
      };
    }
  
    // 计算平均值
    let totalDns = filteredSamples.reduce((sum, s) => sum + s.dnsTime, 0);
    let totalConnect = filteredSamples.reduce((sum, s) => sum + s.connectTime, 0);
    let totalTtfb = filteredSamples.reduce((sum, s) => sum + s.ttfb, 0);
    let successCount = filteredSamples.filter(s => s.success).length;
  
    // 计算下载速度
    let totalBytes = filteredSamples.reduce((sum, s) => sum + s.bytesReceived, 0);
    let totalDownloadTime = filteredSamples.reduce((sum, s) => sum + s.downloadTime, 0);
    let avgDownloadSpeed = totalDownloadTime > 0 ? 
                           (totalBytes / 1024) / (totalDownloadTime / 1000) : 0;
  
    let timestamps = filteredSamples.map(s => s.timestamp);
  
    return {
      avgDnsTime: totalDns / filteredSamples.length,
      avgConnectTime: totalConnect / filteredSamples.length,
      avgTtfb: totalTtfb / filteredSamples.length,
      avgDownloadSpeed: avgDownloadSpeed,
      successRate: successCount / filteredSamples.length,
      sampleCount: filteredSamples.length,
      timeRange: {
        start: Math.min(...timestamps),
        end: Math.max(...timestamps)
      }
    };
  }
  
  // 获取最近的样本
  getRecentSamples(count: number = 10): RequestSample[] {
    return this.samples.slice(-count);
  }
  
  // 清除样本
  clear(): void {
    this.samples = [];
  }
}

// 带性能采样的HTTP客户端
class SampledHttpClient {
  private sampler: RequestPerformanceSampler;
  
  constructor(sampler: RequestPerformanceSampler) {
    this.sampler = sampler;
  }
  
  // 发送请求(带性能采样)
  async request(url: string, options: http.HttpRequestOptions): Promise<http.HttpResponse> {
    let httpRequest = http.createHttp();
  
    let startTime = Date.now();
    let firstByteTime = number = 0;
    let bytesReceived = 0;
    let success = false;
  
    try {
      let response = await httpRequest.request(url, options);
    
      firstByteTime = Date.now();  // 简化处理
    
      // 获取响应大小
      if (response.result instanceof ArrayBuffer) {
        bytesReceived = response.result.byteLength;
      } else if (typeof response.result === 'string') {
        bytesReceived = response.result.length;
      }
    
      success = response.responseCode >= 200 && response.responseCode < 300;
    
      let endTime = Date.now();
    
      // 记录性能
      this.sampler.recordRequest(
        url,
        options.method?.toString() || 'GET',
        {
          startTime: startTime,
          firstByteTime: firstByteTime,
          endTime: endTime
        },
        bytesReceived,
        success
      );
    
      return response;
    
    } catch (error) {
      let endTime = Date.now();
    
      // 记录失败请求
      this.sampler.recordRequest(
        url,
        options.method?.toString() || 'GET',
        {
          startTime: startTime,
          firstByteTime: endTime,
          endTime: endTime
        },
        0,
        false
      );
    
      throw error;
    
    } finally {
      httpRequest.destroy();
    }
  }
  
  // 获取采样器
  getSampler(): RequestPerformanceSampler {
    return this.sampler;
  }
}

// 性能监控页面
@Entry
@Component
struct PerformanceMonitorPage {
  @State stats: PerformanceStats | null = null;
  @State recentSamples: RequestSample[] = [];
  
  private sampler: RequestPerformanceSampler = new RequestPerformanceSampler();
  private httpClient: SampledHttpClient | null = null;
  
  aboutToAppear() {
    this.httpClient = new SampledHttpClient(this.sampler);
  
    // 定期更新统计
    setInterval(() => {
      this.stats = this.sampler.calculateStats(60000);  // 最近1分钟
      this.recentSamples = this.sampler.getRecentSamples(10);
    }, 5000);
  }
  
  // 测试请求
  async testRequest() {
    try {
      await this.httpClient?.request('https://api.example.com/data', {
        method: http.RequestMethod.GET
      });
    } catch (error) {
      console.error('请求失败:', error);
    }
  }
  
  build() {
    Column() {
      Text('请求性能监控')
        .fontSize(20)
        .fontWeight(FontWeight.Bold)
    
      // 统计信息
      if (this.stats) {
        Column() {
          Text(`平均DNS时间: ${this.stats.avgDnsTime.toFixed(0)} ms`)
          Text(`平均连接时间: ${this.stats.avgConnectTime.toFixed(0)} ms`)
          Text(`平均TTFB: ${this.stats.avgTtfb.toFixed(0)} ms`)
          Text(`平均下载速度: ${this.stats.avgDownloadSpeed.toFixed(0)} KB/s`)
          Text(`成功率: ${(this.stats.successRate * 100).toFixed(1)}%`)
          Text(`样本数: ${this.stats.sampleCount}`)
        }
        .margin({ top: 20 })
        .alignItems(HorizontalAlign.Start)
      }
    
      // 测试按钮
      Button('发送测试请求')
        .onClick(() => this.testRequest())
        .margin({ top: 20 })
    
      // 最近样本
      if (this.recentSamples.length > 0) {
        Column() {
          Text('最近请求:')
            .fontSize(14)
        
          ForEach(this.recentSamples, (sample: RequestSample, index: number) => {
            Text(`${sample.method} ${sample.url}: ${sample.totalTime}ms`)
              .fontSize(12)
          })
        }
        .margin({ top: 20 })
        .alignItems(HorizontalAlign.Start)
      }
    }
    .width('100%')
    .padding(20)
  }
}

四、踩坑与注意事项

坑点一:测速消耗流量

带宽测速需要下载大文件,会消耗用户流量。

typescript 复制代码
// ✅ 正确:仅在WiFi下测速
async function safeSpeedTest(networkManager: NetworkStatusManager) {
  if (!networkManager.isWifi()) {
    console.warn('非WiFi环境,跳过测速');
    return null;
  }
  
  let tester = new BandwidthTester();
  return await tester.runTest();
}

坑点二:测速结果受服务器影响

测速结果受测试服务器影响,可能不准确。

typescript 复制代码
// ✅ 正确:多服务器测速取最优
async function multiServerSpeedTest(): Promise<SpeedTestResult> {
  let servers = [
    'https://cdn1.example.com/test.bin',
    'https://cdn2.example.com/test.bin',
    'https://cdn3.example.com/test.bin'
  ];
  
  let results: SpeedTestResult[] = [];
  
  for (let server of servers) {
    let tester = new BandwidthTester({ testUrl: server });
    let result = await tester.runTest();
    results.push(result);
  }
  
  // 取最快的结果
  return results.reduce((best, current) => 
    current.downloadSpeed > best.downloadSpeed ? current : best
  );
}

坑点三:频繁检测影响性能

过于频繁的网络检测会影响应用性能。

typescript 复制代码
// ✅ 正确:合理设置检测间隔
const MONITOR_CONFIG = {
  // 正常情况:每分钟检测一次
  normalInterval: 60000,
  
  // 弱网情况:每30秒检测一次
  weakInterval: 30000,
  
  // 离线情况:每10秒检测一次(等待恢复)
  offlineInterval: 10000
};

function getMonitorInterval(quality: NetworkQuality): number {
  switch (quality) {
    case NetworkQuality.EXCELLENT:
    case NetworkQuality.GOOD:
      return MONITOR_CONFIG.normalInterval;
    case NetworkQuality.FAIR:
    case NetworkQuality.POOR:
      return MONITOR_CONFIG.weakInterval;
    default:
      return MONITOR_CONFIG.offlineInterval;
  }
}

五、HarmonyOS 6适配指南

5.1 系统级网络质量API

HarmonyOS 6提供了系统级的网络质量评估API。

typescript 复制代码
import connection from '@ohos.net.connection';

// HarmonyOS 6: 获取网络质量信息
async function getSystemNetworkQuality(): Promise<void> {
  let netHandle = await connection.getDefaultNet();
  
  // 获取网络能力
  let capabilities = await connection.getNetCapabilities(netHandle);
  
  // 下行带宽
  let downlinkKbps = capabilities.linkDownBandwidthKbps;
  console.info(`下行带宽: ${downlinkKbps} Kbps`);
  
  // 上行带宽
  let uplinkKbps = capabilities.linkUpBandwidthKbps;
  console.info(`上行带宽: ${uplinkKbps} Kbps`);
  
  // HarmonyOS 6: 获取详细质量信息
  // let quality = await connection.getNetworkQuality();
  // console.info(`质量评分: ${quality.score}`);
  // console.info(`时延: ${quality.delay} ms`);
  // console.info(`丢包率: ${quality.packetLoss}`);
}

5.2 网络质量变化监听

HarmonyOS 6支持网络质量变化事件。

typescript 复制代码
// HarmonyOS 6: 监听网络质量变化
let netConnection = connection.createNetConnection();

netConnection.on('netQualityChange', (quality: connection.NetworkQuality) => {
  console.info('网络质量变化');
  console.info(`评分: ${quality.score}`);
  console.info(`时延等级: ${quality.delayLevel}`);
  console.info(`带宽等级: ${quality.bandwidthLevel}`);
  
  // 根据质量调整应用行为
  adjustAppBehavior(quality);
});

六、总结一下下

网络质量评估是精细化网络管理的关键。本文从三个方案展开:

带宽测速:通过下载测试文件测量实际带宽。注意消耗流量、多服务器测试、结果缓存等问题。

延迟检测:通过Ping方式测量延迟和丢包。开销小、可频繁执行,适合持续监测。

请求采样:记录实际请求的性能数据。真实反映用户体验,无需额外开销。

三个常见坑点:测速消耗流量、结果受服务器影响、频繁检测影响性能。遇到质量评估问题时,先排查这三个方面。

HarmonyOS 6带来了系统级网络质量API,可以直接获取带宽、延迟、丢包等信息,大大简化了实现难度。结合自定义检测方案,可以构建完善的网络质量评估体系。

至此,我们的网络基础系列文章全部完成。从HTTP请求到WebSocket长连接,从TCP/UDP Socket到网络状态监听,再到网络质量评估,希望这一系列文章能帮助你全面掌握鸿蒙网络编程!