tcp/quic lab finished

This commit is contained in:
2025-12-25 14:33:29 +08:00
parent ac5b4bc15d
commit 200566e8fe
261 changed files with 2664 additions and 0 deletions

View File

@ -0,0 +1,32 @@
CC = gcc
CFLAGS = -Wall -g
LDFLAGS = -lquiche -ldl -lpthread -lm
all: tcp_server tcp_client quic_server quic_client tcp_perf_server tcp_perf_client quic_perf_server quic_perf_client
tcp_server: tcp_server.c
$(CC) $(CFLAGS) -o tcp_server tcp_server.c
tcp_client: tcp_client.c
$(CC) $(CFLAGS) -o tcp_client tcp_client.c
quic_server: quic_server.c
$(CC) $(CFLAGS) -o quic_server quic_server.c $(LDFLAGS)
quic_client: quic_client.c
$(CC) $(CFLAGS) -o quic_client quic_client.c $(LDFLAGS)
tcp_perf_server: tcp_perf_server.c
$(CC) $(CFLAGS) -o tcp_perf_server tcp_perf_server.c
tcp_perf_client: tcp_perf_client.c
$(CC) $(CFLAGS) -o tcp_perf_client tcp_perf_client.c
quic_perf_server: quic_perf_server.c
$(CC) $(CFLAGS) -o quic_perf_server quic_perf_server.c $(LDFLAGS)
quic_perf_client: quic_perf_client.c
$(CC) $(CFLAGS) -o quic_perf_client quic_perf_client.c $(LDFLAGS)
clean:
rm -f tcp_server tcp_client quic_server quic_client tcp_perf_server tcp_perf_client quic_perf_server quic_perf_client

View File

@ -0,0 +1,109 @@
# Computer Network Experiment: TCP vs QUIC (Linux Guide)
This guide adapts the Windows-based experiment manual for a Linux environment.
## 1. Prerequisites
Ensure you have the following installed:
- `gcc` (Compiler)
- `quiche` library (Headers and Shared Object installed)
- `openssl` (For certificates)
- `tcpdump` or `wireshark` (For packet capture)
- `iproute2` (For `tc` traffic control)
## 2. Compilation
Compile all programs using the provided Makefile:
```bash
make
```
This will generate:
- `tcp_server`, `tcp_client` (Task 1)
- `quic_server`, `quic_client` (Task 2)
- `tcp_perf_server`, `tcp_perf_client` (Task 3 Performance)
- `quic_perf_server`, `quic_perf_client` (Task 3 Performance)
*Note: If `quiche` is not in the standard system path, edit the `Makefile` to point to the include/lib directories.*
## 3. Task 1: Basic TCP Client-Server
1. **Start the Server:**
```bash
./tcp_server
```
2. **Run the Client (in a new terminal):**
```bash
./tcp_client
```
**Expected Output:** The client sends "Hello...", server receives it and replies.
## 4. Task 2: Basic QUIC Client-Server
1. **Start the Server:**
```bash
./quic_server
```
2. **Run the Client (in a new terminal):**
```bash
./quic_client
```
**Expected Output:** QUIC handshake completes, client sends data on a stream, server echoes it back.
## 5. Task 3: Performance Analysis
### 3.1 Connection Establishment Time
1. Start capture on loopback:
```bash
sudo tcpdump -i lo -w handshake.pcap
```
*(Or use Wireshark on the `lo` interface)*
2. Run the TCP or QUIC client/server pairs again.
3. Open `handshake.pcap` in Wireshark to analyze the time difference between the first packet (SYN for TCP, Initial for QUIC) and the completion of the handshake.
### 3.2 Throughput Test (100MB Transfer)
**Baseline (Normal Network):**
1. Run TCP Perf Server: `./tcp_perf_server`
2. Run TCP Perf Client: `./tcp_perf_client`
3. Record the MB/s output.
4. Repeat for QUIC (`./quic_perf_server`, `./quic_perf_client`).
**Simulating Network Conditions (Packet Loss / Delay):**
We use Linux `tc` (Traffic Control) with `netem` instead of `clumsy`.
**Scenario A: 5% Packet Loss**
1. Apply 5% loss to the loopback interface:
```bash
sudo tc qdisc add dev lo root netem loss 5%
```
2. Run the perf tests again.
3. **Important:** Remove the rule after testing!
```bash
sudo tc qdisc del dev lo root
```
**Scenario B: 100ms Delay**
1. Apply 100ms delay:
```bash
sudo tc qdisc add dev lo root netem delay 100ms
```
2. Run the perf tests again.
3. Remove the rule:
```bash
sudo tc qdisc del dev lo root
```
### 3.3 & 3.4 Advanced Tests
- **Multiplexing:** The current `quic_perf_client` uses a single stream (Stream ID 4). You can modify the code to launch multiple streams in parallel to test head-of-line blocking resilience.
- **Network Recovery:** Use `tc` to drop 100% packets (`loss 100%`) for 30 seconds during a long transfer, then remove the rule (`del`) to see if the connection recovers.

View File

@ -0,0 +1,109 @@
# 计算机网络实验TCP 与 QUIC 协议对比 (Linux 指南)
本指南根据 Windows 版本的实验手册,针对 Linux 环境进行了适配。
## 1. 预备工作
确保已安装以下工具和库:
- `gcc` (编译器)
- `quiche` 库 (已安装头文件和共享库/静态库)
- `openssl` (用于生成证书)
- `tcpdump``wireshark` (用于抓包)
- `iproute2` (用于 `tc` 流量控制)
## 2. 编译
使用提供的 Makefile 编译所有程序:
```bash
make
```
这将生成以下可执行文件:
- `tcp_server`, `tcp_client` (任务一:基础 TCP)
- `quic_server`, `quic_client` (任务二:基础 QUIC)
- `tcp_perf_server`, `tcp_perf_client` (任务三TCP 性能测试)
- `quic_perf_server`, `quic_perf_client` (任务三QUIC 性能测试)
*注意:如果编译报错提示找不到 `quiche.h` 或库文件,请修改 `Makefile` 中的路径,指向您本地 `quiche` 安装的 `include` 和 `lib` 目录。*
## 3. 任务一:基础 TCP 客户端-服务器
1. **启动服务器:**
```bash
./tcp_server
```
2. **运行客户端 (在新的终端窗口)**
```bash
./tcp_client
```
**预期输出:** 客户端发送消息,服务器接收并回复。
## 4. 任务二:基础 QUIC 客户端-服务器
1. **启动服务器:**
```bash
./quic_server
```
2. **运行客户端 (在新的终端窗口)**
```bash
./quic_client
```
**预期输出:** 完成 QUIC 握手,客户端发送流数据,服务器回显数据。
## 5. 任务三:性能分析
### 3.1 连接建立时间
1. 在回环接口 (`lo`) 上开始抓包:
```bash
sudo tcpdump -i lo -w handshake.pcap
```
*(或者使用 Wireshark 监听 `lo` 接口)*
2. 再次运行 TCP 或 QUIC 的客户端/服务器程序。
3. 使用 Wireshark 打开 `handshake.pcap`,分析从第一个包 (TCP 的 SYN 或 QUIC 的 Initial) 到握手完成的时间差。
### 3.2 吞吐量测试 (100MB 传输)
**基准测试 (正常网络)**
1. 运行 TCP 性能服务器:`./tcp_perf_server`
2. 运行 TCP 性能客户端:`./tcp_perf_client`
3. 记录输出的 MB/s 吞吐量。
4. 重复上述步骤测试 QUIC (`./quic_perf_server`, `./quic_perf_client`)。
**模拟网络环境 (丢包 / 延迟)**
在 Linux 下我们使用 `tc` (Traffic Control) 的 `netem` 模块,替代 Windows 下的 `clumsy` 工具。
**场景 A: 5% 丢包率**
1. 设置回环接口 5% 丢包:
```bash
sudo tc qdisc add dev lo root netem loss 5%
```
2. 再次运行性能测试程序。
3. **重要:** 测试结束后删除规则!
```bash
sudo tc qdisc del dev lo root
```
**场景 B: 100ms 延迟**
1. 设置 100ms 延迟:
```bash
sudo tc qdisc add dev lo root netem delay 100ms
```
2. 再次运行性能测试程序。
3. 删除规则:
```bash
sudo tc qdisc del dev lo root
```
### 3.3 & 3.4 进阶测试
- **多路复用:** 当前的 `quic_perf_client` 使用单个流 (Stream ID 4)。您可以修改代码并行启动多个流,以测试 QUIC 解决队头阻塞问题的能力。
- **网络恢复:** 在长传输过程中,使用 `tc` 设置 100% 丢包 (`loss 100%`) 持续 30 秒,然后删除规则 (`del`),观察连接是否能恢复传输。

