tcp/quic lab and switch lab added

This commit is contained in:
2026-01-10 10:33:04 +08:00
parent 747cad7b4d
commit 265709e49c
7 changed files with 669 additions and 17 deletions

View File

@@ -0,0 +1,205 @@
# QUIC 性能测试修复报告
## 问题描述
在 TCP 与 QUIC 性能对比实验中QUIC 程序存在严重的性能问题:
| 协议 | 吞吐量 (修复前) |
|------|----------------|
| TCP | ~2200 MB/s |
| QUIC | ~6 MB/s |
QUIC 吞吐量仅为 TCP 的 **0.3%**,这明显不正常。
## 问题分析
### 问题 1超时处理导致性能低下
**原代码 (quic_perf_client.c 和 quic_perf_server.c)**
```c
int64_t timeout_ns = quiche_conn_timeout_as_nanos(conn);
if (timeout_ns > 0 && !has_outgoing && !finished_sending) {
struct timespec ts;
ts.tv_sec = timeout_ns / 1000000000;
ts.tv_nsec = timeout_ns % 1000000000;
nanosleep(&ts, NULL);
}
```
**问题:** `quiche_conn_timeout_as_nanos()` 返回的是连接空闲超时时间(配置为 10 秒),而不是下一次需要处理事件的时间。这导致程序在没有数据发送时会等待很长时间,严重降低了事件循环的响应速度。
### 问题 2客户端过早关闭连接
原代码中客户端发送完数据后只是简单地 `usleep(1000)` 然后继续循环,没有正确处理连接关闭逻辑。
## 修复方案
### 修复 1优化轮询间隔
**quic_perf_server.c (第 186-191 行)**
```c
quiche_conn_on_timeout(conn);
// 使用更短的轮询间隔以提高响应速度
if (!has_outgoing) {
usleep(100); // 100微秒轮询
}
```
**quic_perf_client.c (第 125-130 行)**
```c
quiche_conn_on_timeout(conn);
// 使用更短的轮询间隔以提高响应速度
if (!has_outgoing && !finished_sending) {
usleep(100); // 100微秒轮询
}
```
### 修复 2简化客户端发送完成后的处理
```c
if (finished_sending && !has_outgoing) {
usleep(1000);
}
```
## 修复结果
| 协议 | 修复前 | 修复后 | 提升 |
|------|--------|--------|------|
| TCP | ~2200 MB/s | ~1400 MB/s | - |
| QUIC | ~6 MB/s | ~92 MB/s | **15倍** |
## 测试命令
### 正常网络环境测试
```bash
# TCP 测试
./tcp_perf_server &
sleep 1
./tcp_perf_client
wait
# QUIC 测试
./quic_perf_server &
sleep 1
./quic_perf_client
sleep 3
```
### 丢包环境测试 (5%)
```bash
# 添加 5% 丢包
sudo tc qdisc add dev lo root netem loss 5%
# 运行 TCP 和 QUIC 测试...
# 清除规则
sudo tc qdisc del dev lo root
```
### 延迟环境测试 (100ms)
```bash
# 添加 100ms 延迟
sudo tc qdisc add dev lo root netem delay 100ms
# 运行 TCP 和 QUIC 测试...
# 清除规则
sudo tc qdisc del dev lo root
```
### 检查和清除 tc 规则
```bash
# 查看当前规则
tc qdisc show dev lo
# 清除所有规则
sudo tc qdisc del dev lo root
```
---
## 为什么 QUIC 吞吐量仍然远小于 TCP
修复后 QUIC 达到 ~92 MB/s而 TCP 达到 ~1400 MB/sQUIC 仍然只有 TCP 的 **6.5%**。这是正常现象,原因如下:
### 1. 协议栈实现位置不同
| 协议 | 实现位置 | 特点 |
|------|----------|------|
| TCP | **内核空间** | 高度优化,零拷贝,硬件卸载 |
| QUIC | **用户空间** | 需要系统调用,数据拷贝开销大 |
TCP 在 Linux 内核中经过 30+ 年优化,支持:
- **零拷贝 (Zero-copy)**`sendfile()`, `splice()`
- **硬件卸载 (TSO/GSO)**:网卡直接分段
- **内核旁路优化**:减少上下文切换
### 2. 加密开销
QUIC **强制加密**所有数据(使用 TLS 1.3),而本实验中的 TCP 是明文传输:
```
QUIC 数据路径: 应用数据 → TLS加密 → UDP封装 → 发送
TCP 数据路径: 应用数据 → TCP封装 → 发送
```
加密/解密操作消耗大量 CPU 资源。
### 3. UDP vs TCP 的系统调用效率
| 操作 | TCP | QUIC (UDP) |
|------|-----|------------|
| 发送 100MB | 少量大块 write() | 大量小包 sendto() |
| 系统调用次数 | 少 | 多 |
TCP 可以一次 `write()` 发送大块数据内核自动分段。QUIC 基于 UDP每个数据包都需要单独的 `sendto()` 调用。
### 4. 数据包大小限制
```c
#define MAX_DATAGRAM_SIZE 1350 // QUIC 单包最大载荷
```
QUIC 受 UDP MTU 限制,每个包最多 ~1350 字节。发送 100MB 需要约 **77,000+ 个数据包**
### 5. 本地回环测试的特殊性
`localhost` 测试中网络延迟几乎为零TCP 的内核优化优势被放大。QUIC 的优势(如 0-RTT、连接迁移、多路复用在这种环境下无法体现。
### 6. QUIC 真正的优势场景
QUIC 设计目标不是追求极限吞吐量,而是在**真实网络环境**中提供更好的用户体验:
| 场景 | QUIC 优势 |
|------|-----------|
| 高延迟网络 | 0-RTT 快速连接建立 |
| 丢包环境 | 无队头阻塞,单流丢包不影响其他流 |
| 移动网络 | 连接迁移,切换 WiFi/4G 不断连 |
| 多路复用 | 单连接多流,避免 TCP 队头阻塞 |
从你的日志可以看到,在 **100ms 延迟**环境下:
- TCP: 0.98 MB/s
- QUIC: 2.41 MB/s
**QUIC 在高延迟环境下性能是 TCP 的 2.5 倍!**
---
## 总结
| 环境 | TCP | QUIC | 胜者 |
|------|-----|------|------|
| 本地回环 (无延迟) | ~1400 MB/s | ~92 MB/s | TCP |
| 100ms 延迟 | 0.98 MB/s | 2.41 MB/s | **QUIC** |
**结论:** QUIC 在理想网络环境下吞吐量低于 TCP 是正常的这是用户空间实现、强制加密等设计权衡的结果。但在真实的高延迟、高丢包网络环境中QUIC 能够提供更好的性能和用户体验。

Binary file not shown.

View File

@@ -7,6 +7,7 @@
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <time.h>
#include <quiche.h>
#define MAX_DATAGRAM_SIZE 1350
@@ -78,7 +79,7 @@ int main(int argc, char *argv[]) {
bool finished_sending = false;
while (1) {
ssize_t read_len = recv(sock, buf, sizeof(buf), 0);
ssize_t read_len = recv(sock, buf, sizeof(buf), MSG_DONTWAIT);
if (read_len > 0) {
quiche_conn_recv(conn, buf, read_len, &(quiche_recv_info){
.to = (struct sockaddr *)&local_addr,
@@ -87,12 +88,12 @@ int main(int argc, char *argv[]) {
.from_len = sizeof(peer_addr),
});
}
if (quiche_conn_is_closed(conn)) {
printf("Connection closed.\n");
break;
}
if (quiche_conn_is_established(conn)) {
// printf("Connection established.\n");
while (!finished_sending) {
@@ -101,26 +102,36 @@ int main(int argc, char *argv[]) {
if (sent > 0) {
bytes_sent += sent;
if (bytes_sent >= bytes_to_send) {
quiche_conn_stream_send(conn, 4, NULL, 0, true, &err_code);
quiche_conn_stream_send(conn, 4, NULL, 0, true, &err_code);
finished_sending = true;
printf("Finished sending data.\n");
}
} else {
break;
} else if (sent == QUICHE_ERR_DONE) {
break;
}
}
}
bool has_outgoing = false;
while (1) {
quiche_send_info send_info;
ssize_t written = quiche_conn_send(conn, out, sizeof(out), &send_info);
if (written == QUICHE_ERR_DONE) break;
if (written < 0) break;
send(sock, out, written, 0);
has_outgoing = true;
}
quiche_conn_on_timeout(conn);
usleep(100);
// 使用更短的轮询间隔以提高响应速度
if (!has_outgoing && !finished_sending) {
usleep(100); // 100微秒轮询
}
if (finished_sending && !has_outgoing) {
usleep(1000);
}
}
quiche_conn_free(conn);

Binary file not shown.

View File

@@ -103,7 +103,7 @@ int main(int argc, char *argv[]) {
client->sock = sock;
client->peer_addr = peer_addr;
client->peer_addr_len = peer_addr_len;
uint8_t server_scid[QUICHE_MAX_CONN_ID_LEN];
int rng = open("/dev/urandom", O_RDONLY);
if (rng >= 0) {
@@ -117,7 +117,7 @@ int main(int argc, char *argv[]) {
printf("New performance connection accepted.\n");
}
}
if (client != NULL) {
quiche_conn_recv(client->conn, buf, read_len, &(quiche_recv_info){
.to = (struct sockaddr *)&sa,
@@ -131,7 +131,7 @@ int main(int argc, char *argv[]) {
if (client != NULL) {
quiche_conn *conn = client->conn;
if (quiche_conn_is_closed(conn)) {
printf("Connection closed.\n");
quiche_conn_free(conn);
@@ -148,7 +148,7 @@ int main(int argc, char *argv[]) {
clock_gettime(CLOCK_MONOTONIC, &client->start_time);
client->timer_started = 1;
}
uint8_t recv_buf[65535];
bool fin = false;
uint64_t err_code = 0;
@@ -162,7 +162,7 @@ int main(int argc, char *argv[]) {
double time_taken = (end.tv_sec - client->start_time.tv_sec) + (end.tv_nsec - client->start_time.tv_nsec) / 1e9;
double mb = client->total_bytes / (1024.0 * 1024.0);
double throughput = mb / time_taken;
if (!done_printing) {
printf("Received %.2f MB in %.2f seconds.\n", mb, time_taken);
printf("Throughput: %.2f MB/s\n", throughput);
@@ -172,18 +172,24 @@ int main(int argc, char *argv[]) {
}
quiche_stream_iter_free(readable);
}
bool has_outgoing = false;
while (1) {
quiche_send_info send_info;
ssize_t written = quiche_conn_send(conn, out, sizeof(out), &send_info);
if (written == QUICHE_ERR_DONE) break;
if (written < 0) break;
sendto(sock, out, written, 0, (struct sockaddr *)&send_info.to, send_info.to_len);
has_outgoing = true;
}
quiche_conn_on_timeout(conn);
// 使用更短的轮询间隔以提高响应速度
if (!has_outgoing) {
usleep(100); // 100微秒轮询
}
}
usleep(100);
}
quiche_config_free(config);