diff --git a/CMakeLists.txt b/CMakeLists.txt index c2f8fcc97661f7c1681e9d9ddfdffb7e99bd70d7..838c40d7274133d762489896a34b7ae5e3c9a02b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -6,9 +6,10 @@ set(CXX_STANDARD_REQUIRED ON) include(CPM.cmake) CPMAddPackage("gh:OlivierLDff/asio.cmake@1.1.3") +CPMAddPackage("gh:fmtlib/fmt#9.1.0") add_executable(sender sender.cpp) -target_link_libraries(sender PRIVATE asio::asio) +target_link_libraries(sender PRIVATE asio::asio fmt::fmt) add_executable(receiver receiver.cpp) target_link_libraries(receiver PRIVATE asio::asio) diff --git a/packet.hpp b/packet.hpp new file mode 100644 index 0000000000000000000000000000000000000000..65af808fc3a458b031e5851ee5e3fe711113a201 --- /dev/null +++ b/packet.hpp @@ -0,0 +1,14 @@ +#pragma once + +#include <array> +#include <cstddef> +#include <cstdint> + +constexpr std::size_t MAX_PACKET_SIZE = 65507; + +struct Packet { + std::uint64_t id; + std::array<std::uint8_t, MAX_PACKET_SIZE - 8> payload; +}; + +static_assert(sizeof(Packet) >= MAX_PACKET_SIZE); diff --git a/receiver.cpp b/receiver.cpp index 68649b480e391f6656e71c259fc50ae57d3967c2..cb29c6a1f5e20a1a049a3006a4ceff5a3d200b5a 100644 --- a/receiver.cpp +++ b/receiver.cpp @@ -1,3 +1,4 @@ +#include "packet.hpp" #include <asio.hpp> #include <asio/buffer.hpp> #include <cstddef> @@ -8,6 +9,11 @@ using udp = asio::ip::udp; using clk = std::chrono::steady_clock; +struct ReceivedContext { + std::uint64_t highest_id_received; + clk::time_point first_receive; +}; + class Receiver { public: Receiver(std::uint16_t port) @@ -20,34 +26,59 @@ public: private: asio::io_context context; udp::socket socket; - std::array<std::byte, 1024> data; - std::optional<clk::time_point> first_receive; - std::uint64_t bytes_received = 0; + Packet data; + std::optional<ReceivedContext> receive_data; + std::uint64_t total_bytes_received = 0; + std::uint64_t packets_received = 0; + std::uint64_t out_of_order_delivered_packets = 0; clk::time_point last_print; void receive() { - socket.async_receive(asio::buffer(data), [this](std::error_code ec, - std::size_t bytes_recvd) { - if (ec) { - std::cerr << ec.category().name() << ": " << ec.message() << std::endl; - } else { - bytes_received += bytes_recvd; - if (!first_receive.has_value()) { - first_receive = last_print = clk::now(); - } else { - const auto dt = clk::now() - *first_receive; - const auto seconds = - std::chrono::duration_cast<std::chrono::duration<double>>(dt) - .count(); - - if (clk::now() - last_print > std::chrono::seconds(2)) { - std::cout << bytes_received * 8 / seconds / 1000.0 / 1000.0 << " Mbit/s" << std::endl; - last_print = clk::now(); + socket.async_receive( + asio::buffer(&data, MAX_PACKET_SIZE), + [this](std::error_code ec, std::size_t bytes_received) { + if (ec) { + std::cerr << ec.category().name() << ": " << ec.message() + << std::endl; + } else { + if (!receive_data.has_value()) { + // ignore first packet + receive_data.emplace(ReceivedContext{ + .highest_id_received = data.id, + .first_receive = clk::now(), + }); + } else { + packets_received += 1; + total_bytes_received += bytes_received; + + const auto dt = clk::now() - receive_data->first_receive; + const auto seconds = + std::chrono::duration_cast<std::chrono::duration<double>>(dt) + .count(); + // std::cout << "ID: " << data.id << " length: " << bytes_received + // << std::endl; + // + if (data.id > receive_data->highest_id_received) { + if (data.id != receive_data->highest_id_received + 1) { + out_of_order_delivered_packets += 1; + } + receive_data->highest_id_received = data.id; + } + + if (clk::now() - last_print > std::chrono::seconds(2)) { + std::cout << total_bytes_received * 8 / seconds / 1000.0 / + 1000.0 + << " Mbit/s" + << ", " << (data.id - packets_received) + << " packets lost" + << ", " << out_of_order_delivered_packets + << " packets delivered out-of-order" << std::endl; + last_print = clk::now(); + } + } + this->receive(); } - } - this->receive(); - } - }); + }); } }; diff --git a/sender.cpp b/sender.cpp index 49c082cf7989b97de4083a76fd3692253e164129..c891c29703055575b995b2db03c06f300a159f3e 100644 --- a/sender.cpp +++ b/sender.cpp @@ -1,9 +1,13 @@ +#include "packet.hpp" #include <asio.hpp> #include <asio/buffer.hpp> #include <chrono> #include <cstddef> #include <cstdint> #include <cstdio> +#include <fmt/format.h> +#include <fmt/os.h> +#include <fstream> #include <iostream> using udp = asio::ip::udp; @@ -16,29 +20,47 @@ int main(int argc, char *argv[]) { udp::endpoint endpoint = *resolver.resolve(udp::v4(), argv[1], argv[2]).begin(); udp::socket s(context, udp::endpoint(udp::v4(), 0)); - // udp::socket s(context, endpoint); - int packet_size = argc >= 4 ? std::stoi(argv[3]) : 1024; + int packet_size = argc >= 4 ? std::stoi(argv[3]) : MAX_PACKET_SIZE; double time_between_packets = argc >= 5 ? std::stod(argv[4]) : 100; - std::cout << "packet size: " << packet_size << " byte" << std::endl; + std::cout << "packet size: " << packet_size << " bytes" << std::endl; std::cout << "send interval: " << time_between_packets << " ms" << std::endl; + if (packet_size < 8) { + std::cerr << "packet size must be at least 8 bytes and at most " + << MAX_PACKET_SIZE << " bytes" << std::endl; + return -1; + } + + std::uint64_t bytes_sent = 0; + auto send_duration_file = fmt::output_file(fmt::format("send_durations_{}_{}.csv", packet_size, time_between_packets)); + + std::cout << "theoretical bandwidth: " + << packet_size * 1000 * 8 / time_between_packets / 1000.0 / 1000.0 + << " Mbit/s" << std::endl; + const auto dt = std::chrono::duration_cast<clk::duration>( std::chrono::duration<double, std::milli>(time_between_packets)); - std::array<std::byte, 1024> data; - + std::uint64_t skipped = 0; + Packet packet{.id = 0}; auto last = clk::now(); while (true) { - auto now = clk::now(); + const auto now = clk::now(); if (now - last > dt) { - s.send_to(asio::buffer(data, packet_size), endpoint); - last += dt; - if (clk::now() - now > dt) { + s.send_to(asio::buffer(&packet, packet_size), endpoint); + packet.id += 1; + bytes_sent += packet_size; + const auto after_send = clk::now(); + + send_duration_file.print("{},{}\n", std::chrono::duration_cast<std::chrono::duration<double, std::milli>>(after_send - now).count(), bytes_sent); + send_duration_file.flush(); + + while (last + dt < clk::now()) { + skipped++; std::putchar('.'); - std::cout.flush(); last += dt; } }