c++ – Boost.Asio UDP socket, An invalid argument was supplied error
You have data races.
You start a thread which references both io_service and socket which are local variables in main. However, main exits immediately, destructing those local variables.
The thread, which was detached, still runs causing Undefined Behaviour because of the stale references.
Fix it by
(a) avoiding the run-away thread
(b) also synchronizing on its termination
Also fixing some other issues and making up some code that might also be less safe (e.g. the potentially racy isListening_). There are more issues hiding
e.g.
- remote_endpoint is probably used for local endpoint as well
- no bind was happening on the listening socket
startListeningmay be more aptly namedstartReceive- blocking receive_from may never return so the thread would never stop unless data was received or an error occurred
- the
catchblock seems inefficient since it would only handle the error thrown locally
#include <boost/asio.hpp>
#include <fmt/ranges.h>
#include <iostream>
namespace asio = boost::asio;
using asio::ip::udp;
using boost::system::error_code;
using namespace std::chrono_literals;
namespace spdlog {
void info(auto fmt, auto const&... args) { fmt::print(fmt::runtime(fmt), args...); }
} // namespace spdlog
struct tello {
tello(asio::io_context& io_context, udp::socket& socket, udp::endpoint const& remote_endpoint);
void StartListening();
void StopListening();
void ReceiveThread();
private:
std::string ip;
uint16_t port;
asio::io_context& io_context;
udp::socket& socket;
udp::endpoint remote_endpoint;
std::atomic_bool isListening_{true};
std::thread udpThread;
};
tello::tello(asio::io_context& io_context, udp::socket& socket, udp::endpoint const& remote_endpoint)
: ip(remote_endpoint.address().to_string())
, port(remote_endpoint.port())
, io_context(io_context)
, socket(socket)
, remote_endpoint(remote_endpoint) //
{
socket.bind(remote_endpoint);
}
void tello::ReceiveThread() {
for (char receivedData[1024]; isListening_;) {
try {
error_code error;
size_t len = socket.receive_from(asio::buffer(receivedData), remote_endpoint, 0, error);
if (error && error != asio::error::message_size) {
throw boost::system::system_error(error);
}
receivedData[len] = '\0';
spdlog::info("Received UDP data: {}", std::string_view(receivedData, len));
} catch (std::exception const& e) {
std::cerr << "Error receiving UDP data: " << e.what() << std::endl;
}
}
}
void tello::StartListening() { udpThread = std::thread(&tello::ReceiveThread, this); }
void tello::StopListening() {
isListening_ = false;
udpThread.join();
}
int main() {
asio::io_context io_context;
udp::socket socket(io_context);
udp::endpoint remote_endpoint({}, 12345);
socket.open(udp::v4());
tello drone(io_context, socket, remote_endpoint);
drone.StartListening();
std::this_thread::sleep_for(30s);
drone.StopListening();
}
Local demo:
BONUS
You want to use async IO so you can have much better behaviour. I think ironically it’s a little bit simpler (about 30 LoC less):
#include <boost/asio.hpp>
#include <fmt/ranges.h>
#include <iostream>
namespace asio = boost::asio;
using asio::ip::udp;
using boost::system::error_code;
using namespace std::chrono_literals;
namespace spdlog {
void info(auto fmt, auto const&... args) { fmt::print(fmt::runtime(fmt), args...); }
} // namespace spdlog
struct tello {
tello(asio::any_io_executor ex, udp::endpoint ep);
void Start() { readLoop(); }
void Stop();
private:
void readLoop() {
socket_.async_receive_from(
asio::buffer(buffer_), remote_ep_, [this](error_code ec, size_t len) {
if (ec && ec != asio::error::message_size) {
std::cerr << "Error receiving UDP data: " << ec.message() << std::endl;
} else {
spdlog::info("Received UDP data: {}", std::string_view(buffer_.data(), len));
readLoop();
}
});
}
udp::socket socket_;
udp::endpoint remote_ep_;
std::array<char, 1024> buffer_;
};
tello::tello(asio::any_io_executor ex, udp::endpoint ep) : socket_(ex, ep.protocol()) { socket_.bind(ep); }
void tello::Stop() { socket_.cancel(); }
int main() {
asio::io_context ioc;
tello drone(ioc.get_executor(), {{}, 12345});
drone.Start();
ioc.run_for(30s);
// not really needed:
drone.Stop();
ioc.run();
}
And more local demo:
Read more here: Source link