View File

@ -0,0 +1,19 @@
-----BEGIN CERTIFICATE-----
MIIDCTCCAfGgAwIBAgIUW5+jEv1Pf4vBxjbLWEyXBhyKM5EwDQYJKoZIhvcNAQEL
BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI1MTIyNTA2MjEyN1oXDTI2MTIy
NTA2MjEyN1owFDESMBAGA1UEAwwJbG9jYWxob3N0MIIBIjANBgkqhkiG9w0BAQEF
AAOCAQ8AMIIBCgKCAQEAq+DK/kcAQ7oID4P/tDWT7gGaOJjbHYenPKMxqC67IBC1
x3hlu2ze828p0K81XNUmnlzhgjw9jnIlIc/oLtBZ7SQrH9o2mXB6wXeSxcVuDbUY
qp7SiqFWF8L3ZBgi+uhY3dtFN/1CrEtBgiqSyDsTGqa4MZq5L/Nh4GTzkUYyzQG1
52C/1VjDqcPLNdVoJGfMUCA2jC7rdkO+9IRqlkJQMb5XsE4kDMfal80TZiCqzH6I
FRjFCTXtoI8YlzofOlRSbChdYj0xutiH9i9UE6yb+0JHS9KVgYcUBhQ+qeHje8w5
siCrJ4s4NFSyl7GUjawCzEhLmdVCHrEMi4y7FXSbowIDAQABo1MwUTAdBgNVHQ4E
FgQU9+ZRlQIBxZZBv2Z0p1xAwoj2IeQwHwYDVR0jBBgwFoAU9+ZRlQIBxZZBv2Z0
p1xAwoj2IeQwDwYDVR0TAQH/BAUwAwEB/zANBgkqhkiG9w0BAQsFAAOCAQEAYj9D
DGopooNlH6ltJ5DIQuSXtd1FfSE3QF62vVDkPr2xhnuiHpKDjFnEQvEgYd6aCG6J
rf5YhM3yOnQMHH10WZl3bAlQ+bQUv+cHLn2X72QMZ8TumwYaIFgkkXgAHl3z309b
ZWQojJMwCIk9fy1Fl/PQQ3gjBi/5JfUtxRFeWmhbDNOJUOKlskaIdynvrgwnlJUk
0rwOMqhvHahUaJxqa7VCEx4NfmchpK9UmdXVVWSv5rrBFJUzBFPVdI4HoQeVAXCK
kuRWiHk4EOtweoJILOfDwZtIsS/yJQ4gF903GBKxJsjosKMrI0Z3ClRlRUoqxA+h
kvzgZsTfps3a3+1Zgg==
-----END CERTIFICATE-----

View File

@ -0,0 +1,28 @@
-----BEGIN PRIVATE KEY-----
MIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQCr4Mr+RwBDuggP
g/+0NZPuAZo4mNsdh6c8ozGoLrsgELXHeGW7bN7zbynQrzVc1SaeXOGCPD2OciUh
z+gu0FntJCsf2jaZcHrBd5LFxW4NtRiqntKKoVYXwvdkGCL66Fjd20U3/UKsS0GC
KpLIOxMaprgxmrkv82HgZPORRjLNAbXnYL/VWMOpw8s11WgkZ8xQIDaMLut2Q770
hGqWQlAxvlewTiQMx9qXzRNmIKrMfogVGMUJNe2gjxiXOh86VFJsKF1iPTG62If2
L1QTrJv7QkdL0pWBhxQGFD6p4eN7zDmyIKsnizg0VLKXsZSNrALMSEuZ1UIesQyL
jLsVdJujAgMBAAECggEABnWhS2MhuGniaardTkmBML2wrRXZjkeN2nKLqDVxZOgb
3M5CjIOv8VgpKyWajM2Z8POuqfVXnuXdTWMB59h+uLHWSRErYiLfCixTQMmFtFAt
CCSF5x3fHW9/Wqypi+J1jIj0FgGXouAFKz4sXAgUVLkVTQ/yi8HK2OMSYFBSEnBB
JPPKScFfDGCbsC3ij9/Jl0IB2o3qRuwUNkcwvdeMm3iK+N9CHOqZ0yx/2L+EncsD
SH9FQEFkCa0u2aroVFddfDikzzPXxYeG+WYrEkLjr+Blfq54PHeJzfV35ppO4lZ5
U0o71hqX7guzb2XQFBLGw6GxVqrQzfCU6ADveO1jIQKBgQDrxKJRF4BxjS0QdF7w
DmnFg9aU8K6jXzaOdWQ9h7lcFanw9hNdbi5ZFEBqhgiw6IpEnm/raD0+diJiJuyd
u/WH6mT8tZpXRNmoKJcRuxft5XXLdpgG3Dy+AEpINpnH9Od0ZzuGBHcXuSKay8sN
/gChZotwg4bASnu4DhE61szH0wKBgQC6oJ60191vIvb4NJEaq6CJps7DvqgA6MMR
RypBh30pbY08RwrCBzD4i+9Lg1fBoKQD53qfrjV1KUIlqw2VhyIBU0oUFntBbNFm
nmRYVhcgwN9cpLF4ngnDGyv9t/aRvdb7XXEjBe5kzmJ6bHWZYsiABEzQPXmySiNd
Xjak8wDK8QKBgGCjneDdYEGrG3CmMo/1aHeUfa1ZJkxC58rm5WqvKlRLcPga9X8T
fZ33xhujywYwmxRbWQbGGGx04c0XpV9WPuMyOai4C4Z/6zOZR6r15G4X4vu+JbyS
I7fByLDm6Ivkn8a/1c8uH16y/TM0G4wltD50GO3Ki75gCWw9H+TdTN0PAoGAS/EI
I7ajWJH1xVI+qYelL948zNJMMvKETgeTXk7v02fMzPZrnkCm4Lywhx3PG+9uTlhn
QYr2HdLII+PTB0GRyNBHmJz9UsYg/4z8cCW4C4/wVBaAUQCeIFJyODAfmXaSgH6p
hwLm2wZQdFimEeBsjgsx5prdZntcoKWxvrVTYUECgYAfcIaMcIO6BNhPnYsHmqlV
NcELQnRg/GHw42SiDQ1V772TivrTfOPn5MzPg9/PmrDlCa5ZIqecKtWcgjF8TJkH
1Xltn+Ar+ERDJWuFSS5Wu0CT8n1jp0rfxAuzRL7/zN9wWqn4t5Bd6vebU4RmENyr
s/UT4EovEshPpRCY3mQawg==
-----END PRIVATE KEY-----

