Skip to content

Instantly share code, notes, and snippets.

@shardator
Created December 11, 2024 12:51
Show Gist options
  • Save shardator/a66acd5262f94d0b58002c4990ac9eaa to your computer and use it in GitHub Desktop.
Save shardator/a66acd5262f94d0b58002c4990ac9eaa to your computer and use it in GitHub Desktop.
#include <boost/asio.hpp>
#include <iostream>
#include <string>
#include <stdexcept>
#include <unordered_map>
class MulticastServer {
public:
// Constructor throws on critical errors (e.g., invalid bind address or socket open fail).
explicit MulticastServer(const std::string &bind_address = "0.0.0.0", int failure_threshold = 3)
: io_context_(),
socket_(io_context_, boost::asio::ip::udp::v4()),
next_group_id_(1),
failure_threshold_(failure_threshold)
{
boost::system::error_code ec;
boost::asio::ip::udp::endpoint local_endpoint(
boost::asio::ip::address::from_string(bind_address, ec), 0);
if (ec) {
throw std::runtime_error("Invalid bind address: " + bind_address + " Error: " + ec.message());
}
socket_.open(local_endpoint.protocol(), ec);
if (ec) {
throw std::runtime_error("Failed to open socket: " + ec.message());
}
socket_.bind(local_endpoint, ec);
if (ec) {
throw std::runtime_error("Failed to bind socket: " + ec.message());
}
}
// Registers a multicast group and returns an integer group_id.
// Throws if address is invalid or not multicast.
int register_group(const std::string &group_address, unsigned short port) {
boost::system::error_code ec;
auto address = boost::asio::ip::address::from_string(group_address, ec);
if (ec) {
throw std::runtime_error("Invalid multicast address: " + group_address + ", error: " + ec.message());
}
if (!address.is_multicast()) {
throw std::runtime_error("Address " + group_address + " is not a multicast address.");
}
boost::asio::ip::udp::endpoint endpoint(address, port);
int group_id = next_group_id_++;
GroupInfo ginfo{endpoint, 0};
group_map_[group_id] = ginfo;
return group_id;
}
// Sends a raw bytes message to the previously registered multicast group (by ID).
// Returns true on success, false on a single send failure.
// If a group fails consecutively more than 'failure_threshold_' times, throws an exception.
bool send_to_group(int group_id, const std::string &message) {
auto it = group_map_.find(group_id);
if (it == group_map_.end()) {
throw std::out_of_range("Group ID " + std::to_string(group_id) + " not registered.");
}
auto &ginfo = it->second;
boost::system::error_code ec;
std::size_t bytes_sent = socket_.send_to(boost::asio::buffer(message), ginfo.endpoint, 0, ec);
if (ec) {
// Increment failure count
ginfo.consecutive_failures++;
// Check if we've reached the threshold
if (ginfo.consecutive_failures > failure_threshold_) {
throw std::runtime_error(
"Group " + std::to_string(group_id) + " exceeded failure threshold with error: " + ec.message());
}
// Return false for a single failure
return false;
}
// On success, reset failures to 0
ginfo.consecutive_failures = 0;
return true;
}
// If you need asynchronous operations, you could run the io_context here.
void run() {
io_context_.run();
}
private:
struct GroupInfo {
boost::asio::ip::udp::endpoint endpoint;
int consecutive_failures;
};
boost::asio::io_context io_context_;
boost::asio::ip::udp::socket socket_;
std::unordered_map<int, GroupInfo> group_map_;
int next_group_id_;
int failure_threshold_;
};
// Example usage:
//
// int main() {
// try {
// MulticastServer server;
// int group_id = server.register_group("239.255.0.1", 30001);
// // Try sending a message
// if (!server.send_to_group(group_id, "Hello Multicast Group 1!")) {
// std::cerr << "Send attempt failed once.\n";
// }
// } catch (const std::exception &ex) {
// std::cerr << "Error: " << ex.what() << "\n";
// }
// return 0;
// }
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment