tcp/quic lab patched
This commit is contained in:
@@ -2,7 +2,7 @@ CC = gcc
|
|||||||
CFLAGS = -Wall -g
|
CFLAGS = -Wall -g
|
||||||
LDFLAGS = -lquiche -ldl -lpthread -lm
|
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
|
all: tcp_server tcp_client quic_server quic_client tcp_perf_server tcp_perf_client quic_perf_server quic_perf_client tcp_multi_server tcp_multi_client quic_multi_server quic_multi_client
|
||||||
|
|
||||||
tcp_server: tcp_server.c
|
tcp_server: tcp_server.c
|
||||||
$(CC) $(CFLAGS) -o tcp_server tcp_server.c
|
$(CC) $(CFLAGS) -o tcp_server tcp_server.c
|
||||||
@@ -28,5 +28,17 @@ quic_perf_server: quic_perf_server.c
|
|||||||
quic_perf_client: quic_perf_client.c
|
quic_perf_client: quic_perf_client.c
|
||||||
$(CC) $(CFLAGS) -o quic_perf_client quic_perf_client.c $(LDFLAGS)
|
$(CC) $(CFLAGS) -o quic_perf_client quic_perf_client.c $(LDFLAGS)
|
||||||
|
|
||||||
|
tcp_multi_server: tcp_multi_server.c
|
||||||
|
$(CC) $(CFLAGS) -o tcp_multi_server tcp_multi_server.c -lpthread
|
||||||
|
|
||||||
|
tcp_multi_client: tcp_multi_client.c
|
||||||
|
$(CC) $(CFLAGS) -o tcp_multi_client tcp_multi_client.c -lpthread
|
||||||
|
|
||||||
|
quic_multi_server: quic_multi_server.c
|
||||||
|
$(CC) $(CFLAGS) -o quic_multi_server quic_multi_server.c $(LDFLAGS)
|
||||||
|
|
||||||
|
quic_multi_client: quic_multi_client.c
|
||||||
|
$(CC) $(CFLAGS) -o quic_multi_client quic_multi_client.c $(LDFLAGS)
|
||||||
|
|
||||||
clean:
|
clean:
|
||||||
rm -f tcp_server tcp_client quic_server quic_client tcp_perf_server tcp_perf_client quic_perf_server quic_perf_client
|
rm -f tcp_server tcp_client quic_server quic_client tcp_perf_server tcp_perf_client quic_perf_server quic_perf_client tcp_multi_server tcp_multi_client quic_multi_server quic_multi_client
|
||||||
|
|||||||
@@ -103,7 +103,38 @@ We use Linux `tc` (Traffic Control) with `netem` instead of `clumsy`.
|
|||||||
sudo tc qdisc del dev lo root
|
sudo tc qdisc del dev lo root
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3.3 & 3.4 Advanced Tests
|
### 3.3 Advanced Test: Multiplexing vs Multi-Connection
|
||||||
|
|
||||||
- **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.
|
This task compares the performance of 5 parallel TCP connections against a single QUIC connection with 5 concurrent streams.
|
||||||
- **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.
|
|
||||||
|
**Scenario 1: TCP Multi-Connection**
|
||||||
|
Establish 5 TCP connections simultaneously, each transferring 20MB (Total 100MB).
|
||||||
|
|
||||||
|
1. Start TCP Multi-Connection Server:
|
||||||
|
```bash
|
||||||
|
./tcp_multi_server
|
||||||
|
```
|
||||||
|
2. Run TCP Multi-Connection Client:
|
||||||
|
```bash
|
||||||
|
./tcp_multi_client
|
||||||
|
```
|
||||||
|
3. Record total time and throughput from the server output.
|
||||||
|
|
||||||
|
**Scenario 2: QUIC Single-Connection Multi-Streaming**
|
||||||
|
Establish 1 QUIC connection and open 5 streams concurrently, each transferring 20MB (Total 100MB).
|
||||||
|
|
||||||
|
1. Start QUIC Multi-Stream Server:
|
||||||
|
```bash
|
||||||
|
./quic_multi_server
|
||||||
|
```
|
||||||
|
2. Run QUIC Multi-Stream Client:
|
||||||
|
```bash
|
||||||
|
./quic_multi_client
|
||||||
|
```
|
||||||
|
3. Record the performance statistics.
|
||||||
|
|
||||||
|
**Analysis Points:**
|
||||||
|
- Compare completion times in a normal network.
|
||||||
|
- Use `tc` to simulate packet loss (e.g., 5%). Observe how QUIC's multiplexing avoids TCP's Head-of-Line (HoL) blocking, where a single lost packet in one TCP connection doesn't stall the other streams in QUIC.
|
||||||
|
|
||||||
|
### 3.4 Network Recovery
|
||||||
|
|||||||
@@ -103,7 +103,38 @@ make
|
|||||||
sudo tc qdisc del dev lo root
|
sudo tc qdisc del dev lo root
|
||||||
```
|
```
|
||||||
|
|
||||||
### 3.3 & 3.4 进阶测试
|
### 3.3 进阶测试:多路复用与多连接对比
|
||||||
|
|
||||||
- **多路复用:** 当前的 `quic_perf_client` 使用单个流 (Stream ID 4)。您可以修改代码并行启动多个流,以测试 QUIC 解决队头阻塞问题的能力。
|
本实验任务要求对比 TCP 多连接与 QUIC 多流复用的性能。
|
||||||
- **网络恢复:** 在长传输过程中,使用 `tc` 设置 100% 丢包 (`loss 100%`) 持续 30 秒,然后删除规则 (`del`),观察连接是否能恢复传输。
|
|
||||||
|
**场景 1: TCP 多连接并发**
|
||||||
|
同时建立 5 个 TCP 连接,每个连接传输 20MB 数据 (总计 100MB)。
|
||||||
|
|
||||||
|
1. 启动 TCP 多连接服务器:
|
||||||
|
```bash
|
||||||
|
./tcp_multi_server
|
||||||
|
```
|
||||||
|
2. 启动 TCP 多连接客户端:
|
||||||
|
```bash
|
||||||
|
./tcp_multi_client
|
||||||
|
```
|
||||||
|
3. 记录服务器输出的总时间与吞吐量。
|
||||||
|
|
||||||
|
**场景 2: QUIC 单连接多流复用**
|
||||||
|
建立 1 个 QUIC 连接,在其中同时开启 5 个流 (Stream),每个流传输 20MB 数据 (总计 100MB)。
|
||||||
|
|
||||||
|
1. 启动 QUIC 多流服务器:
|
||||||
|
```bash
|
||||||
|
./quic_multi_server
|
||||||
|
```
|
||||||
|
2. 启动 QUIC 多流客户端:
|
||||||
|
```bash
|
||||||
|
./quic_multi_client
|
||||||
|
```
|
||||||
|
3. 记录服务器输出的统计数据。
|
||||||
|
|
||||||
|
**分析重点:**
|
||||||
|
- 在正常网络下,两者的总耗时差异。
|
||||||
|
- 使用 `tc` 模拟丢包 (如 5%) 后,对比两者性能下降的幅度。QUIC 的多流复用应能避免 TCP 的“队头阻塞”问题 (即一个包丢失不影响其他流的传输),从而在丢包环境下表现更优。
|
||||||
|
|
||||||
|
### 3.4 网络恢复测试
|
||||||
|
|||||||
BIN
network/tcpquiclab/quic_multi_client
Executable file
BIN
network/tcpquiclab/quic_multi_client
Executable file
Binary file not shown.
169
network/tcpquiclab/quic_multi_client.c
Normal file
169
network/tcpquiclab/quic_multi_client.c
Normal file
@@ -0,0 +1,169 @@
|
|||||||
|
#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 <stdbool.h>
|
||||||
|
|
||||||
|
#define MAX_DATAGRAM_SIZE 1350
|
||||||
|
#define TOTAL_MB 100
|
||||||
|
#define NUM_STREAMS 5
|
||||||
|
#define MB_PER_STREAM (TOTAL_MB / NUM_STREAMS)
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
uint64_t stream_id;
|
||||||
|
long long bytes_sent;
|
||||||
|
long long bytes_total;
|
||||||
|
bool finished;
|
||||||
|
} StreamState;
|
||||||
|
|
||||||
|
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 * 500);
|
||||||
|
quiche_config_set_initial_max_stream_data_bidi_local(config, 1024 * 1024 * 100);
|
||||||
|
quiche_config_set_initial_max_stream_data_bidi_remote(config, 1024 * 1024 * 100);
|
||||||
|
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, "100.115.45.1", &peer_addr.sin_addr);
|
||||||
|
|
||||||
|
if (connect(sock, (struct sockaddr *)&peer_addr, sizeof(peer_addr)) < 0) return -1;
|
||||||
|
|
||||||
|
struct sockaddr_in local_addr;
|
||||||
|
socklen_t local_addr_len = sizeof(local_addr);
|
||||||
|
if (getsockname(sock, (struct sockaddr *)&local_addr, &local_addr_len) < 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);
|
||||||
|
read(rng, scid, sizeof(scid));
|
||||||
|
close(rng);
|
||||||
|
|
||||||
|
quiche_conn *conn = quiche_connect("100.115.45.1", (const uint8_t *)scid, sizeof(scid), (struct sockaddr *)&local_addr, local_addr_len, (struct sockaddr *)&peer_addr, sizeof(peer_addr), config);
|
||||||
|
if (conn == NULL) return -1;
|
||||||
|
|
||||||
|
printf("Connecting to QUIC Multi-Stream Server...\n");
|
||||||
|
printf("Sending %d streams, %d MB each (Total %d MB)...\n", NUM_STREAMS, MB_PER_STREAM, TOTAL_MB);
|
||||||
|
|
||||||
|
uint8_t buf[65535];
|
||||||
|
uint8_t out[MAX_DATAGRAM_SIZE];
|
||||||
|
uint8_t payload[4096];
|
||||||
|
memset(payload, 'C', sizeof(payload));
|
||||||
|
|
||||||
|
// Initialize stream states
|
||||||
|
StreamState streams[NUM_STREAMS];
|
||||||
|
for (int i = 0; i < NUM_STREAMS; i++) {
|
||||||
|
streams[i].stream_id = 4 * i + 4; // 4, 8, 12, 16, 20... (Client Bidi) or simple increment if library handles it?
|
||||||
|
// Note: Client initiated bidi streams usually start at 0, then 4, 8...
|
||||||
|
// but let's stick to explicit IDs or check quiche docs.
|
||||||
|
// Quiche expects us to use IDs. 0, 4, 8, 12, 16 are valid client bidi.
|
||||||
|
streams[i].stream_id = i * 4;
|
||||||
|
streams[i].bytes_sent = 0;
|
||||||
|
streams[i].bytes_total = (long long)MB_PER_STREAM * 1024 * 1024;
|
||||||
|
streams[i].finished = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool all_finished = false;
|
||||||
|
|
||||||
|
while (1) {
|
||||||
|
ssize_t read_len = recv(sock, buf, sizeof(buf), MSG_DONTWAIT);
|
||||||
|
if (read_len > 0) {
|
||||||
|
quiche_conn_recv(conn, buf, read_len, &(quiche_recv_info){
|
||||||
|
.to = (struct sockaddr *)&local_addr,
|
||||||
|
.to_len = local_addr_len,
|
||||||
|
.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)) {
|
||||||
|
all_finished = true;
|
||||||
|
for (int i = 0; i < NUM_STREAMS; i++) {
|
||||||
|
if (!streams[i].finished) {
|
||||||
|
all_finished = false;
|
||||||
|
// Try to send on this stream
|
||||||
|
while (streams[i].bytes_sent < streams[i].bytes_total) {
|
||||||
|
uint64_t err_code = 0;
|
||||||
|
// Determine payload size
|
||||||
|
ssize_t sent = quiche_conn_stream_send(conn, streams[i].stream_id, payload, sizeof(payload), false, &err_code);
|
||||||
|
if (sent > 0) {
|
||||||
|
streams[i].bytes_sent += sent;
|
||||||
|
if (streams[i].bytes_sent >= streams[i].bytes_total) {
|
||||||
|
// Send FIN
|
||||||
|
quiche_conn_stream_send(conn, streams[i].stream_id, NULL, 0, true, &err_code);
|
||||||
|
streams[i].finished = true;
|
||||||
|
printf("Stream %ld finished.\n", streams[i].stream_id);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// E.g. Done (congestion) or Stream Limit
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (all_finished) {
|
||||||
|
// Wait a bit to ensure ACKs or just exit?
|
||||||
|
// Ideally wait for close, but let's just loop a bit or wait for idle.
|
||||||
|
// Actually the server will likely not close connection, we can just idle.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool has_outgoing = false;
|
||||||
|
while (1) {
|
||||||
|
quiche_send_info send_info;
|
||||||
|
ssize_t written = quiche_conn_send(conn, out, sizeof(out), &send_info);
|
||||||
|
if (written == QUICHE_ERR_DONE) break;
|
||||||
|
if (written < 0) break;
|
||||||
|
send(sock, out, written, 0);
|
||||||
|
has_outgoing = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
quiche_conn_on_timeout(conn);
|
||||||
|
if (!has_outgoing && !all_finished) usleep(100);
|
||||||
|
if (all_finished && !has_outgoing) {
|
||||||
|
// Maybe wait for connection close or timeout
|
||||||
|
usleep(100000); // Wait 100ms
|
||||||
|
// quiche_conn_close(conn, true, 0, "Done", 4);
|
||||||
|
// break;
|
||||||
|
// Let's keep it alive for a moment for server to ack then exit
|
||||||
|
static int linger = 0;
|
||||||
|
if (linger++ > 20) {
|
||||||
|
printf("All streams finished. Closing.\n");
|
||||||
|
uint8_t reason[] = "done";
|
||||||
|
quiche_conn_close(conn, true, 0, reason, sizeof(reason));
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
quiche_conn_free(conn);
|
||||||
|
quiche_config_free(config);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
BIN
network/tcpquiclab/quic_multi_server
Executable file
BIN
network/tcpquiclab/quic_multi_server
Executable file
Binary file not shown.
179
network/tcpquiclab/quic_multi_server.c
Normal file
179
network/tcpquiclab/quic_multi_server.c
Normal file
@@ -0,0 +1,179 @@
|
|||||||
|
#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
|
||||||
|
#define TARGET_MB 100 // Target total MB to receive
|
||||||
|
|
||||||
|
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[]) {
|
||||||
|
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);
|
||||||
|
quiche_config_set_initial_max_data(config, 1024 * 1024 * 500);
|
||||||
|
quiche_config_set_initial_max_stream_data_bidi_local(config, 1024 * 1024 * 100);
|
||||||
|
quiche_config_set_initial_max_stream_data_bidi_remote(config, 1024 * 1024 * 100);
|
||||||
|
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 Multi-Stream Server listening on port 8889\n");
|
||||||
|
printf("Expecting approx %d MB total data...\n", TARGET_MB);
|
||||||
|
|
||||||
|
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 {
|
||||||
|
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);
|
||||||
|
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("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");
|
||||||
|
free(client);
|
||||||
|
client = NULL;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
quiche_stream_iter_free(readable);
|
||||||
|
|
||||||
|
// Check if target reached (fuzzy check as logic overhead might mean exact bytes vary slightly or we just use >=)
|
||||||
|
if (client->total_bytes >= (long long)TARGET_MB * 1024 * 1024 && !done_printing) {
|
||||||
|
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;
|
||||||
|
|
||||||
|
printf("\nTest Finished:\n");
|
||||||
|
printf("Total Data Received: %.2f MB\n", mb);
|
||||||
|
printf("Time Taken: %.2f seconds\n", time_taken);
|
||||||
|
printf("Total Throughput: %.2f MB/s\n", throughput);
|
||||||
|
done_printing = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool has_outgoing = false;
|
||||||
|
while (1) {
|
||||||
|
quiche_send_info send_info;
|
||||||
|
ssize_t written = quiche_conn_send(conn, out, sizeof(out), &send_info);
|
||||||
|
if (written == QUICHE_ERR_DONE) break;
|
||||||
|
if (written < 0) break;
|
||||||
|
sendto(sock, out, written, 0, (struct sockaddr *)&send_info.to, send_info.to_len);
|
||||||
|
has_outgoing = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
quiche_conn_on_timeout(conn);
|
||||||
|
if (!has_outgoing && !done_printing) usleep(100);
|
||||||
|
if (done_printing && !has_outgoing) usleep(10000); // Slow down if done
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
quiche_config_free(config);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
BIN
network/tcpquiclab/tcp_multi_client
Executable file
BIN
network/tcpquiclab/tcp_multi_client
Executable file
Binary file not shown.
70
network/tcpquiclab/tcp_multi_client.c
Normal file
70
network/tcpquiclab/tcp_multi_client.c
Normal file
@@ -0,0 +1,70 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
|
||||||
|
#define PORT 8081
|
||||||
|
#define SERVER_IP "100.115.45.1" // Default IP, change if needed
|
||||||
|
#define BUFFER_SIZE 4096
|
||||||
|
#define TOTAL_MB 100
|
||||||
|
#define NUM_CONNS 5
|
||||||
|
#define MB_PER_CONN (TOTAL_MB / NUM_CONNS)
|
||||||
|
|
||||||
|
void *send_data(void *arg) {
|
||||||
|
int sock = 0;
|
||||||
|
struct sockaddr_in serv_addr;
|
||||||
|
char buffer[BUFFER_SIZE];
|
||||||
|
memset(buffer, 'B', BUFFER_SIZE);
|
||||||
|
|
||||||
|
if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
|
||||||
|
printf("\n Socket creation error \n");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
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 NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
|
||||||
|
printf("\nConnection Failed \n");
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
long long bytes_to_send = (long long)MB_PER_CONN * 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
close(sock);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(int argc, char const *argv[]) {
|
||||||
|
pthread_t threads[NUM_CONNS];
|
||||||
|
printf("Starting %d TCP connections, sending %d MB each (Total %d MB).\n", NUM_CONNS, MB_PER_CONN, TOTAL_MB);
|
||||||
|
|
||||||
|
for (int i = 0; i < NUM_CONNS; i++) {
|
||||||
|
if (pthread_create(&threads[i], NULL, send_data, NULL) != 0) {
|
||||||
|
perror("Thread create failed");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int i = 0; i < NUM_CONNS; i++) {
|
||||||
|
pthread_join(threads[i], NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("All connections finished sending.\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
BIN
network/tcpquiclab/tcp_multi_server
Executable file
BIN
network/tcpquiclab/tcp_multi_server
Executable file
Binary file not shown.
118
network/tcpquiclab/tcp_multi_server.c
Normal file
118
network/tcpquiclab/tcp_multi_server.c
Normal file
@@ -0,0 +1,118 @@
|
|||||||
|
#include <stdio.h>
|
||||||
|
#include <stdlib.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
#include <arpa/inet.h>
|
||||||
|
#include <sys/socket.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
#include <time.h>
|
||||||
|
|
||||||
|
#define PORT 8081
|
||||||
|
#define BUFFER_SIZE 4096
|
||||||
|
#define EXPECTED_CONNECTIONS 5
|
||||||
|
#define TOTAL_TARGET_MB 100
|
||||||
|
|
||||||
|
long long global_bytes_received = 0;
|
||||||
|
int connections_handled = 0;
|
||||||
|
pthread_mutex_t lock = PTHREAD_MUTEX_INITIALIZER;
|
||||||
|
struct timespec start_time, end_time;
|
||||||
|
int first_connect = 1;
|
||||||
|
|
||||||
|
void *handle_client(void *socket_desc) {
|
||||||
|
int sock = *(int*)socket_desc;
|
||||||
|
free(socket_desc);
|
||||||
|
char buffer[BUFFER_SIZE];
|
||||||
|
int valread;
|
||||||
|
long long thread_bytes = 0;
|
||||||
|
|
||||||
|
while ((valread = read(sock, buffer, BUFFER_SIZE)) > 0) {
|
||||||
|
thread_bytes += valread;
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutex_lock(&lock);
|
||||||
|
global_bytes_received += thread_bytes;
|
||||||
|
connections_handled++;
|
||||||
|
pthread_mutex_unlock(&lock);
|
||||||
|
|
||||||
|
close(sock);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
int main() {
|
||||||
|
int server_fd, new_socket;
|
||||||
|
struct sockaddr_in address;
|
||||||
|
int opt = 1;
|
||||||
|
int addrlen = sizeof(address);
|
||||||
|
|
||||||
|
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, 5) < 0) {
|
||||||
|
perror("listen");
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
printf("TCP Multi-Connection Server listening on port %d...\n", PORT);
|
||||||
|
printf("Waiting for %d connections to transfer total %d MB...\n", EXPECTED_CONNECTIONS, TOTAL_TARGET_MB);
|
||||||
|
|
||||||
|
pthread_t threads[EXPECTED_CONNECTIONS];
|
||||||
|
int t_count = 0;
|
||||||
|
|
||||||
|
while (t_count < EXPECTED_CONNECTIONS) {
|
||||||
|
if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
|
||||||
|
perror("accept");
|
||||||
|
exit(EXIT_FAILURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (first_connect) {
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &start_time);
|
||||||
|
first_connect = 0;
|
||||||
|
printf("First connection received. Timer started.\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
int *new_sock = malloc(1);
|
||||||
|
*new_sock = new_socket;
|
||||||
|
|
||||||
|
if (pthread_create(&threads[t_count], NULL, handle_client, (void*)new_sock) < 0) {
|
||||||
|
perror("could not create thread");
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
t_count++;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for all threads to finish
|
||||||
|
for (int i = 0; i < EXPECTED_CONNECTIONS; i++) {
|
||||||
|
pthread_join(threads[i], NULL);
|
||||||
|
}
|
||||||
|
|
||||||
|
clock_gettime(CLOCK_MONOTONIC, &end_time);
|
||||||
|
|
||||||
|
double time_taken = (end_time.tv_sec - start_time.tv_sec) + (end_time.tv_nsec - start_time.tv_nsec) / 1e9;
|
||||||
|
double mb = global_bytes_received / (1024.0 * 1024.0);
|
||||||
|
double throughput = mb / time_taken;
|
||||||
|
|
||||||
|
printf("\nTest Finished:\n");
|
||||||
|
printf("Total Connections: %d\n", connections_handled);
|
||||||
|
printf("Total Data Received: %.2f MB\n", mb);
|
||||||
|
printf("Time Taken: %.2f seconds\n", time_taken);
|
||||||
|
printf("Total Throughput: %.2f MB/s\n", throughput);
|
||||||
|
|
||||||
|
close(server_fd);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user