BIN
network/tcpquiclab/quic_client Executable file

Binary file not shown.

View File

@ -0,0 +1,118 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <quiche.h>
#define MAX_DATAGRAM_SIZE 1350
int main(int argc, char *argv[]) {
quiche_config *config = quiche_config_new(QUICHE_PROTOCOL_VERSION);
if (config == NULL) return -1;
quiche_config_verify_peer(config, false);
quiche_config_set_application_protos(config, (uint8_t *) "\x0ahq-interop\x05hq-29\x05hq-28\x05hq-27\x08http/0.9", 38);
quiche_config_set_max_idle_timeout(config, 5000);
quiche_config_set_max_recv_udp_payload_size(config, MAX_DATAGRAM_SIZE);
quiche_config_set_max_send_udp_payload_size(config, MAX_DATAGRAM_SIZE);
quiche_config_set_initial_max_data(config, 10000000);
quiche_config_set_initial_max_stream_data_bidi_local(config, 1000000);
quiche_config_set_initial_max_streams_bidi(config, 100);
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0) return -1;
struct sockaddr_in peer_addr;
memset(&peer_addr, 0, sizeof(peer_addr));
peer_addr.sin_family = AF_INET;
peer_addr.sin_port = htons(8888);
inet_pton(AF_INET, "127.0.0.1", &peer_addr.sin_addr);
if (connect(sock, (struct sockaddr *)&peer_addr, sizeof(peer_addr)) < 0) {
perror("connect");
return -1;
}
int flags = fcntl(sock, F_GETFL, 0);
fcntl(sock, F_SETFL, flags | O_NONBLOCK);
uint8_t scid[QUICHE_MAX_CONN_ID_LEN];
int rng = open("/dev/urandom", O_RDONLY);
if (rng >= 0) {
read(rng, scid, sizeof(scid));
close(rng);
}
quiche_conn *conn = quiche_connect("127.0.0.1", (const uint8_t *)scid, sizeof(scid), NULL, 0, (struct sockaddr *)&peer_addr, sizeof(peer_addr), config);
if (conn == NULL) {
fprintf(stderr, "quiche_connect failed\n");
return -1;
}
printf("Connecting to QUIC server...\n");
uint8_t buf[65535];
uint8_t out[MAX_DATAGRAM_SIZE];
bool req_sent = false;
while (1) {
ssize_t read_len = recv(sock, buf, sizeof(buf), 0);
if (read_len > 0) {
quiche_conn_recv(conn, buf, read_len, &(quiche_recv_info){
.to = NULL,
.to_len = 0,
.from = (struct sockaddr *)&peer_addr,
.from_len = sizeof(peer_addr),
});
}
if (quiche_conn_is_closed(conn)) {
printf("Connection closed.\n");
break;
}
if (quiche_conn_is_established(conn)) {
if (!req_sent) {
const char *msg = "Hello from QUIC Client!";
uint64_t err_code = 0;
quiche_conn_stream_send(conn, 4, (uint8_t*)msg, strlen(msg), true, &err_code);
printf("Sent: %s\n", msg);
req_sent = true;
}
uint64_t s = 0;
quiche_stream_iter *readable = quiche_conn_readable(conn);
while (quiche_stream_iter_next(readable, &s)) {
uint8_t recv_buf[1024];
bool fin = false;
uint64_t err_code = 0;
ssize_t len = quiche_conn_stream_recv(conn, s, recv_buf, sizeof(recv_buf), &fin, &err_code);
if (len > 0) {
printf("Received: %.*s\n", (int)len, recv_buf);
quiche_conn_close(conn, true, 0, (const uint8_t *)"Done", 4);
}
}
quiche_stream_iter_free(readable);
}
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);
}
quiche_conn_on_timeout(conn);
usleep(1000);
}
quiche_conn_free(conn);
quiche_config_free(config);
return 0;
}

Binary file not shown.

View File

