tcp/quic lab finished
This commit is contained in:
32
network/tcpquiclab/Makefile
Normal file
32
network/tcpquiclab/Makefile
Normal 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
|
||||
109
network/tcpquiclab/README_LINUX.md
Normal file
109
network/tcpquiclab/README_LINUX.md
Normal 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.
|
||||
109
network/tcpquiclab/README_LINUX_CN.md
Normal file
109
network/tcpquiclab/README_LINUX_CN.md
Normal 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`),观察连接是否能恢复传输。
|
||||
19
network/tcpquiclab/cert.crt
Normal file
19
network/tcpquiclab/cert.crt
Normal 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-----
|
||||
28
network/tcpquiclab/cert.key
Normal file
28
network/tcpquiclab/cert.key
Normal 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
BIN
network/tcpquiclab/quic_client
Executable file
Binary file not shown.
118
network/tcpquiclab/quic_client.c
Normal file
118
network/tcpquiclab/quic_client.c
Normal 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;
|
||||
}
|
||||
BIN
network/tcpquiclab/quic_perf_client
Executable file
BIN
network/tcpquiclab/quic_perf_client
Executable file
Binary file not shown.
109
network/tcpquiclab/quic_perf_client.c
Normal file
109
network/tcpquiclab/quic_perf_client.c
Normal 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;
|
||||
}
|
||||
BIN
network/tcpquiclab/quic_perf_server
Executable file
BIN
network/tcpquiclab/quic_perf_server
Executable file
Binary file not shown.
187
network/tcpquiclab/quic_perf_server.c
Normal file
187
network/tcpquiclab/quic_perf_server.c
Normal 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
BIN
network/tcpquiclab/quic_server
Executable file
Binary file not shown.
177
network/tcpquiclab/quic_server.c
Normal file
177
network/tcpquiclab/quic_server.c
Normal 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
1196
network/tcpquiclab/quiche.h
Normal file
File diff suppressed because it is too large
Load Diff
BIN
network/tcpquiclab/tcp_client
Executable file
BIN
network/tcpquiclab/tcp_client
Executable file
Binary file not shown.
53
network/tcpquiclab/tcp_client.c
Normal file
53
network/tcpquiclab/tcp_client.c
Normal 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;
|
||||
}
|
||||
BIN
network/tcpquiclab/tcp_perf_client
Executable file
BIN
network/tcpquiclab/tcp_perf_client
Executable file
Binary file not shown.
52
network/tcpquiclab/tcp_perf_client.c
Normal file
52
network/tcpquiclab/tcp_perf_client.c
Normal 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;
|
||||
}
|
||||
BIN
network/tcpquiclab/tcp_perf_server
Executable file
BIN
network/tcpquiclab/tcp_perf_server
Executable file
Binary file not shown.
74
network/tcpquiclab/tcp_perf_server.c
Normal file
74
network/tcpquiclab/tcp_perf_server.c
Normal 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
BIN
network/tcpquiclab/tcp_server
Executable file
Binary file not shown.
75
network/tcpquiclab/tcp_server.c
Normal file
75
network/tcpquiclab/tcp_server.c
Normal 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;
|
||||
}
|
||||
BIN
network/tcpquiclab/实验3讲解最终版.pptx
Normal file
BIN
network/tcpquiclab/实验3讲解最终版.pptx
Normal file
Binary file not shown.
BIN
network/tcpquiclab/计算机网络实验指导手册.pdf
Normal file
BIN
network/tcpquiclab/计算机网络实验指导手册.pdf
Normal file
Binary file not shown.
326
network/tcpquiclab/计算机网络实验指导手册.txt
Normal file
326
network/tcpquiclab/计算机网络实验指导手册.txt
Normal 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 结构体清零,确保没有残留数据,设置地址族为
|
||||
IPv4(AF_INET),绑定到所有可用的网络接口(INADDR_ANY 表示接受任意IP 地址的连接),设置端口号,使用 htons() 函数确保端口号以网络字节序
|
||||
(大端)传输。然后通过bind()绑定套接字,如果绑定失败将打印错误信息,
|
||||
释放资源后结束程序运行。
|
||||
|
||||
使用listen()命令开始监听客户端连接,输出服务器正在监听的端口号。
|
||||
等待客户端连接,输出客户端已连接的信息。
|
||||
从客户端接收数据,存入 buffer 中,输出接收到的消息,向客户端返回响
|
||||
|
||||
应,包含接收到数据的长度。在通信完成后,关闭套接字,并清理资源。
|
||||
② 客户端代码
|
||||
a. 引入相关头文件,链接 ws2_32.lib 库,定义客户端连接的目标端口号,必须
|
||||
与服务器端口一致。
|
||||
|
||||
b.
|
||||
|
||||
定义相关变量。
|
||||
|
||||
c.
|
||||
|
||||
初始化Winsock库,确保在使用套接字之前初始化 Winsock。
|
||||
|
||||
d.
|
||||
|
||||
创建客户端套接字。设置地址族为
|
||||
|
||||
IPv4(AF_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和cargo:https://rust-l
|
||||
ang.org/zh-CN/tools/install/
|
||||
② 克隆并构建quiche仓库
|
||||
a. 使用git工具进行克隆(这一步需要确保自己的主机安装了git工具,如果没有
|
||||
安装,可以通过https://git-scm.com/install/windows下载安装),使用Window
|
||||
s+R快捷键打开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 socket,bind() 到指定端口(比如 5555),设置成 non-blocking,这
|
||||
样循环不会卡死在 recvfrom() 上,此时 server 已经在 “UDP 层”听包了,但
|
||||
还没有 QUIC 连接对象
|
||||
c. 进行主循环(核心框架)
|
||||
主循环反复执行:收UDP包。recvfrom()收到一个 QUIC UDP 包,同时拿到客
|
||||
户端地址 peer_addr(用于后续回包)
|
||||
d. 解析包头:收到 UDP 包后,先用 quiche_header_info() 解析 QUIC 头部,
|
||||
拿到:version:QUIC 版本,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 后喂给 quiche(quiche_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 或端口变化后)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user