| 7659 | #include <asio.hpp> |
| 7660 | |
| 7661 | class rpc_asio_server { |
| 7662 | |
| 7663 | asio::io_context context_; |
| 7664 | asio::ip::udp::socket socket_; |
| 7665 | std::thread context_thread_; |
| 7666 | |
| 7667 | /// @brief Buffers, one per concurrent request |
| 7668 | std::vector<rpc_buffer_t> buffers_; |
| 7669 | /// @brief Where did the packets come from |
| 7670 | std::vector<asio::ip::udp::endpoint> clients_; |
| 7671 | /// @brief Which buffers are available? |
| 7672 | std::vector<std::size_t> buffers_available_; |
| 7673 | /// @brief Flag to stop the server without corrupting the state |
| 7674 | std::atomic_bool should_stop_; |
| 7675 | // Use a work guard so the io_context doesn’t run out of work and exit. |
| 7676 | asio::executor_work_guard<asio::io_context::executor_type> work_guard_; |
| 7677 | |
| 7678 | std::size_t failed_receptions_ = 0; |
| 7679 | std::size_t failed_responses_ = 0; |
| 7680 | |
| 7681 | public: |
| 7682 | rpc_asio_server(std::string const &address, std::uint16_t port, std::size_t max_concurrency) |
| 7683 | : context_(), socket_(context_), buffers_(max_concurrency), clients_(max_concurrency), |
| 7684 | work_guard_(asio::make_work_guard(context_)) { |
| 7685 | // Use your helper function to create and bind the native socket. |
| 7686 | auto server = rpc_server_socket(port, address); |
| 7687 | // Now assign the native socket to the ASIO socket. |
| 7688 | socket_.assign(asio::ip::udp::v4(), server.socket_descriptor); |
| 7689 | } |
| 7690 | |
| 7691 | void stop() { should_stop_.store(true, std::memory_order_seq_cst); } |
| 7692 | void close() { |
| 7693 | socket_.cancel(); |
| 7694 | context_.stop(); |
| 7695 | if (context_thread_.joinable()) context_thread_.join(); |
| 7696 | } |
| 7697 | |
| 7698 | void operator()() { |
| 7699 | // For per-operation cancellations we could use the `asio::cancellation_signal`. |
| 7700 | // Let's issue a receive operation for each buffer, which will call a chain of |
| 7701 | // operations to process the packet and send a response, and repeat again. |
| 7702 | for (std::size_t job = 0; job < buffers_.size(); ++job) reuse_buffer(job); |
| 7703 | // Start listening for incoming packets. |
| 7704 | context_thread_ = std::thread([this] { context_.run(); }); |
| 7705 | } |
| 7706 | |
| 7707 | private: |
| 7708 | void reuse_buffer(std::size_t job) { |
| 7709 | auto finalize = [this, job](std::error_code error, std::size_t) { |
| 7710 | if (error) failed_responses_++; |
| 7711 | if (should_stop_.load(std::memory_order_seq_cst)) return; |
| 7712 | reuse_buffer(job); |
| 7713 | }; |
| 7714 | auto respond = [this, finalize, job](std::error_code error, std::size_t bytes) { |
| 7715 | if (error) { reuse_buffer(job); } |
| 7716 | else { socket_.async_send_to(asio::buffer(buffers_[job], bytes), clients_[job], finalize); } |
| 7717 | }; |
| 7718 | socket_.async_receive_from(asio::buffer(buffers_[job]), clients_[job], respond); |
nothing calls this directly
no outgoing calls
no test coverage detected