@ -0,0 +1,109 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <quiche.h>
#define MAX_DATAGRAM_SIZE 1350
#define TARGET_MB 100
int main(int argc, char *argv[]) {
quiche_config *config = quiche_config_new(QUICHE_PROTOCOL_VERSION);
if (config == NULL) return -1;
quiche_config_verify_peer(config, false);
quiche_config_set_application_protos(config, (uint8_t *) "\x0ahq-interop\x05hq-29\x05hq-28\x05hq-27\x08http/0.9", 38);
quiche_config_set_max_idle_timeout(config, 10000);
quiche_config_set_max_recv_udp_payload_size(config, MAX_DATAGRAM_SIZE);
quiche_config_set_max_send_udp_payload_size(config, MAX_DATAGRAM_SIZE);
quiche_config_set_initial_max_data(config, 1024 * 1024 * 200);
quiche_config_set_initial_max_stream_data_bidi_local(config, 1024 * 1024 * 200);
quiche_config_set_initial_max_stream_data_bidi_remote(config, 1024 * 1024 * 200);
quiche_config_set_initial_max_streams_bidi(config, 100);
quiche_config_set_cc_algorithm(config, QUICHE_CC_RENO);
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0) return -1;
struct sockaddr_in peer_addr;
memset(&peer_addr, 0, sizeof(peer_addr));
peer_addr.sin_family = AF_INET;
peer_addr.sin_port = htons(8889);
inet_pton(AF_INET, "127.0.0.1", &peer_addr.sin_addr);
if (connect(sock, (struct sockaddr *)&peer_addr, sizeof(peer_addr)) < 0) return -1;
int flags = fcntl(sock, F_GETFL, 0);
fcntl(sock, F_SETFL, flags | O_NONBLOCK);
uint8_t scid[QUICHE_MAX_CONN_ID_LEN];
int rng = open("/dev/urandom", O_RDONLY);
if (rng >= 0) {
read(rng, scid, sizeof(scid));
close(rng);
}
quiche_conn *conn = quiche_connect("127.0.0.1", (const uint8_t *)scid, sizeof(scid), NULL, 0, (struct sockaddr *)&peer_addr, sizeof(peer_addr), config);
printf("Connecting to QUIC Perf Server and sending %d MB...\n", TARGET_MB);
uint8_t buf[65535];
uint8_t out[MAX_DATAGRAM_SIZE];
uint8_t payload[4096];
memset(payload, 'A', sizeof(payload));
long long bytes_to_send = (long long)TARGET_MB * 1024 * 1024;
long long bytes_sent = 0;
bool finished_sending = false;
while (1) {
ssize_t read_len = recv(sock, buf, sizeof(buf), 0);
if (read_len > 0) {
quiche_conn_recv(conn, buf, read_len, &(quiche_recv_info){
.to = NULL,
.to_len = 0,
.from = (struct sockaddr *)&peer_addr,
.from_len = sizeof(peer_addr),
});
}
if (quiche_conn_is_closed(conn)) break;
if (quiche_conn_is_established(conn)) {
while (!finished_sending) {
uint64_t err_code = 0;
ssize_t sent = quiche_conn_stream_send(conn, 4, payload, sizeof(payload), false, &err_code);
if (sent > 0) {
bytes_sent += sent;
if (bytes_sent >= bytes_to_send) {
quiche_conn_stream_send(conn, 4, NULL, 0, true, &err_code);
finished_sending = true;
printf("Finished sending data.\n");
}
} else {
break;
}
}
}
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);
}
quiche_conn_on_timeout(conn);
usleep(100);
}
quiche_conn_free(conn);
quiche_config_free(config);
return 0;
}

Binary file not shown.

View File

@ -0,0 +1,187 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <quiche.h>
#include <time.h>
#define MAX_DATAGRAM_SIZE 1350
#define LOCAL_CONN_ID_LEN 16
// Simple structure to hold connection state
typedef struct {
int sock;
struct sockaddr_storage peer_addr;
socklen_t peer_addr_len;
quiche_conn *conn;
long long total_bytes;
struct timespec start_time;
int timer_started;
} Client;
int main(int argc, char *argv[]) {
// 1. Configuration
quiche_config *config = quiche_config_new(QUICHE_PROTOCOL_VERSION);
if (config == NULL) return -1;
quiche_config_load_cert_chain_from_pem_file(config, "cert.crt");
quiche_config_load_priv_key_from_pem_file(config, "cert.key");
quiche_config_set_application_protos(config, (uint8_t *) "\x0ahq-interop\x05hq-29\x05hq-28\x05hq-27\x08http/0.9", 38);
quiche_config_set_max_idle_timeout(config, 10000);
quiche_config_set_max_recv_udp_payload_size(config, MAX_DATAGRAM_SIZE);
quiche_config_set_max_send_udp_payload_size(config, MAX_DATAGRAM_SIZE);
// Increase limits for performance
quiche_config_set_initial_max_data(config, 1024 * 1024 * 200);
quiche_config_set_initial_max_stream_data_bidi_local(config, 1024 * 1024 * 200);
quiche_config_set_initial_max_stream_data_bidi_remote(config, 1024 * 1024 * 200);
quiche_config_set_initial_max_streams_bidi(config, 100);
quiche_config_set_cc_algorithm(config, QUICHE_CC_RENO);
struct sockaddr_in sa;
memset(&sa, 0, sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_port = htons(8889);
sa.sin_addr.s_addr = INADDR_ANY;
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0) return -1;
if (bind(sock, (struct sockaddr *)&sa, sizeof(sa)) < 0) return -1;
int flags = fcntl(sock, F_GETFL, 0);
fcntl(sock, F_SETFL, flags | O_NONBLOCK);
printf("QUIC Performance Server listening on port 8889\n");
Client *client = NULL;
uint8_t buf[65535];
uint8_t out[MAX_DATAGRAM_SIZE];
bool done_printing = false;
while (1) {
struct sockaddr_storage peer_addr;
socklen_t peer_addr_len = sizeof(peer_addr);
ssize_t read_len = recvfrom(sock, buf, sizeof(buf), 0, (struct sockaddr *)&peer_addr, &peer_addr_len);
if (read_len > 0) {
uint8_t type;
uint32_t version;
uint8_t scid[QUICHE_MAX_CONN_ID_LEN];
size_t scid_len = sizeof(scid);
uint8_t dcid[QUICHE_MAX_CONN_ID_LEN];
size_t dcid_len = sizeof(dcid);
uint8_t token[256];
size_t token_len = sizeof(token);
int rc = quiche_header_info(buf, read_len, LOCAL_CONN_ID_LEN, &version, &type, scid, &scid_len, dcid, &dcid_len, token, &token_len);
if (rc >= 0) {
if (client == NULL) {
if (!quiche_version_is_supported(version)) {
ssize_t written = quiche_negotiate_version(scid, scid_len, dcid, dcid_len, out, sizeof(out));
if (written > 0) sendto(sock, out, written, 0, (struct sockaddr *)&peer_addr, peer_addr_len);
} else if (token_len == 0) {
uint8_t new_scid[QUICHE_MAX_CONN_ID_LEN];
int rng = open("/dev/urandom", O_RDONLY);
if (rng >= 0) {
read(rng, new_scid, sizeof(new_scid));
close(rng);
}
ssize_t written = quiche_retry(scid, scid_len, dcid, dcid_len, new_scid, sizeof(new_scid), token, token_len, version, out, sizeof(out));
if (written > 0) sendto(sock, out, written, 0, (struct sockaddr *)&peer_addr, peer_addr_len);
} else {
client = malloc(sizeof(Client));
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) {
read(rng, server_scid, sizeof(server_scid));
close(rng);
}
client->conn = quiche_accept(server_scid, sizeof(server_scid), dcid, dcid_len, (struct sockaddr *)&sa, sizeof(sa), (struct sockaddr *)&peer_addr, peer_addr_len, config);
client->total_bytes = 0;
client->timer_started = 0;
printf("New performance connection accepted.\n");
}
}
if (client != NULL) {
quiche_conn_recv(client->conn, buf, read_len, &(quiche_recv_info){
.to = (struct sockaddr *)&sa,
.to_len = sizeof(sa),
.from = (struct sockaddr *)&peer_addr,
.from_len = peer_addr_len,
});
}
}
}
if (client != NULL) {
quiche_conn *conn = client->conn;
if (quiche_conn_is_closed(conn)) {
printf("Connection closed.\n");
quiche_conn_free(conn);
free(client);
client = NULL;
continue;
}
if (quiche_conn_is_established(conn)) {
uint64_t s = 0;
quiche_stream_iter *readable = quiche_conn_readable(conn);
while (quiche_stream_iter_next(readable, &s)) {
if (!client->timer_started) {
clock_gettime(CLOCK_MONOTONIC, &client->start_time);
client->timer_started = 1;
}
uint8_t recv_buf[65535];
bool fin = false;
uint64_t err_code = 0;
ssize_t recv_bytes = quiche_conn_stream_recv(conn, s, recv_buf, sizeof(recv_buf), &fin, &err_code);
if (recv_bytes > 0) {
client->total_bytes += recv_bytes;
}
if (fin) {
struct timespec end;
clock_gettime(CLOCK_MONOTONIC, &end);
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);
done_printing = true;
}
}
}
quiche_stream_iter_free(readable);
}
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);
}
quiche_conn_on_timeout(conn);
}
usleep(100);
}
quiche_config_free(config);
return 0;
}

BIN
network/tcpquiclab/quic_server Executable file

Binary file not shown.

View File

@ -0,0 +1,177 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <quiche.h>
#define LOCAL_CONN_ID_LEN 16
#define MAX_DATAGRAM_SIZE 1350
typedef struct {
int sock;
struct sockaddr_storage peer_addr;
socklen_t peer_addr_len;
quiche_conn *conn;
} Client;
int main(int argc, char *argv[]) {
quiche_config *config = quiche_config_new(QUICHE_PROTOCOL_VERSION);
if (config == NULL) {
fprintf(stderr, "failed to create config\n");
return -1;
}
quiche_config_load_cert_chain_from_pem_file(config, "cert.crt");
quiche_config_load_priv_key_from_pem_file(config, "cert.key");
quiche_config_set_application_protos(config, (uint8_t *) "\x0ahq-interop\x05hq-29\x05hq-28\x05hq-27\x08http/0.9", 38);
quiche_config_set_max_idle_timeout(config, 5000);
quiche_config_set_max_recv_udp_payload_size(config, MAX_DATAGRAM_SIZE);
quiche_config_set_max_send_udp_payload_size(config, MAX_DATAGRAM_SIZE);
quiche_config_set_initial_max_data(config, 10000000);
quiche_config_set_initial_max_stream_data_bidi_local(config, 1000000);
quiche_config_set_initial_max_stream_data_bidi_remote(config, 1000000);
quiche_config_set_initial_max_streams_bidi(config, 100);
quiche_config_set_cc_algorithm(config, QUICHE_CC_RENO);
struct sockaddr_in sa;
memset(&sa, 0, sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_port = htons(8888);
sa.sin_addr.s_addr = INADDR_ANY;
int sock = socket(AF_INET, SOCK_DGRAM, 0);
if (sock < 0) {
perror("socket");
return -1;
}
if (bind(sock, (struct sockaddr *)&sa, sizeof(sa)) < 0) {
perror("bind");
return -1;
}
int flags = fcntl(sock, F_GETFL, 0);
fcntl(sock, F_SETFL, flags | O_NONBLOCK);
printf("QUIC Server listening on port 8888\n");
Client *client = NULL;
uint8_t buf[65535];
uint8_t out[MAX_DATAGRAM_SIZE];
while (1) {
struct sockaddr_storage peer_addr;
socklen_t peer_addr_len = sizeof(peer_addr);
ssize_t read_len = recvfrom(sock, buf, sizeof(buf), 0, (struct sockaddr *)&peer_addr, &peer_addr_len);
if (read_len < 0) {
if (errno != EWOULDBLOCK && errno != EAGAIN) {
perror("recvfrom");
break;
}
} else {
uint8_t type;
uint32_t version;
uint8_t scid[QUICHE_MAX_CONN_ID_LEN];
size_t scid_len = sizeof(scid);
uint8_t dcid[QUICHE_MAX_CONN_ID_LEN];
size_t dcid_len = sizeof(dcid);
uint8_t token[256];
size_t token_len = sizeof(token);
int rc = quiche_header_info(buf, read_len, LOCAL_CONN_ID_LEN, &version, &type, scid, &scid_len, dcid, &dcid_len, token, &token_len);
if (rc >= 0) {
if (client == NULL) {
if (!quiche_version_is_supported(version)) {
ssize_t written = quiche_negotiate_version(scid, scid_len, dcid, dcid_len, out, sizeof(out));
if (written > 0) sendto(sock, out, written, 0, (struct sockaddr *)&peer_addr, peer_addr_len);
} else if (token_len == 0) {
uint8_t new_scid[QUICHE_MAX_CONN_ID_LEN];
int rng = open("/dev/urandom", O_RDONLY);
if (rng >= 0) {
read(rng, new_scid, sizeof(new_scid));
close(rng);
}
ssize_t written = quiche_retry(scid, scid_len, dcid, dcid_len, new_scid, sizeof(new_scid), token, token_len, version, out, sizeof(out));
if (written > 0) sendto(sock, out, written, 0, (struct sockaddr *)&peer_addr, peer_addr_len);
} else {
client = malloc(sizeof(Client));
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) {
read(rng, server_scid, sizeof(server_scid));
close(rng);
}
client->conn = quiche_accept(server_scid, sizeof(server_scid), dcid, dcid_len, (struct sockaddr *)&sa, sizeof(sa), (struct sockaddr *)&peer_addr, peer_addr_len, config);
printf("New connection accepted.\n");
}
}
if (client != NULL) {
quiche_conn_recv(client->conn, buf, read_len, &(quiche_recv_info){
.to = (struct sockaddr *)&sa,
.to_len = sizeof(sa),
.from = (struct sockaddr *)&peer_addr,
.from_len = peer_addr_len,
});
}
}
}
if (client != NULL) {
quiche_conn *conn = client->conn;
if (quiche_conn_is_closed(conn)) {
printf("Connection closed.\n");
quiche_conn_free(conn);
free(client);
client = NULL;
continue;
}
if (quiche_conn_is_established(conn)) {
uint64_t s = 0;
quiche_stream_iter *readable = quiche_conn_readable(conn);
while (quiche_stream_iter_next(readable, &s)) {
uint8_t recv_buf[1024];
bool fin = false;
uint64_t err_code = 0;
ssize_t recv_bytes = quiche_conn_stream_recv(conn, s, recv_buf, sizeof(recv_buf), &fin, &err_code);
if (recv_bytes > 0) {
printf("Received %zd bytes on stream %lu: %.*s\n", recv_bytes, s, (int)recv_bytes, recv_buf);
char resp[1200];
snprintf(resp, sizeof(resp), "Server received: %.*s", (int)recv_bytes, recv_buf);
quiche_conn_stream_send(conn, s, (uint8_t*)resp, strlen(resp), true, &err_code);
}
}
quiche_stream_iter_free(readable);
}
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);
}
quiche_conn_on_timeout(conn);
}
usleep(1000);
}
quiche_config_free(config);
return 0;
}

