22、实验环境搭建:使用Docker搭建WebRTC测试环境、网络模拟工具(NetEm)、日志分析工具

做WebRTC拥塞控制算法研究,最头疼的是什么?

不是算法本身有多复杂,而是你很难在真实网络中复现问题。我早年调试GCC算法时,经常遇到「办公室网络跑得好好的,一上飞机场Wi-Fi就崩了」的情况。后来我学乖了——先把实验环境搭好,再动手改代码

这一章,我就带你搭一套完整的WebRTC测试环境。说白了,就是三件套:Docker容器(隔离运行环境)、NetEm(模拟烂网络)、日志分析工具(看数据说话)。

22.1 为什么非要用Docker?

你想想看,WebRTC涉及音视频采集、编码、网络传输、渲染,依赖一大堆系统库。每个人电脑环境不一样,装一遍能折腾半天。我有个同事曾经在Mac上编译WebRTC,搞了整整两天——最后发现是Xcode版本不对。

Docker的好处就是:一次构建,到处运行。我把整个环境打包成镜像,你拉下来就能用。而且容器之间网络隔离,方便模拟多个端。

核心思路:用两个Docker容器分别模拟发送端和接收端,中间用NetEm控制网络质量。

22.2 搭建Docker WebRTC环境

先写一个Dockerfile。我个人习惯用Ubuntu 22.04作为基础镜像,因为它的包管理比较新,编译WebRTC依赖时少踩坑。

# Dockerfile
FROM ubuntu:22.04

