Loading test/CMakeLists.txt +7 −0 Original line number Diff line number Diff line Loading @@ -171,6 +171,13 @@ else() target_link_libraries(benchmark_tls_tcp_bulk netplus-static) endif() add_executable(benchmark_tls_quic_bulk benchmark_tls_quic_bulk.cpp) if(WIN32) target_link_libraries(benchmark_tls_quic_bulk netplus-static ws2_32) else() target_link_libraries(benchmark_tls_quic_bulk netplus-static) endif() add_executable(quic_rfc9000_test quic_rfc9000_test.cpp) if(WIN32) target_link_libraries(quic_rfc9000_test netplus-static ws2_32) Loading test/benchmark_tls_quic_bulk.cpp 0 → 100644 +326 −0 Original line number Diff line number Diff line // Side-by-side one-way bulk transfer throughput comparison: TCP+TLS 1.3 vs QUIC. // // Companion to benchmark_tls_tcp_bulk.cpp and benchmark_quic_bulk.cpp, which // each benchmark one transport in isolation. This runs the identical // total_bytes payload over both stacks back-to-back on loopback and prints // both throughput numbers next to each other in one run, so a change to // either transport's hot path can be judged directly against the other // instead of comparing numbers from two separate invocations (different // process, different point in time, different machine load). #include <iostream> #include <iomanip> #include <string> #include <vector> #include <thread> #include <atomic> #include <chrono> #include <cstdlib> #include <cstring> #include "connection.h" #include "eventapi.h" #include "socket.h" #include "exception.h" #include "https_certs.h" #include "https_ca_cert.h" using namespace netplus; using hrc = std::chrono::high_resolution_clock; static double elapsed_s(hrc::time_point start, hrc::time_point end) { return std::chrono::duration<double>(end - start).count(); } struct BenchResult { std::string label; size_t total_bytes = 0; double send_elapsed_s = 0.0; double total_elapsed_s = 0.0; uint64_t recv_bytes = 0; bool complete = false; }; static void printResult(const BenchResult& r) { double send_mb_s = (double(r.total_bytes) / (1024.0 * 1024.0)) / r.send_elapsed_s; double recv_mb_s = (double(r.recv_bytes) / (1024.0 * 1024.0)) / r.total_elapsed_s; std::cout << std::fixed << std::setprecision(2); std::cout << " Sent: " << r.total_bytes / (1024 * 1024) << " MB in " << r.send_elapsed_s * 1000.0 << " ms => " << send_mb_s << " MB/s (sender-side)" << std::endl; std::cout << " Received: " << r.recv_bytes / (1024 * 1024) << " MB in " << r.total_elapsed_s * 1000.0 << " ms => " << recv_mb_s << " MB/s (receiver-confirmed)" << (r.complete ? "" : " [TIMED OUT waiting for completion]") << std::endl; } // ============================================================================ // TCP + TLS 1.3 // ============================================================================ static std::atomic<bool> g_tcp_server_ready(false); static std::atomic<uint64_t> g_tcp_sink_bytes_received{0}; class TcpTlsSinkServer : public event { public: TcpTlsSinkServer(std::vector<netplus::socket*> socks, int timeout = 500) : event(socks, timeout) {} // Pure sink: count and discard whatever arrived, never write back. void RequestEvent(con& curcon, const int, ULONG_PTR) override { if (!curcon.RecvData.empty()) { g_tcp_sink_bytes_received.fetch_add(curcon.RecvData.size(), std::memory_order_relaxed); curcon.RecvData.clear(); } } void ResponseEvent(con&, const int, ULONG_PTR) override {} void ConnectEvent(con&, const int, ULONG_PTR) override {} void DisconnectEvent(con&, const int, ULONG_PTR) override {} void CreateConnection(std::shared_ptr<con>& res) override { res = std::make_shared<con>(this); } }; static void run_tcp_tls_sink_server(std::map<std::string, ssl::CertificateBundle>& certs, int port) { try { ssl serverSock(certs, "127.0.0.1", port, 64, -1); TcpTlsSinkServer srv({&serverSock}); g_tcp_server_ready.store(true); srv.runEventloop(); } catch (std::exception& e) { std::cerr << "[TcpTlsSinkServer] Error: " << e.what() << std::endl; g_tcp_server_ready.store(true); } } static BenchResult benchmark_tcp_tls(std::map<std::string, ssl::CertificateBundle>& certs, const std::string& host, int port, size_t total_bytes, size_t chunk_size) { ssl client(certs); client.getTls().trust_policy.verifyPeer = false; // test-only self-signed cert client.connect(host, port); std::vector<uint8_t> chunk(chunk_size, 0xAB); g_tcp_sink_bytes_received.store(0, std::memory_order_relaxed); BenchResult r; r.label = "TCP+TLS1.3"; r.total_bytes = total_bytes; auto t0 = hrc::now(); size_t sent_total = 0; while (sent_total < total_bytes) { size_t remaining = total_bytes - sent_total; size_t this_chunk = std::min(remaining, chunk_size); buffer snd(reinterpret_cast<const char*>(chunk.data()), this_chunk); size_t sent; try { sent = client.sendData(snd, 0); } catch (NetException& e) { if (e.getErrorType() == NetException::Note) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); continue; } throw; } if (sent == 0) throw std::runtime_error("benchmark_tcp_tls: sendData stalled"); sent_total += sent; } auto t1 = hrc::now(); // Wait for the server's event-loop thread to actually finish draining // (it processes asynchronously), bounded so a real failure doesn't hang. auto deadline = hrc::now() + std::chrono::seconds(10); uint64_t last_seen = 0; while (hrc::now() < deadline) { uint64_t now_seen = g_tcp_sink_bytes_received.load(std::memory_order_relaxed); if (now_seen >= total_bytes) break; if (now_seen != last_seen) { last_seen = now_seen; deadline = hrc::now() + std::chrono::seconds(2); // still making progress } std::this_thread::sleep_for(std::chrono::milliseconds(2)); } auto t2 = hrc::now(); r.send_elapsed_s = elapsed_s(t0, t1); r.total_elapsed_s = elapsed_s(t0, t2); r.recv_bytes = g_tcp_sink_bytes_received.load(std::memory_order_relaxed); r.complete = r.recv_bytes >= total_bytes; client.close(); return r; } // ============================================================================ // QUIC // ============================================================================ static std::atomic<bool> g_quic_server_ready(false); static std::atomic<uint64_t> g_quic_sink_bytes_received{0}; static std::atomic<bool> g_quic_sink_fin_seen(false); class QuicSinkServer : public event { public: QuicSinkServer(std::vector<netplus::socket*> socks, int timeout = 500) : event(socks, timeout) {} void RequestEvent(con&, const int, ULONG_PTR) override {} void ResponseEvent(con&, const int, ULONG_PTR) override {} void ConnectEvent(con&, const int, ULONG_PTR) override {} void DisconnectEvent(con&, const int, ULONG_PTR) override {} void CreateConnection(std::shared_ptr<con>& res) override { res = std::make_shared<con>(this); } }; static void run_quic_sink_server(std::map<std::string, netplus::ssl::CertificateBundle>& certs, int port) { try { quic serverSock(certs, "127.0.0.1", port, 64, -1); // Without this, the default behavior buffers the ENTIRE stream in // Stream::recv_buffer and only fires once, at FIN -- not // representative of a real large-transfer server (see // benchmark_quic_bulk.cpp's own comment on this). serverSock.setIncrementalStreamDispatch(true); serverSock.setStreamCallback([](netplus::socket*, uint64_t, const std::vector<uint8_t>& data, bool fin) { g_quic_sink_bytes_received.fetch_add(data.size(), std::memory_order_relaxed); if (fin) g_quic_sink_fin_seen.store(true, std::memory_order_relaxed); }); QuicSinkServer srv({&serverSock}); g_quic_server_ready.store(true); srv.runEventloop(); } catch (std::exception& e) { std::cerr << "[QuicSinkServer] Error: " << e.what() << std::endl; g_quic_server_ready.store(true); } } static BenchResult benchmark_quic(std::map<std::string, netplus::ssl::CertificateBundle>& certs, const std::string& host, int port, size_t total_bytes, size_t chunk_size) { quic client; client.setTrustPolicy(netplus::TlsTrustPolicy{false}); client.connect(host, port); uint64_t stream_id = client.openStream(true); std::vector<uint8_t> chunk(chunk_size, 0xAB); g_quic_sink_bytes_received.store(0, std::memory_order_relaxed); g_quic_sink_fin_seen.store(false, std::memory_order_relaxed); BenchResult r; r.label = "QUIC"; r.total_bytes = total_bytes; auto t0 = hrc::now(); size_t sent_total = 0; while (sent_total < total_bytes) { size_t remaining = total_bytes - sent_total; size_t this_chunk = std::min(remaining, chunk_size); bool fin = (this_chunk == remaining); size_t sent = client.sendStreamData(stream_id, chunk.data(), this_chunk, fin); if (sent == 0) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); continue; } sent_total += sent; } auto t1 = hrc::now(); auto deadline = hrc::now() + std::chrono::seconds(5); while (!g_quic_sink_fin_seen.load(std::memory_order_relaxed) && hrc::now() < deadline) { std::this_thread::sleep_for(std::chrono::milliseconds(2)); } auto t2 = hrc::now(); r.send_elapsed_s = elapsed_s(t0, t1); r.total_elapsed_s = elapsed_s(t0, t2); r.recv_bytes = g_quic_sink_bytes_received.load(std::memory_order_relaxed); r.complete = g_quic_sink_fin_seen.load(); client.close(); return r; } // ============================================================================ // main // ============================================================================ int main(int argc, char** argv) { try { x509cert cert; if (!cert.loadFromBuffer(test_cert_der)) { std::cerr << "Failed to load certificate" << std::endl; return 1; } std::map<std::string, netplus::ssl::CertificateBundle> certs; netplus::ssl::CertificateBundle bundle; bundle.cert = cert; bundle.privateKeyDer = std::vector<uint8_t>(test_key_der.begin(), test_key_der.end()); bundle.rsa_key = netplus::rsa(bundle.privateKeyDer); bundle.chain.push_back(std::vector<uint8_t>(MKCERT_ROOT_CA_DER, MKCERT_ROOT_CA_DER + MKCERT_ROOT_CA_DER_LEN)); certs["localhost"] = bundle; certs["127.0.0.1"] = bundle; const int tcp_port = 19547; const int quic_port = 9446; std::thread tcp_server_thread(run_tcp_tls_sink_server, std::ref(certs), tcp_port); tcp_server_thread.detach(); std::thread quic_server_thread(run_quic_sink_server, std::ref(certs), quic_port); quic_server_thread.detach(); while (!g_tcp_server_ready.load() || !g_quic_server_ready.load()) std::this_thread::sleep_for(std::chrono::milliseconds(10)); std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Default: 256 MB in 16 KB app writes for TCP+TLS (the TLS // max-plaintext-per-record size, one write == one TLS record) and // 32 KB for QUIC (its own default, matching benchmark_quic_bulk.cpp). // Override via argv, e.g. `benchmark_tls_quic_bulk 64 16 32`. size_t total_mb = argc > 1 ? std::stoul(argv[1]) : 256; size_t tcp_chunk_kb = argc > 2 ? std::stoul(argv[2]) : 16; size_t quic_chunk_kb = argc > 3 ? std::stoul(argv[3]) : 32; std::cout << "TCP+TLS1.3 vs QUIC one-way bulk transfer comparison (loopback, 127.0.0.1)" << std::endl; std::cout << total_mb << " MB total, " << tcp_chunk_kb << " KB TCP+TLS chunks / " << quic_chunk_kb << " KB QUIC chunks" << std::endl << std::endl; std::cout << "--- TCP+TLS1.3 ---" << std::endl; BenchResult tcp_result = benchmark_tcp_tls(certs, "127.0.0.1", tcp_port, total_mb * 1024 * 1024, tcp_chunk_kb * 1024); printResult(tcp_result); std::cout << std::endl << "--- QUIC ---" << std::endl; BenchResult quic_result = benchmark_quic(certs, "127.0.0.1", quic_port, total_mb * 1024 * 1024, quic_chunk_kb * 1024); printResult(quic_result); double tcp_mb_s = (double(tcp_result.recv_bytes) / (1024.0 * 1024.0)) / tcp_result.total_elapsed_s; double quic_mb_s = (double(quic_result.recv_bytes) / (1024.0 * 1024.0)) / quic_result.total_elapsed_s; std::cout << std::endl << "--- Comparison (receiver-confirmed MB/s) ---" << std::endl; std::cout << std::fixed << std::setprecision(2); std::cout << " TCP+TLS1.3: " << tcp_mb_s << " MB/s" << std::endl; std::cout << " QUIC: " << quic_mb_s << " MB/s" << std::endl; std::cout << " Ratio (TCP+TLS1.3 / QUIC): " << (tcp_mb_s / quic_mb_s) << "x" << std::endl; return 0; } catch (netplus::NetException& e) { std::cerr << "NetException: " << e.what() << std::endl; return 1; } catch (std::exception& e) { std::cerr << "Exception: " << e.what() << std::endl; return 1; } } Loading
test/CMakeLists.txt +7 −0 Original line number Diff line number Diff line Loading @@ -171,6 +171,13 @@ else() target_link_libraries(benchmark_tls_tcp_bulk netplus-static) endif() add_executable(benchmark_tls_quic_bulk benchmark_tls_quic_bulk.cpp) if(WIN32) target_link_libraries(benchmark_tls_quic_bulk netplus-static ws2_32) else() target_link_libraries(benchmark_tls_quic_bulk netplus-static) endif() add_executable(quic_rfc9000_test quic_rfc9000_test.cpp) if(WIN32) target_link_libraries(quic_rfc9000_test netplus-static ws2_32) Loading
test/benchmark_tls_quic_bulk.cpp 0 → 100644 +326 −0 Original line number Diff line number Diff line // Side-by-side one-way bulk transfer throughput comparison: TCP+TLS 1.3 vs QUIC. // // Companion to benchmark_tls_tcp_bulk.cpp and benchmark_quic_bulk.cpp, which // each benchmark one transport in isolation. This runs the identical // total_bytes payload over both stacks back-to-back on loopback and prints // both throughput numbers next to each other in one run, so a change to // either transport's hot path can be judged directly against the other // instead of comparing numbers from two separate invocations (different // process, different point in time, different machine load). #include <iostream> #include <iomanip> #include <string> #include <vector> #include <thread> #include <atomic> #include <chrono> #include <cstdlib> #include <cstring> #include "connection.h" #include "eventapi.h" #include "socket.h" #include "exception.h" #include "https_certs.h" #include "https_ca_cert.h" using namespace netplus; using hrc = std::chrono::high_resolution_clock; static double elapsed_s(hrc::time_point start, hrc::time_point end) { return std::chrono::duration<double>(end - start).count(); } struct BenchResult { std::string label; size_t total_bytes = 0; double send_elapsed_s = 0.0; double total_elapsed_s = 0.0; uint64_t recv_bytes = 0; bool complete = false; }; static void printResult(const BenchResult& r) { double send_mb_s = (double(r.total_bytes) / (1024.0 * 1024.0)) / r.send_elapsed_s; double recv_mb_s = (double(r.recv_bytes) / (1024.0 * 1024.0)) / r.total_elapsed_s; std::cout << std::fixed << std::setprecision(2); std::cout << " Sent: " << r.total_bytes / (1024 * 1024) << " MB in " << r.send_elapsed_s * 1000.0 << " ms => " << send_mb_s << " MB/s (sender-side)" << std::endl; std::cout << " Received: " << r.recv_bytes / (1024 * 1024) << " MB in " << r.total_elapsed_s * 1000.0 << " ms => " << recv_mb_s << " MB/s (receiver-confirmed)" << (r.complete ? "" : " [TIMED OUT waiting for completion]") << std::endl; } // ============================================================================ // TCP + TLS 1.3 // ============================================================================ static std::atomic<bool> g_tcp_server_ready(false); static std::atomic<uint64_t> g_tcp_sink_bytes_received{0}; class TcpTlsSinkServer : public event { public: TcpTlsSinkServer(std::vector<netplus::socket*> socks, int timeout = 500) : event(socks, timeout) {} // Pure sink: count and discard whatever arrived, never write back. void RequestEvent(con& curcon, const int, ULONG_PTR) override { if (!curcon.RecvData.empty()) { g_tcp_sink_bytes_received.fetch_add(curcon.RecvData.size(), std::memory_order_relaxed); curcon.RecvData.clear(); } } void ResponseEvent(con&, const int, ULONG_PTR) override {} void ConnectEvent(con&, const int, ULONG_PTR) override {} void DisconnectEvent(con&, const int, ULONG_PTR) override {} void CreateConnection(std::shared_ptr<con>& res) override { res = std::make_shared<con>(this); } }; static void run_tcp_tls_sink_server(std::map<std::string, ssl::CertificateBundle>& certs, int port) { try { ssl serverSock(certs, "127.0.0.1", port, 64, -1); TcpTlsSinkServer srv({&serverSock}); g_tcp_server_ready.store(true); srv.runEventloop(); } catch (std::exception& e) { std::cerr << "[TcpTlsSinkServer] Error: " << e.what() << std::endl; g_tcp_server_ready.store(true); } } static BenchResult benchmark_tcp_tls(std::map<std::string, ssl::CertificateBundle>& certs, const std::string& host, int port, size_t total_bytes, size_t chunk_size) { ssl client(certs); client.getTls().trust_policy.verifyPeer = false; // test-only self-signed cert client.connect(host, port); std::vector<uint8_t> chunk(chunk_size, 0xAB); g_tcp_sink_bytes_received.store(0, std::memory_order_relaxed); BenchResult r; r.label = "TCP+TLS1.3"; r.total_bytes = total_bytes; auto t0 = hrc::now(); size_t sent_total = 0; while (sent_total < total_bytes) { size_t remaining = total_bytes - sent_total; size_t this_chunk = std::min(remaining, chunk_size); buffer snd(reinterpret_cast<const char*>(chunk.data()), this_chunk); size_t sent; try { sent = client.sendData(snd, 0); } catch (NetException& e) { if (e.getErrorType() == NetException::Note) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); continue; } throw; } if (sent == 0) throw std::runtime_error("benchmark_tcp_tls: sendData stalled"); sent_total += sent; } auto t1 = hrc::now(); // Wait for the server's event-loop thread to actually finish draining // (it processes asynchronously), bounded so a real failure doesn't hang. auto deadline = hrc::now() + std::chrono::seconds(10); uint64_t last_seen = 0; while (hrc::now() < deadline) { uint64_t now_seen = g_tcp_sink_bytes_received.load(std::memory_order_relaxed); if (now_seen >= total_bytes) break; if (now_seen != last_seen) { last_seen = now_seen; deadline = hrc::now() + std::chrono::seconds(2); // still making progress } std::this_thread::sleep_for(std::chrono::milliseconds(2)); } auto t2 = hrc::now(); r.send_elapsed_s = elapsed_s(t0, t1); r.total_elapsed_s = elapsed_s(t0, t2); r.recv_bytes = g_tcp_sink_bytes_received.load(std::memory_order_relaxed); r.complete = r.recv_bytes >= total_bytes; client.close(); return r; } // ============================================================================ // QUIC // ============================================================================ static std::atomic<bool> g_quic_server_ready(false); static std::atomic<uint64_t> g_quic_sink_bytes_received{0}; static std::atomic<bool> g_quic_sink_fin_seen(false); class QuicSinkServer : public event { public: QuicSinkServer(std::vector<netplus::socket*> socks, int timeout = 500) : event(socks, timeout) {} void RequestEvent(con&, const int, ULONG_PTR) override {} void ResponseEvent(con&, const int, ULONG_PTR) override {} void ConnectEvent(con&, const int, ULONG_PTR) override {} void DisconnectEvent(con&, const int, ULONG_PTR) override {} void CreateConnection(std::shared_ptr<con>& res) override { res = std::make_shared<con>(this); } }; static void run_quic_sink_server(std::map<std::string, netplus::ssl::CertificateBundle>& certs, int port) { try { quic serverSock(certs, "127.0.0.1", port, 64, -1); // Without this, the default behavior buffers the ENTIRE stream in // Stream::recv_buffer and only fires once, at FIN -- not // representative of a real large-transfer server (see // benchmark_quic_bulk.cpp's own comment on this). serverSock.setIncrementalStreamDispatch(true); serverSock.setStreamCallback([](netplus::socket*, uint64_t, const std::vector<uint8_t>& data, bool fin) { g_quic_sink_bytes_received.fetch_add(data.size(), std::memory_order_relaxed); if (fin) g_quic_sink_fin_seen.store(true, std::memory_order_relaxed); }); QuicSinkServer srv({&serverSock}); g_quic_server_ready.store(true); srv.runEventloop(); } catch (std::exception& e) { std::cerr << "[QuicSinkServer] Error: " << e.what() << std::endl; g_quic_server_ready.store(true); } } static BenchResult benchmark_quic(std::map<std::string, netplus::ssl::CertificateBundle>& certs, const std::string& host, int port, size_t total_bytes, size_t chunk_size) { quic client; client.setTrustPolicy(netplus::TlsTrustPolicy{false}); client.connect(host, port); uint64_t stream_id = client.openStream(true); std::vector<uint8_t> chunk(chunk_size, 0xAB); g_quic_sink_bytes_received.store(0, std::memory_order_relaxed); g_quic_sink_fin_seen.store(false, std::memory_order_relaxed); BenchResult r; r.label = "QUIC"; r.total_bytes = total_bytes; auto t0 = hrc::now(); size_t sent_total = 0; while (sent_total < total_bytes) { size_t remaining = total_bytes - sent_total; size_t this_chunk = std::min(remaining, chunk_size); bool fin = (this_chunk == remaining); size_t sent = client.sendStreamData(stream_id, chunk.data(), this_chunk, fin); if (sent == 0) { std::this_thread::sleep_for(std::chrono::milliseconds(1)); continue; } sent_total += sent; } auto t1 = hrc::now(); auto deadline = hrc::now() + std::chrono::seconds(5); while (!g_quic_sink_fin_seen.load(std::memory_order_relaxed) && hrc::now() < deadline) { std::this_thread::sleep_for(std::chrono::milliseconds(2)); } auto t2 = hrc::now(); r.send_elapsed_s = elapsed_s(t0, t1); r.total_elapsed_s = elapsed_s(t0, t2); r.recv_bytes = g_quic_sink_bytes_received.load(std::memory_order_relaxed); r.complete = g_quic_sink_fin_seen.load(); client.close(); return r; } // ============================================================================ // main // ============================================================================ int main(int argc, char** argv) { try { x509cert cert; if (!cert.loadFromBuffer(test_cert_der)) { std::cerr << "Failed to load certificate" << std::endl; return 1; } std::map<std::string, netplus::ssl::CertificateBundle> certs; netplus::ssl::CertificateBundle bundle; bundle.cert = cert; bundle.privateKeyDer = std::vector<uint8_t>(test_key_der.begin(), test_key_der.end()); bundle.rsa_key = netplus::rsa(bundle.privateKeyDer); bundle.chain.push_back(std::vector<uint8_t>(MKCERT_ROOT_CA_DER, MKCERT_ROOT_CA_DER + MKCERT_ROOT_CA_DER_LEN)); certs["localhost"] = bundle; certs["127.0.0.1"] = bundle; const int tcp_port = 19547; const int quic_port = 9446; std::thread tcp_server_thread(run_tcp_tls_sink_server, std::ref(certs), tcp_port); tcp_server_thread.detach(); std::thread quic_server_thread(run_quic_sink_server, std::ref(certs), quic_port); quic_server_thread.detach(); while (!g_tcp_server_ready.load() || !g_quic_server_ready.load()) std::this_thread::sleep_for(std::chrono::milliseconds(10)); std::this_thread::sleep_for(std::chrono::milliseconds(100)); // Default: 256 MB in 16 KB app writes for TCP+TLS (the TLS // max-plaintext-per-record size, one write == one TLS record) and // 32 KB for QUIC (its own default, matching benchmark_quic_bulk.cpp). // Override via argv, e.g. `benchmark_tls_quic_bulk 64 16 32`. size_t total_mb = argc > 1 ? std::stoul(argv[1]) : 256; size_t tcp_chunk_kb = argc > 2 ? std::stoul(argv[2]) : 16; size_t quic_chunk_kb = argc > 3 ? std::stoul(argv[3]) : 32; std::cout << "TCP+TLS1.3 vs QUIC one-way bulk transfer comparison (loopback, 127.0.0.1)" << std::endl; std::cout << total_mb << " MB total, " << tcp_chunk_kb << " KB TCP+TLS chunks / " << quic_chunk_kb << " KB QUIC chunks" << std::endl << std::endl; std::cout << "--- TCP+TLS1.3 ---" << std::endl; BenchResult tcp_result = benchmark_tcp_tls(certs, "127.0.0.1", tcp_port, total_mb * 1024 * 1024, tcp_chunk_kb * 1024); printResult(tcp_result); std::cout << std::endl << "--- QUIC ---" << std::endl; BenchResult quic_result = benchmark_quic(certs, "127.0.0.1", quic_port, total_mb * 1024 * 1024, quic_chunk_kb * 1024); printResult(quic_result); double tcp_mb_s = (double(tcp_result.recv_bytes) / (1024.0 * 1024.0)) / tcp_result.total_elapsed_s; double quic_mb_s = (double(quic_result.recv_bytes) / (1024.0 * 1024.0)) / quic_result.total_elapsed_s; std::cout << std::endl << "--- Comparison (receiver-confirmed MB/s) ---" << std::endl; std::cout << std::fixed << std::setprecision(2); std::cout << " TCP+TLS1.3: " << tcp_mb_s << " MB/s" << std::endl; std::cout << " QUIC: " << quic_mb_s << " MB/s" << std::endl; std::cout << " Ratio (TCP+TLS1.3 / QUIC): " << (tcp_mb_s / quic_mb_s) << "x" << std::endl; return 0; } catch (netplus::NetException& e) { std::cerr << "NetException: " << e.what() << std::endl; return 1; } catch (std::exception& e) { std::cerr << "Exception: " << e.what() << std::endl; return 1; } }