1196
network/tcpquiclab/quiche.h Normal file

File diff suppressed because it is too large Load Diff

BIN
network/tcpquiclab/tcp_client Executable file

Binary file not shown.

View File

@ -0,0 +1,53 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define PORT 8080
#define SERVER_IP "127.0.0.1"
#define BUFFER_SIZE 1024
int main() {
int sock = 0;
struct sockaddr_in serv_addr;
const char *hello = "Hello from TCP Client";
char buffer[BUFFER_SIZE] = {0};
// 1. Create socket
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
printf("\n Socket creation error \n");
return -1;
}
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);
// Convert IPv4 and IPv6 addresses from text to binary form
if (inet_pton(AF_INET, SERVER_IP, &serv_addr.sin_addr) <= 0) {
printf("\nInvalid address/ Address not supported \n");
return -1;
}
// 2. Connect to server
if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
printf("\nConnection Failed \n");
return -1;
}
// 3. Send data
send(sock, hello, strlen(hello), 0);
printf("Message sent to server: %s\n", hello);
// 4. Receive response
int valread = read(sock, buffer, BUFFER_SIZE);
if (valread > 0) {
printf("Server response: %s\n", buffer);
}
// 5. Close socket
close(sock);
return 0;
}

Binary file not shown.

View File

@ -0,0 +1,52 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define PORT 8081
#define SERVER_IP "127.0.0.1"
#define BUFFER_SIZE 4096
#define TARGET_MB 100
int main() {
int sock = 0;
struct sockaddr_in serv_addr;
char buffer[BUFFER_SIZE];
memset(buffer, 'A', BUFFER_SIZE); // Dummy data
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
printf("\n Socket creation error \n");
return -1;
}
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(PORT);
if (inet_pton(AF_INET, SERVER_IP, &serv_addr.sin_addr) <= 0) {
printf("\nInvalid address/ Address not supported \n");
return -1;
}
if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
printf("\nConnection Failed \n");
return -1;
}
printf("Sending %d MB of data...\n", TARGET_MB);
long long bytes_to_send = TARGET_MB * 1024 * 1024;
long long bytes_sent = 0;
while (bytes_sent < bytes_to_send) {
int to_send = (bytes_to_send - bytes_sent > BUFFER_SIZE) ? BUFFER_SIZE : (bytes_to_send - bytes_sent);
send(sock, buffer, to_send, 0);
bytes_sent += to_send;
}
printf("Data sent.\n");
close(sock);
return 0;
}

Binary file not shown.

View File

@ -0,0 +1,74 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <time.h>
#define PORT 8081
#define BUFFER_SIZE 4096
int main() {
int server_fd, new_socket;
struct sockaddr_in address;
int opt = 1;
int addrlen = sizeof(address);
char buffer[BUFFER_SIZE] = {0};
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
perror("setsockopt");
exit(EXIT_FAILURE);
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(PORT);
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("bind failed");
exit(EXIT_FAILURE);
}
if (listen(server_fd, 3) < 0) {
perror("listen");
exit(EXIT_FAILURE);
}
printf("TCP Performance Server listening on port %d...\n", PORT);
if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
perror("accept");
exit(EXIT_FAILURE);
}
printf("Client connected. Receiving data...\n");
long long total_bytes = 0;
int valread;
struct timespec start, end;
clock_gettime(CLOCK_MONOTONIC, &start);
while ((valread = read(new_socket, buffer, BUFFER_SIZE)) > 0) {
total_bytes += valread;
}
clock_gettime(CLOCK_MONOTONIC, &end);
double time_taken = (end.tv_sec - start.tv_sec) + (end.tv_nsec - start.tv_nsec) / 1e9;
double mb = total_bytes / (1024.0 * 1024.0);
double throughput = mb / time_taken;
printf("Received %.2f MB in %.2f seconds.\n", mb, time_taken);
printf("Throughput: %.2f MB/s\n", throughput);
close(new_socket);
close(server_fd);
return 0;
}

BIN
network/tcpquiclab/tcp_server Executable file

Binary file not shown.

View File

@ -0,0 +1,75 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#define PORT 8080
#define BUFFER_SIZE 1024
int main() {
int server_fd, new_socket;
struct sockaddr_in address;
int opt = 1;
int addrlen = sizeof(address);
char buffer[BUFFER_SIZE] = {0};
// 1. Create socket file descriptor
// AF_INET: IPv4, SOCK_STREAM: TCP
if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
perror("socket failed");
exit(EXIT_FAILURE);
}
// 2. Attach socket to the port 8080
// SO_REUSEADDR allows restarting the server immediately after closing
if (setsockopt(server_fd, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt))) {
perror("setsockopt");
exit(EXIT_FAILURE);
}
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY; // Listen on all interfaces
address.sin_port = htons(PORT);
// Bind
if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
perror("bind failed");
exit(EXIT_FAILURE);
}
// 3. Listen for incoming connections
if (listen(server_fd, 3) < 0) {
perror("listen");
exit(EXIT_FAILURE);
}
printf("TCP Server listening on port %d...\n", PORT);
// 4. Accept connection
if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
perror("accept");
exit(EXIT_FAILURE);
}
printf("Client connected.\n");
// 5. Receive data
int valread = read(new_socket, buffer, BUFFER_SIZE);
if (valread > 0) {
printf("Received %d bytes: %s\n", valread, buffer);
// 6. Send response (Echo length or simple ack)
char response[BUFFER_SIZE];
snprintf(response, BUFFER_SIZE, "Server received %d bytes", valread);
send(new_socket, response, strlen(response), 0);
printf("Response sent to client.\n");
}
// 7. Close socket
close(new_socket);
close(server_fd);
return 0;
}

Binary file not shown.

View File

