boost::asio UDP broadcasting
Try the following code snippet to send a UDP broadcast, utilizing the ba::ip::address_v4::broadcast()
call to get an endpoint:
bs::error_code error;
ba::ip::udp::socket socket(_impl->_ioService);
socket.open(ba::ip::udp::v4(), error);
if (!error)
{
socket.set_option(ba::ip::udp::socket::reuse_address(true));
socket.set_option(ba::socket_base::broadcast(true));
ba::ip::udp::endpoint senderEndpoint(ba::ip::address_v4::broadcast(), port);
socket.send_to(data, senderEndpoint);
socket.close(error);
}
boost::asio UDP Broadcast Client Only Receives fast Packets
I had a long search going, because broadcast UDP can be finicky. Then I spotted your future<void>
. Not only would I not trust std::async
to do what you expect (it can do pretty much anything), but also, there's a potentially lethal race, and this is 99% certain your issue:
you launch the async task - it will start /some time in the future/
only then you add the
async_receive_from
operation. If the task had already started, the queue would have been empty, therun()
completes and the future is madeready
. Indeed, this is visible when you:ioService.run();
std::clog << "End of run " << std::boolalpha << ioService.stopped() << std::endl;
It was printing
End of run true
most of the time for me. I suggest using a thread:
ioThread = std::thread([this] {
ioService.run();
std::clog << "End of run " << std::boolalpha << ioService.stopped() << std::endl;
});
with corresponding join
:
~BroadcastClient() {
std::clog << "~BroadcastClient()" << std::endl;
ioThread.join();
}
To be complete, also handle exceptions: Should the exception thrown by boost::asio::io_service::run() be caught? or use
thread_pool(1)
which is nice because it also replaces yourio_service
.
Alternatively, use a work guard (
io_service::work
ormake_executor_guard
).
Now, I can't seem to make it miss packets when testing locally.
More Review
In general you want to know earlier when error conditions arise in your code, so report on
error
inhandle_read
, because such a condition leads to the async loop to terminate. See below for more fixedhandle_read
The
result
buffer is not thread safe and you access it from multiple threads¹. That invoked Undefined Behavior. Add synchronization, or use e.g. atomic exchanges.¹ to be sure that the
poll
happens on the service thread you'd have topost
the poll operation to the io_service. That's not possible because the service is privateYou use
buffer.size()
in handle_read but that's hard-coded (4096). You probably wantedbytes_transferred
result.append(std::begin(buffer), std::begin(buffer) + bytes_transferred);
Also avoids an unnecessary temporary. Also, now you don't have to reset the buffer to zeroes:
void BroadcastClient::handle_read(const boost::system::error_code& error, std::size_t bytes_transferred) {
if (!error) {
std::lock_guard lk(result_mx);
result.append(std::begin(buffer), std::begin(buffer) + bytes_transferred);
start_read();
} else {
std::clog << "handle_read: " << error.message() << std::endl;
}
}why is
socket
dynamically instantiated? In fact, you should initialize it in the constructor initializer list, or since C++11 from the NSMI:uint16_t port{ 8888 };
boost::asio::io_service ioService;
udp::socket socket { ioService, { {}, port } };There's duplication of the
async_receive_from
call. This calls for astart_read
or similar method. Also, consider using a lambda to reduce the code and not rely on old-fashionedboost::bind
:void BroadcastClient::start_read() {
socket.async_receive_from(
boost::asio::buffer(buffer), sender_endpoint,
[this](auto ec, size_t xfr) { handle_read(ec, xfr); });
}
Full Listing
Live On Coliru
#include <boost/asio.hpp>
#include <iostream>
#include <iomanip>
#include <thread>
#include <mutex>
using namespace std::chrono_literals;
class BroadcastClient {
using socket_base = boost::asio::socket_base;
using udp = boost::asio::ip::udp;
public:
BroadcastClient();
~BroadcastClient() {
std::clog << "~BroadcastClient()" << std::endl;
socket.cancel();
work.reset();
ioThread.join();
}
std::optional<std::string> poll();
protected:
void start_read();
void handle_read(const boost::system::error_code& error, std::size_t bytes_transferred);
private:
uint16_t port{ 8888 };
boost::asio::io_service ioService;
boost::asio::executor_work_guard<
boost::asio::io_service::executor_type> work { ioService.get_executor() };
udp::socket socket { ioService, { {}, port } };
std::thread ioThread;
std::string buffer = std::string(4096, '\0');
std::mutex result_mx;
std::string result;
udp::endpoint sender_endpoint;
};
BroadcastClient::BroadcastClient() {
socket.set_option(socket_base::broadcast(true));
socket.set_option(socket_base::reuse_address(true));
ioThread = std::thread([this] {
ioService.run();
std::clog << "Service thread, stopped? " << std::boolalpha << ioService.stopped() << std::endl;
});
start_read(); // actually okay now because of `work` guard
}
void BroadcastClient::start_read() {
socket.async_receive_from(
boost::asio::buffer(buffer), sender_endpoint,
[this](auto ec, size_t xfr) { handle_read(ec, xfr); });
}
void BroadcastClient::handle_read(const boost::system::error_code& error, std::size_t bytes_transferred) {
if (!error) {
std::lock_guard lk(result_mx);
result.append(std::begin(buffer), std::begin(buffer) + bytes_transferred);
start_read();
} else {
std::clog << "handle_read: " << error.message() << std::endl;
}
}
std::optional<std::string> BroadcastClient::poll() {
std::lock_guard lk(result_mx);
if (result.empty())
return std::nullopt;
else
return std::move(result);
}
constexpr auto now = std::chrono::steady_clock::now;
int main() {
BroadcastClient bcc;
for (auto start = now(); now() - start < 3s;) {
if (auto r = bcc.poll())
std::cout << std::quoted(r.value()) << std::endl;
std::this_thread::sleep_for(100ms);
}
} // BroadcastClient destructor safely cancels the work
Tested live with
g++ -std=c++17 -O2 -Wall -pedantic -pthread main.cpp
while sleep .05; do echo -n "hello world $RANDOM" | netcat -w 0 -u 127.0.0.1 8888 ; done&
./a.out
kill %1
Prints
"hello world 18422"
"hello world 3810"
"hello world 26191hello world 10419"
"hello world 23666hello world 18552"
"hello world 2076"
"hello world 19871hello world 8978"
"hello world 1836"
"hello world 11134hello world 16603"
"hello world 3748hello world 8089"
"hello world 27946"
"hello world 14834hello world 15274"
"hello world 26555hello world 6695"
"hello world 32419"
"hello world 26996hello world 26796"
"hello world 9882"
"hello world 680hello world 29358"
"hello world 9723hello world 31163"
"hello world 3646"
"hello world 10602hello world 22562"
"hello world 18394hello world 17229"
"hello world 20028"
"hello world 14444hello world 3890"
"hello world 16258"
"hello world 28555hello world 21184"
"hello world 31342hello world 30891"
"hello world 3088"
"hello world 1051hello world 5638"
"hello world 24308hello world 7748"
"hello world 18398"
~BroadcastClient()
handle_read: Operation canceled
Service thread, stopped? true
Old answer contents which may /still/ be of interest
Wait. I noticed this is not "regular" peer-to-peer UDP.
As far as I understand, multicast works courtesy of routers. They have to maintain complex tables of endpoints "subscribed" so they know where to forward the actual packets.
Many routers struggle with these, there are builtin pitfalls with the reliability, especially on WiFi etc. It would /not/ surprise me if you had a router (or rather a topology that includes the router) that is struggling with this too and just stops "remembering" the participating endpoints in a multicast group at some time interval.
I think tables of this type have to be kept in every hop on the route (including the kernel which may have to keep track of several processes for the same multicast group).
Some hints about this:
- https://superuser.com/questions/1287485/udp-broadcasting-not-working-on-some-routers
One oft heard piece of advice is:
- if you can, use multicast for dicscovery, switch to unicast after
- try to be specific about the bound interface (in your code you might want to replace address_v4::any() with something like
lo
(127.0.0.1) or whatever ip address identifies your NIC.
udp broadcast using boost::asio under windows
I believe you want to bind your socket to a local endpoint with any() (if you wish to receive broadcast packets - see this question), and send to a remote endpoint using broadcast() (see this question).
The following compiles for me and does not throw any errors:
void test_udp_broadcast(void)
{
boost::asio::io_service io_service;
boost::asio::ip::udp::socket socket(io_service);
boost::asio::ip::udp::endpoint local_endpoint;
boost::asio::ip::udp::endpoint remote_endpoint;
socket.open(boost::asio::ip::udp::v4());
socket.set_option(boost::asio::ip::udp::socket::reuse_address(true));
socket.set_option(boost::asio::socket_base::broadcast(true));
local_endpoint = boost::asio::ip::udp::endpoint(boost::asio::ip::address_v4::any(), 4000);
remote_endpoint = boost::asio::ip::udp::endpoint(boost::asio::ip::address_v4::broadcast(), 4000);
try {
socket.bind(local_endpoint);
socket.send_to(boost::asio::buffer("abc", 3), remote_endpoint);
} catch (boost::system::system_error e) {
std::cout << e.what() << std::endl;
}
}
Read boost::asio UDP Broadcast Response
Your first problem is Udefined Behaviour.
You start asynchronous operations on a temporary object of type udp_find
. The object is destructed immediately after construction, so it doesn't exist anymore even before you start any of the async work (service.run()
).
That is easily fixed by making udp_find
a local variable instead of a temporary:
udp_find op(service, 9000);
Now sending works for me. You will want to test that receiving works as well. In my netstat
output it appears that the UDP socket is bound to an ephemeral port. Sending a datagram to that port makes the test succeed for me.
You might want to actually bind/connect to the broadcast address before receiving (the endpoint&
parameter to async_receive_from
is not for that, I think it is an output parameter).
Different ways of opening and binding a UDP socket with Boost Asio c++
The first method will create a socket without binding to a specific port. This is fine if you don't care about someone initiating the messages with you. IE: You send a message to a recipent, they can reply back because they received the sender's IP and Port along with the message.
If you want someone to be able to message you on a specific IP and port, you can initialize your socket like so:
socket_(io_service, udp::endpoint(udp::v4(), port))
How to receive a UDP broadcast sent to 255.255.255.255 using boost asio?
I finally discovered why my code was never seeing the broadcast packets even though tcpdump proved that they were being received. After finding this StackOverflow question:
Disable reverse path filtering from Linux kernel space
it seems that I just needed to disable Reverse Path Filtering on both hosts like this:
echo 0 > /proc/sys/net/ipv4/conf/all/rp_filter
echo 0 > /proc/sys/net/ipv4/conf/eth0/rp_filter
and then my code worked perfectly with no modifications. Hopefully this will help other people wondering why they can't get UDP broadcasts to the network broadcast address (255.255.255.255) to work.
boost asio broadcast not going out on all interfaces
As suggested by Alan Birtles in the comments to the question i found an explanation here:
UDP-Broadcast on all interfaces
I solved the issue by iterating over he configured interfaces and sending the broadcast to each networks broadcast address as suggested by the linked answer.
Boost Asio not sending UDP broadcast packets
Answering my own question to provide information to others who may have the same issue. I determined that the culprit was the "VirtualBox Host-Only Network" adapter added by VirtualBox. It looks like it was swallowing up all UDP broadcast packets sent from my machine. Disabling that adapter allowed my Asio code to work, and didn't event interfere with my VMs set to Bridged Adapter.
Related Topics
Fast Multiplication/Division by 2 for Floats and Doubles (C/C++)
How to Mark a Struct Template as Friend
How to Easily Indent Output to Ofstream
How to See the Output of the Visual C++ Preprocessor
C++ Sqrt Function Precision for Full Squares
How to Determine If Returned Pointer Is on the Stack or Heap
Hide the Console of a C Program in the Windows Os
How Can Adding Code to a Loop Make It Faster
How Many Decimal Places Does the Primitive Float and Double Support
Openssl::Ssl_Library_Init() Memory Leak
Fine Tuning Hough Line Function Parameters Opencv
How Does Excel Successfully Round Floating Point Numbers Even Though They Are Imprecise
Small String Optimization for Vector
Priority Queue with Limited Space: Looking for a Good Algorithm
Is Clrscr(); a Function in C++
Check If a Type Is Passed in Variadic Template Parameter Pack