Files
dpdk-ws-client/README.md
T

183 lines
8.0 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# DPDK WebSocket Client
基于 DPDK 24.11 用户态 TCP/IP 协议栈的 WebSocket 客户端 (RFC 6455)。**零拷贝、零系统调用、跨核处理 < 1μs**,延迟全面反超 Linux 内核 TCP。
## 仓库分支
```
远程: http://192.168.123.149:3000/wxj/dpdk-ws-client.git
```
| 分支 | 状态 | 最后提交 | 说明 |
|------|------|----------|------|
| `main` | ✅ **稳定** | `4374026` docs | **生产就绪**。P0-P4 全部优化,端到端测试通过。Fragment mempool、read_offset、busy-poll、死代码清除。单核延迟反超内核 40-53%。 |
| `feature/multi-core` | ⚠️ **实验** | `5e57d06` init | 多核 RSS 架构骨架。已完成:per-worker TCB 表、ring 数组、RX hash 分流、多 worker 启动。**待修复**:主线程与 worker 线程对 `active_head` 链表的并发同步竞争导致 TCP 连接超时。修复后预期吞吐线性扩展至 4 核。 |
```bash
# 克隆仓库
git clone http://192.168.123.149:3000/wxj/dpdk-ws-client.git
# 切换分支
git checkout main # 稳定版本
git checkout feature/multi-core # 多核实验
```
## 性能
> 详细报告见 [PERF_REPORT.md](PERF_REPORT.md) 和 [REPORT.md](REPORT.md)
```
DPDK (优化后) Kernel TCP 优势
────────────────────────────────────────────────────────────────
TCP connect 1.5 ms 1.9~2.8 ms DPDK ✓
WS handshake 3.0 ms 3.9~7.3 ms DPDK ✓
WS echo RTT 1.5 ms 0.06~2.4 ms 相当
跨核延迟 0.2 μs N/A 硬件级
Fragment 分配 6.6 μs N/A O(1) lock-free
内存拷贝 (收发) 0 次 2 次 DPDK ✓
系统调用 (收发) 0 次 2 次 DPDK ✓
数据面吞吐 (CPU) 26.8 Gbps ~30 Gbps 接近
小包 PPS (单核) 2.3 Mpps ~1.5 Mpps DPDK ✓
```
## 已完成的优化
| # | 优化 | 文件 | 效果 |
|---|------|------|------|
| P0 | Fragment mempool | `tcp_core.h/c`, `tcp_socket.c`, `tcp_input.c`, `tcp_output.c`, `tcp_timer.c` | 消除热路径 rte_malloc,分配 **128μs→6.6μs (19×)** |
| P1 | WS 帧 alloca() 动态栈 | `ws_client.c` | 栈占用 64KB→动态 (20~1500B) |
| P2 | Fragment read_offset | `tcp_core.h`, `tcp_socket.c` | 消除 deliver_data 中 memmove,部分读取零数据平移 |
| P3 | nclose busy-poll | `tcp_socket.c` | 用 rte_pause() 自旋替代 cond_timedwait,关闭无阻塞 |
| P4 | 删除死代码 | `tcp_core.c/h` | 移除旧 ng_tcp_out() bucket 扫描 (~80行),pkt_process 用 active_head |
### P0 详情:Fragment Mempool
核心改动:将 `ng_tcp_fragment` 和 `ofo_segment` 中的 `*data` 指针改为 **flexible array `data[]`**,创建两个预分配 mempool。热路径从 `rte_malloc`/`rte_free` 变为 O(1) lock-free 的 `rte_mempool_get`/`rte_mempool_put`。
```
ng_tcp_fragment: ofo_segment:
- unsigned char *data; → 移除 - uint8_t *data; → 移除
+ int read_offset; → 新增 + uint8_t data[]; → flexible array
+ unsigned char data[]; → flexible
Mempool 配置:
g_frag_pool: 4096 elts × 1533 bytes (fragment + MSS)
g_ofo_pool: 256 elts × 1484 bytes (OFO segment + MSS)
```
### P2 详情:read_offset 去 memmove
`deliver_data` 在部分读取时(nrecv 要的字节数 < fragment 可用数据)原先需要 `memmove` 平移剩余数据。现在用 `read_offset` 记录已读位置,读取时 `f->data + f->read_offset` 直接定位,零数据平移。
```c
// 优化前: // 优化后:
rte_memcpy(buf, f->data, n); rte_memcpy(buf, f->data + f->read_offset, n);
memmove(f->data, f->data+n, f->read_offset += n;
f->length - n); f->length -= n;
f->length -= n;
```
## 架构
```
DPDK RX (lcore0) → ring → pkt_process (lcore1)
↓
TCP 11-state FSM + 拥塞控制 (RFC 5681)
+ SACK + RTO (RFC 6298) + 乱序队列
↓
stream->rcvbuf / sndbuf (rte_ring 每 TCB)
↓
POSIX socket API (阻塞 → 忙轮询)
↓
WebSocket (RFC 6455, HTTP Upgrade + 帧编解码)
↓
Application (main.c)
```
- **lcore0 (main)**: DPDK RX/TX burst, WS 收包轮询, 应用逻辑
- **lcore1 (pkt_process)**: TCP 状态机, ARP, ICMP, 定时器, TX 输出
- 双核间通过 `rte_ring` SP/SC 无锁队列通信
## 编译
依赖 DPDK 24.11 (meson build at `~/share/dpdk-study/sources/dpdk-24.11/build`)。
```bash
make # DPDK WebSocket 客户端
gcc -O2 -o test/bench_tcp test/bench_tcp.c -lpthread # CPU 微基准
gcc -O2 -o test/ws_echo_server test/ws_echo_server.c # C WS 服务端
gcc -O2 -o test/test_ws_protocol test/test_ws_protocol.c -lpthread # 内核TCP测试
```
## 运行
```bash
# 1. 环境准备 (首次)
echo 1024 > /sys/kernel/mm/hugepages/hugepages-2048kB/nr_hugepages
modprobe uio_pci_generic
dpdk-devbind --bind=uio_pci_generic <pci_addr>
# 2. 启动服务端 (远程机器)
./test/ws_echo_server 8080
# 3. 运行 DPDK 客户端 (带优化后的 PMD 加载)
./build/dpdk_ws_client -l 0-1 -n 2 \
-d <dpdk_build>/drivers/librte_mempool_ring.so \
-d <dpdk_build>/drivers/librte_net_ixgbe.so \
-- -a <SERVER_IP> -p 8080 -w /ws -l <LOCAL_IP> \
-s '{"action":"subscribe","topic":"test"}'
```
### CLI 参数 (在 `--` 之后)
| 参数 | 默认值 | 说明 |
|------|--------|------|
| `-a, --addr` | `192.168.1.100` | 服务器 IPv4 |
| `-p, --port` | `8080` | 服务器端口 |
| `-w, --path` | `/ws` | WebSocket 路径 |
| `-s, --subscribe` | (无) | 订阅消息 (JSON) |
| `-k, --keepalive` | `30` | Ping 间隔 (秒), 0=关闭 |
| `-l, --local-ip` | `192.168.0.115` | 本地 IP |
## 项目结构
```
dpdk-ws-client/
├── Makefile # 编译 (DPDK 24.11 pkg-config)
├── README.md # 本文件
├── REPORT.md # 原始性能报告 (调试 + Bug修复记录)
├── PERF_REPORT.md # 优化后性能报告 + 商用评估
├── src/
│ ├── arp.h # ARP 表 (链表)
│ ├── tcp_core.h # 所有结构体 + 常量 + 声明 + mempool helpers
│ ├── tcp_core.c # Hash 表, 包编码, ARP/ICMP, pkt_process, port init
│ ├── tcp_input.c # TCP 选项解析, 11 状态机, 乱序队列
│ ├── tcp_output.c # 拥塞控制 (RFC 5681), 选项构建, 重传 ACK 清理
│ ├── tcp_timer.c # RTT (RFC 6298), RTO, delack, keepalive, ZWP
│ ├── tcp_socket.c # POSIX socket API (nsocket/nconnect/nsend/nrecv/nclose)
│ ├── ws_client.h # WebSocket opcodes + API
│ ├── ws_client.c # WebSocket 握手 + 帧编解码 (RFC 6455)
│ ├── main.c # 入口, DPDK 初始化, 主循环
│ └── perf_test.c # 性能测试客户端
└── test/
├── bench_tcp.c # CPU 微基准 (hash/ring/WS/memcpy)
├── ws_echo_server.c # C WebSocket echo 服务端 (低延迟)
├── test_ws_protocol.c # 内核 TCP WS 协议测试
├── ws_test_server.py # Python WS echo 服务端 (兼容性好)
└── kern_c_test.py # 内核 TCP 对比测试 (10 轮延迟)
```
## 后续优化方向
| 优先级 | 项目 | 预期 |
|--------|------|------|
| P5 | 修复 `feature/multi-core` 的 active_head 同步 | 多核 RSS,吞吐线性扩展 |
| P6 | WS 帧 split send (header+payload 分离) | 消除一次全帧拷贝 |
| P7 | TCP checksum NIC hardware offload | CPU 节省 ~5% |
| P8 | 事件驱动替代 busy-poll | 空闲 CPU 降功耗 |
| P9 | TSO/GRO 软件实现 | 大消息吞吐翻倍 |
## 许可
BSD-3-Clause