@ -0,0 +1,326 @@
任务一头歌平台编程实现WEB服务器
任务二TCP 与 QUIC 协议性能对比分析
任务要求:
1. 基于 TCP 的客户端-服务器程序实现TCP服务器功能包括监听指定端口接受
客户端连接;接收客户端发送的消息;向客户端返回响应(包含接收数据的长
。客户端功能包括连接到TCP服务器向服务器发送指定消息接收服务器
响应并显示。
2. 基于QUIC的客户端-服务器程序实现使用quiche库实现QUIC服务器与客户端
能与第一条类似。
实验环境:
操作系统Windows或Linux
编程语言C语言其他语言也可能实现实验功能即可
依赖库socket库、quiche库
实验步骤:
1. 基于TCP的客户端-服务器程序实现
1环境配置
此处以Windows系统下使用VSCode运行c语言代码为例不同的系统、运行软件、
编程语言之间会有差异,自行决定选用工具,实现功能即可)。
在 Windows 系统中使用 VSCode 运行 C 语言代码之前,需要先配置 C 语言开
发环境。首先,在官网下载并安装 MinGW下载链接https://sourceforge.net/pr
ojects/mingw/)。安装完成后,需将 MinGW 的 bin 目录添加到系统的环境变量
Path 中,以确保能够在命令行中直接调用 gcc 等编译工具;
添加完成之后使用Windows+R命令打开cmd窗口运行命令gcc --version可验证是否成功安装。
在完成.c文件编写后必须编译并生成对应的.exe可执行文件才能启动调试过
程。
以上图中的hello.c为例其余代码的运行调试过程相同修改相应命令即可
首先在终端运行命令gcc hello.c -o hello.exe生成对应的hello.exe文件之后在对应
的路径下运行./hello.exe即可得到输出。
2代码书写
① 服务端代码
a. 引入相关头文件,链接 ws2_32.lib 库,对服务器监听的端口号和缓冲区大小
进行宏定义
定义需要用到的变量
使用Windows套接字编程首先需要初始化 Winsock 库。如果初始化失败,
打印错误信息,返回 -1 值,结束程序运行
使用socket()函数创建服务器套接字,如果创建失败,打印错误信息,清理
Winsock 资源并返回 -1 表示程序异常结束。
使用memset将 serverAddr 结构体清零,确保没有残留数据,设置地址族为
IPv4AF_INET绑定到所有可用的网络接口INADDR_ANY 表示接受任意IP 地址的连接),设置端口号,使用 htons() 函数确保端口号以网络字节序
大端传输。然后通过bind()绑定套接字,如果绑定失败将打印错误信息,
释放资源后结束程序运行。
使用listen()命令开始监听客户端连接,输出服务器正在监听的端口号。
等待客户端连接,输出客户端已连接的信息。
从客户端接收数据,存入 buffer 中,输出接收到的消息,向客户端返回响
应,包含接收到数据的长度。在通信完成后,关闭套接字,并清理资源。
② 客户端代码
a. 引入相关头文件,链接 ws2_32.lib 库,定义客户端连接的目标端口号,必须
与服务器端口一致。
b.
定义相关变量。
c.
初始化Winsock库确保在使用套接字之前初始化 Winsock。
d.
创建客户端套接字。设置地址族为
IPv4AF_INET设置目标服务器的端
口号,使用 htons() 转换为网络字节序。使用inet_addr() 将字符串格式的 IP
地址转换为 in_addr 结构体中可使用的二进制格式。此处使用的是本地回环
地址localhost意味着客户端将连接到本地运行的服务器。
连接到服务器。
和服务器进行通讯。
send() 函数将消息从客户端发送到服务器。参数 sock 是套接字message
是要发送的消息strlen(message) 是消息的长度0 表示没有使用附加标志。
recv() 函数从服务器接收数据,将其存储在 buffer 中。valread 存储实际接
收到的字节数。如果接收失败,返回值为负数,程序将打印错误信息。否则,打
印服务器的响应内容。
完成数据交换后,关闭套接字并清理 Winsock 库。
3实验结果示例
编写好.c文件后先在终端使用gcc命令生成.exe文件每次对代码进行改动后都
需要重新生成.exe文件gcc server.c -o server.exe -lws2_32其中-lws2_32表示链接
Winsock 库;生成并运行 server.exe 和 client.exe 后,可以看到以下实验结果。
4总结
本实验通过实现基于 TCP 协议的客户端-服务器通信,帮助理解网络编程的基本
原理。要求完成服务器端和客户端的代码编写,并通过实际运行验证通信功能。
2. 基于QUIC的客户端-服务器程序实现
1实验环境
本实验需要使用quiche库实现QUIC服务器与客户端除了要配置好VSCode的C语言
环境之外还需要安装quiche库确保它支持C语言的绑定。
安装quiche库的步骤如下
① 安装 Rust 和 Cargo
首先需要安装Rust开发环境Rust 包括了 cargo这是构建和管理 Rust
项目的工具。按照官网提供的下载地址和下载步骤下载Rust和cargohttps://rust-l
ang.org/zh-CN/tools/install/
② 克隆并构建quiche仓库
a. 使用git工具进行克隆这一步需要确保自己的主机安装了git工具如果没有
安装可以通过https://git-scm.com/install/windows下载安装使用Window
sR快捷键打开cmd窗口运行克隆命令
git clone https://github.com/cloudflare/quiche
cd quiche
如果上述方法遇到问题可以通过访问quiche库的github主页下载并解压其源
代码网址为https://github.com/cloudflare/quiche。
b. 准备好quiche仓库后在cmd窗口运行命令cargo build --release --features ffi构建
quiche的C语言接口注意这一步需要再下载好的quiche文件夹下完成
行这条命令没有报错后在quiche相应的文件夹下查找是否生成以下文件
Your local quiche Path\target\release\quiche.lib
Your local quiche Path\target\release\quiche.dll.lib
Your local quiche Path\target\release\libquiche.d
Your local quiche Path\target\release\libquiche.rlib
Your local quiche Path\quiche\include\quiche.h
确认存在就说明已经配置好quiche库的C语言静态库。
如果在构建quiche库的C语言接口时出现问题可以尝试检查以下问题
系统缺少 Visual Studio 或 Visual Studio Build Tools。访问https://visual
studio.microsoft.com/zh-hans/visual-cpp-build-tools/下载并安装Microsoft C
++生成工具,在安装过程中,确保选择了 C++ 构建工具(包括 Visual C
++ 编译器和链接器。安装完成之后找到编译工具链link.exe所在地
一般位于D:\Program Files (x86)\Visual Studio 2022\Build Tools\VC\Tool
s\MSVC
\14.44.35207\bin将bin文件地址添加到系统环境变量中。
未安装cmake工具。访问Cmake官网https://cmake.org/download/)下
载并安装适用于 Windows 的安装程序,选择 Windows x64 Installer。安装时确保勾选了 "Add CMake to the system PATH" 选项,这样可以在
命令行中直接使用 cmake。
安装完成后可以在终端检查是否成功安装。
未安装NASM编译器。访问官网https://www.nasm.us/)下载并安装适
合你系统的版本。安装后,将 NASM 的路径添加到 PATH 环境变量
中。在cmd窗口检验是否安装成功。
未安装clang编译器。可以参照这个博客下载并测试安装
https://blog.csdn.net/weixin_44565660/article/details/121758517
Clang安装后需要设置LIBCLANG_PATH环境变量指向libclang.dll的位
置。该文件通常位于Clang安装目录的bin文件夹下可以在该目录下找到这个
文件。检查该文件确实存在之后打开系统环境变量设置,在系统变量部分点
击新建然后添加一个新的环境变量,如下图所示。
之后验证通过运行命令echo %LIBCLANG_PATH%验证环境变量设置正确,
正确的话会返回Clang安装目录的路径。
③ 书写C语言代码熟悉quiche库的使用
a. VSCode运行环境
使用MinGW编译工具运行quiche库难度比较大不推荐新手使用因此
课程选用MSVC环境VS Build Tools运行C项目。打开“x64 Native Tools Command
Prompt for VS 2022”在窗口中cd到你的C工程目录然后输入code .启动VSCode。
b. VSCode配置文件
为了链接到quiche库需要改写相应C工程目录下.vscode的配置文件通过ar
gs命令指定静态库的位置。
c.
最小验证程序
新建verify_quiche.c先把quiche工具链跑通
#include <stdio.h>
#include <quiche.h>
int main(void) {
const char *v = quiche_version();
printf("quiche version: %s\n", v ? v : "(null)");
return 0;
}
使用cl命令生成相应的.exe文件
cl /nologo /Zi /EHsc /MD `
/I E:\quiche\quiche-master\quiche\include verify_quiche.c `
/Fe:verify_quiche.exe `
/link /LIBPATH:E:\quiche\quiche-master\target\release `
quiche.lib ws2_32.lib advapi32.lib `
crypt32.lib userenv.lib ntdll.lib
然后在VSCode终端运行该.exe文件输出quiche版本号就说明quiche的c语言
接口构建完成:
2代码书写
① 服务端代码
a. 准备quiche配置
b. 创建UDP socket并监听端口用socket(AF_INET/AF_INET6, SOCK_DGRAM, ...)创
建 UDP socketbind() 到指定端口(比如 5555设置成 non-blocking
样循环不会卡死在 recvfrom() 上,此时 server 已经在 “UDP 层”听包了,但
还没有 QUIC 连接对象
c. 进行主循环(核心框架)
主循环反复执行收UDP包。recvfrom()收到一个 QUIC UDP 包,同时拿到客
户端地址 peer_addr用于后续回包
d. 解析包头:收到 UDP 包后,先用 quiche_header_info() 解析 QUIC 头部,
拿到versionQUIC 版本dcid / scid连接 ID用于识别连接token
实验里可以不深入理解
e. 第一次收到合法包时,创建 QUIC 连接accept
f. 把 UDP 包交给 quiche 处理conn_recv
g. 如果连接已建立established就读 stream 并生成响应
h. 把 quiche 产生的所有 UDP 包发出去conn_send 循环)
② 客户端代码
a. 初始化 quiche_config
b. 创建 QUIC 连接 quiche_connect
c. 触发握手
d. recvfrom 后喂给 quichequiche_conn_recv
e. 建立连接后quiche_conn_is_established(conn))发送一次消息
f. 调用quiche_conn_readable(conn) 函数获取可读 stream对每个 stream id
调 quiche_conn_stream_recv打印响应并设置 got_resp = true
g. 每轮循环 flush_egress否则ACK / handshake / stream data 都可能“卡在内存
里没发出去”,表现为建立慢或收不到响应
h. timeout 处理
i.
收到响应后关闭连接
③ 运行代码
首先需要在终端使用openssl命令生成证书否则不能编译通过
生成证书之后使用cl命令得到相应的.exe文件cl命令参考
cl /nologo /Zi /EHsc /MD `
/I E:\quiche\quiche-master\quiche\include quic_server.c
`
/Fe:quic_server.exe
`
/link /LIBPATH:E:\quiche\quiche-master\target\release `
quiche.lib ws2_32.lib advapi32.lib crypt32.lib userenv.lib ntdll.lib
3实验结果示例
运行生成的.exe文件得到结果如下
任务三对比分析TCP与QUIC性能
任务 3.1:连接建立时间对比
任务要求:
1. 测量 TCP 三次握手时间:使用 Wireshark 捕获 TCP 连接建立过程;记录从客户
端发送 SYN 到收到服务器 ACK 的时间。
2. 测量 QUIC 连接建立时间:使用 Wireshark 捕获 QUIC 连接建立过程;记录从客
户端发送初始数据包到完成握手的时间。
3. 对比分析:记录 3 次测试的平均值,比较两种协议的连接建立效率
实验环境:
安装好wireshark软件即可
实验步骤:
1. 打开wireshark主界面之后根据书写的代码选择正确的网卡之后开始捕获
2. 根据代码端口或者协议类型设置wireshark的显示过滤器
3. 运行书写的代码模拟通信过程即可得到结果并进行分析。
实验结果示例:
任务 3.2:吞吐量测试
任务要求:
1. 修改 TCP 和 QUIC 程序,实现大文件传输功能(如传输 100MB 的随机文件)
2. 在不同网络条件下测试吞吐量:
正常网络(无丢包)
使用 tc 模拟 5% 丢包率sudo tc qdisc add dev eth0 root netem loss 5%
使用 tc 模拟 100ms 延迟sudo tc qdisc add dev eth0 root netem delay 100ms
计算并对比两种协议的吞吐量MB/s
实验环境:
为了在Windows系统上实现第2点需要准备一个网络故障模拟工具clumsy,当然,
也可以在Windows系统下安装WSL2或者Linux虚拟机使用sudo tc命令运行相同的代码并
测试吞吐量。
本手册以clumsy为例进行教学使用WSL或虚拟机工具请自行学习。
首先打开clumsy的下载网址http://jagt.github.io/clumsy/download根据系统
版本下载对应的软件压缩包。解压后得到一个文件夹:
右键clumsy.exe以管理员身份运行其主界面如下
有疑问可以参考这篇博客:
https://blog.csdn.net/hgftgfffg/article/details/147412888
实验步骤:
1. 实现大文件传输功能
1 TCP程序
① 按照提示补全TCP程序即可
② 编译与运行
gcc -O2 tcp_server.c -o tcp_server.exe -lws2_32
gcc -O2 tcp_client.c -o tcp_client.exe -lws2_32
2 QUIC程序
① 按照提示补全QUIC程序即可
② 编译与运行
依旧参照之前的格式使用cl命令进行编译得到可运行文件后运行观察结
果。
2. 在不同网络条件下测试吞吐量
使用clumsy模拟 5% 丢包率首先以管理员身份打开clumsy.exe选择Lag=100m
s点击开始然后再次运行程序观察实验结果。
使用clumsy模拟 100ms 延迟首先以管理员身份打开clumsy.exe选择Lag=100m
s点击开始然后再次运行程序观察实验结果。
3. 实验结果示例:
此处仅提供了正常网络环境下tcp传输大文件的结果示例并且是以本地loopback
进行实验作为例子建议在自己做实验的时候可以改用本级IP尝试实现实验这样结
果会更加接近真实网络且clumsy对真实网卡路径更加稳定。
任务 3.3:多路复用性能测试
任务要求:
设计多流传输测试:同时建立 5 个 TCP 连接传输数据,在单个 QUIC 连接上建
立 5 个流传输数据,测量并对比两种方式的总传输时间和资源占用,分析 QUIC 多
路复用如何解决 TCP 的队头阻塞问题。
任务 3.4:网络异常恢复测试
任务要求:
模拟网络中断后恢复的场景:
建立连接并开始传输数据
使用tc qdisc add dev eth0 root netem loss 100%模拟网络中断
30 秒后使用tc qdisc del dev eth0 root恢复网络
对比两种协议的恢复能力和数据完整性
测试 QUIC 的连接迁移能力(服务器 IP 或端口变化后)