# 安装编译依赖
RUN apt-get update && apt-get install -y \
    build-essential \
    cmake \
    git \
    python3 \
    ninja-build \
    pkg-config \
    libssl-dev \
    libasound2-dev \
    libpulse-dev \
    libjpeg-dev \
    libvpx-dev \
    libx264-dev \
    libxext-dev \
    libxfixes-dev \
    libxrandr-dev \
    libxrender-dev \
    libgl1-mesa-dev \
    libgles2-mesa-dev \
    libdrm-dev \
    libgbm-dev \
    libxtst-dev \
    libxcomposite-dev \
    libxdamage-dev \
    libx11-xcb-dev \
    libxcb-shm0-dev \
    libxcb-xfixes0-dev \
    libxcb1-dev \
    libxcb-keysyms1-dev \
    libxcb-image0-dev \
    libxcb-shm0-dev \
    libxcb-randr0-dev \
    libxcb-xinerama0-dev \
    libxcb-xinput-dev \
    libxcb-composite0-dev \
    libxcb-xv0-dev \
    libxcb-xkb-dev \
    libxcb-util-dev \
    libxcb-cursor-dev \
    libxcb-icccm4-dev \
    libxcb-ewmh-dev \
    libxcb-util0-dev \
    libxcb-xrm-dev \
    libxcb-xkb-dev \
    libxkbcommon-dev \
    libxkbcommon-x11-dev \
    libpciaccess-dev \
    libudev-dev \
    libjsoncpp-dev \
    libabsl-dev \
    libprotobuf-dev \
    protobuf-compiler \
    wget \
    curl \
    vim \
    net-tools \
    iproute2 \
    iperf3 \
    tcpdump \
    && rm -rf /var/lib/apt/lists/*

# 设置工作目录
WORKDIR /workspace

# 克隆WebRTC源码(这里用精简版,实际项目建议用depot_tools)
RUN git clone https://github.com/webrtc-sdk/webrtc.git --depth 1

# 编译WebRTC示例程序
WORKDIR /workspace/webrtc
RUN mkdir -p out/debug && \
    cd out/debug && \
    cmake ../.. -DCMAKE_BUILD_TYPE=Debug && \
    make -j$(nproc) peerconnection_client peerconnection_server

CMD ["/bin/bash"]

嗯,这里要注意:WebRTC官方源码非常大(十几个G),上面这个Dockerfile只是演示结构。实际项目中,我建议你先把WebRTC编译好,做成一个基础镜像,然后在这个镜像上叠加你的测试代码。

我的经验:把编译好的WebRTC库和头文件打包成tar,放到Docker镜像里。这样每次构建只需要解压,不用重新编译。能省下至少40分钟。

22.3 构建并启动容器

# 构建镜像
docker build -t webrtc-lab:latest .

# 创建网络(方便容器间通信)
docker network create webrtc-net

# 启动发送端容器
docker run -it --name sender \
    --network webrtc-net \
    --cap-add=NET_ADMIN \
    webrtc-lab:latest

# 启动接收端容器
docker run -it --name receiver \
    --network webrtc-net \
    --cap-add=NET_ADMIN \
    webrtc-lab:latest

为什么加--cap-add=NET_ADMIN?因为后面我们要在容器里用NetEm修改网络参数,这个权限默认是关闭的。我曾经忘了加这个参数,折腾了半天才发现NetEm命令报权限错误。

22.4 网络模拟工具NetEm

NetEm是Linux内核自带的网络模拟工具,可以模拟延迟、丢包、抖动、带宽限制。说白了,就是让你的网络环境「变烂」——而且是可控地变烂。

我在项目中遇到过最典型的场景:模拟4G网络下的弱网环境。4G网络的特点是延迟波动大(50ms-200ms),偶尔丢包(1%-5%),带宽不稳定。用NetEm可以轻松复现。

22.4.1 常用NetEm命令

# 在接收端容器中执行
# 1. 查看当前网卡
ip addr show

# 2. 添加延迟(假设网卡是eth0)
tc qdisc add dev eth0 root netem delay 100ms 20ms distribution normal

# 3. 添加丢包
tc qdisc change dev eth0 root netem loss 5% 25%

# 4. 添加抖动
tc qdisc change dev eth0 root netem delay 100ms 30ms

# 5. 限制带宽(需要配合tbf)
tc qdisc add dev eth0 root handle 1: htb default 30
tc class add dev eth0 parent 1: classid 1:1 htb rate 1mbit
tc qdisc add dev eth0 parent 1:1 handle 10: netem delay 100ms loss 2%

# 6. 清除所有规则
tc qdisc del dev eth0 root

注意:NetEm的规则是叠加的,不是替换。如果你先加了延迟,又加丢包,要用tc qdisc change而不是add。否则会报「文件已存在」的错误。

22.4.2 模拟典型网络场景

场景 延迟 丢包率 抖动 带宽
完美网络 0ms 0% 0ms 无限制
普通Wi-Fi 20ms 0.5% 5ms 50Mbps
4G移动网络 80ms 2% 30ms 5Mbps
卫星网络 600ms 1% 50ms 2Mbps
极端弱网 300ms 10% 100ms 500Kbps

你可以写一个脚本,一键切换网络场景。我个人习惯把这些场景定义成函数,放在/root/net_profiles.sh里,测试时直接调用。

22.5 日志分析工具

WebRTC本身会输出大量日志,但默认是打印到控制台。你要做拥塞控制分析,光看控制台输出是不够的——数据太多,刷屏太快。

我推荐三个工具:

  • WebRTC内部统计API:通过getStats()获取实时数据
  • Wireshark:抓包分析RTP/RTCP包
  • 自定义日志脚本:把关键指标写入文件,事后分析

22.5.1 启用WebRTC详细日志

// 在WebRTC代码中启用日志
// 设置日志级别
rtc::LogMessage::LogToDebug(rtc::LS_VERBOSE);

// 或者通过环境变量
// export WEBRTC_LOG_FILE=/workspace/logs/webrtc.log
// export WEBRTC_LOG_LEVEL=5

22.5.2 抓取RTP包

# 在发送端容器中抓包
tcpdump -i eth0 -w /workspace/capture.pcap port 5000 or port 5001

# 用Wireshark分析(在宿主机上)
# wireshark capture.pcap

抓包时要注意:只抓RTP/RTCP端口,否则数据量太大。我一般只抓5000-5005端口,这些是WebRTC默认的媒体端口。

22.5.3 自定义日志脚本

这是我个人最常用的方法。写一个Python脚本,每隔1秒采集一次关键指标,写入CSV文件。

#!/usr/bin/env python3
"""
webrtc_stats_collector.py
采集WebRTC拥塞控制相关指标
"""

import subprocess
import time
import csv
import os
from datetime import datetime

# 采集指标列表
METRICS = [
    "timestamp",
    "rtt_ms",
    "packets_lost",
    "fraction_lost",
    "jitter_ms",
    "available_send_bw_bps",
    "available_receive_bw_bps",
    "target_bitrate_bps",
    "transmit_bitrate_bps",
    "retransmit_bitrate_bps",
    "nack_count",
    "pli_count",
    "fir_count"
]

def collect_webrtc_stats():
    """通过WebRTC的getStats接口采集数据"""
    # 这里假设你有一个HTTP接口可以获取stats
    # 实际项目中,你需要修改WebRTC代码暴露这些数据
    result = subprocess.run(
        ["curl", "-s", "http://localhost:8080/stats"],
        capture_output=True,
        text=True
    )
    return result.stdout

def parse_stats(raw_data):
    """解析JSON格式的统计数据"""
    import json
    try:
        data = json.loads(raw_data)
        return data
    except:
        return None

def main():
    output_file = f"/workspace/logs/stats_{datetime.now().strftime('%Y%m%d_%H%M%S')}.csv"
    
    with open(output_file, 'w', newline='') as f:
        writer = csv.DictWriter(f, fieldnames=METRICS)
        writer.writeheader()
        
        print(f"开始采集数据,输出文件: {output_file}")
        print("按 Ctrl+C 停止采集")
        
        try:
            while True:
                raw = collect_webrtc_stats()
                stats = parse_stats(raw)
                if stats:
                    # 添加时间戳
                    stats['timestamp'] = datetime.now().isoformat()
                    writer.writerow(stats)
                    f.flush()
                    print(f"采集到 {len(stats)} 个指标")
                else:
                    print("警告: 无法解析统计数据")
                
                time.sleep(1)  # 每秒采集一次
                
        except KeyboardInterrupt:
            print("\n采集结束")

if __name__ == "__main__":
    main()

我的经验:采集频率不要太高。每秒一次足够分析拥塞控制行为。如果频率太高(比如10ms一次),数据量太大,反而看不清趋势。

22.6 整体架构图

下面这张图展示了整个实验环境的架构。我把它画成SVG,方便你理解各个组件之间的关系。

WebRTC拥塞控制实验环境架构 Docker容器:发送端 WebRTC PeerConnection 视频采集/编码 GCC发送端控制器 RTP打包/发送 日志输出模块 网络模拟层 (NetEm) 延迟模拟 (delay) 丢包模拟 (loss) 抖动模拟 (jitter) 带宽限制 (tbf) tc qdisc 规则链 可动态切换场景 Docker容器:接收端 RTP接收/解包 视频解码/渲染 GCC接收端控制器 RTCP反馈生成 日志输出模块 RTP流 RTP流 RTCP RR/REMB RTCP RR/REMB 日志采集点:每个容器内部输出日志 + 网络层抓包 + 自定义统计脚本

22.7 一键启动脚本

最后,我给你一个完整的启动脚本。把下面内容保存为start_lab.sh,以后每次测试直接运行。

#!/bin/bash
# start_lab.sh - 一键启动WebRTC测试环境

set -e

echo "=== 启动WebRTC拥塞控制实验环境 ==="

# 1. 清理旧容器
echo "清理旧容器..."
docker rm -f sender receiver 2>/dev/null || true

# 2. 创建网络
echo "创建Docker网络..."
docker network create webrtc-net 2>/dev/null || true

# 3. 启动容器
echo "启动发送端容器..."
docker run -d --name sender \
    --network webrtc-net \
    --cap-add=NET_ADMIN \
    --cap-add=NET_RAW \
    -v $(pwd)/logs:/workspace/logs \
    webrtc-lab:latest \
    tail -f /dev/null

echo "启动接收端容器..."
docker run -d --name receiver \
    --network webrtc-net \
    --cap-add=NET_ADMIN \
    --cap-add=NET_RAW \
    -v $(pwd)/logs:/workspace/logs \
    webrtc-lab:latest \
    tail -f /dev/null

# 4. 配置网络场景(默认:普通Wi-Fi)
echo "配置网络场景:普通Wi-Fi..."
docker exec receiver tc qdisc add dev eth0 root netem delay 20ms 5ms distribution normal loss 0.5%

echo "=== 环境启动完成 ==="
echo "发送端: docker exec -it sender bash"
echo "接收端: docker exec -it receiver bash"
echo "日志目录: ./logs/"

重要提醒:这个脚本假设你已经构建好了webrtc-lab:latest镜像。如果还没有,先执行docker build -t webrtc-lab:latest .

好了,环境搭好了。下一章我们会真正开始跑WebRTC的拥塞控制算法,看看GCC在模拟网络下的表现。到时候你会发现——嗯,原来算法在实验室里跑得再好,到了真实网络里还是会有各种「惊喜」。


公众号:蓝海资料掘金营,微信deep3321