I am trying to write a small UDP proxy using Boost asio. Within it I need to read a socket, and if that socket receives a datagram, a callback should be triggered after a specific time, forwarding the datagram. It should also immediately be ready to receive a new datagram.
My issue is that my code works when it is not inside a class, but as soon as I try to make a class of it, the code stops working. I am quite sure I am missing something crucial as I am very new to async boost.
The code that does work looks like this, and I get the output that the timer hits as expected.
#include <iostream>
#include <boost/asio.hpp>
void receive(udp::socket &socket, io_context &ioContext) {
udp::endpoint receive_endpoint;
std::array<char, 8192> buf;
socket.async_receive_from(buffer(buf, buf.size()), receive_endpoint,
[&](const boost::system::error_code& error, std::size_t bytesReceived) {
if (!error && bytesReceived > 0) {
std::cout << "Got a package" << std::endl;
boost::asio::steady_timer timer = boost::asio::steady_timer(ioContext, boost::asio::chrono::milliseconds(100));
timer.async_wait([&](const boost::system::error_code& ec) {
std::cout << "Timer hit" << std::endl;
});
receive(socket, ioContext);
} else {
std::cout << "Error receiving data from client" << std::endl;
}
});
}
int main() {
boost::asio::io_context ioContext;
udp::socket socket(ioContext);
socket.open(udp::v4());
udp::endpoint localEndpoint(ip::address::from_string("127.0.0.1"), 12345);
socket.bind(localEndpoint);
receive(socket, ioContext);
ioContext.run();
return 0;
}
When I then make a class of it, the timer never hits. My code for the main file when using the class looks like this:
#include "udp_class.h"
#include <iostream>
#include <boost/asio.hpp>
int main() {
boost::asio::io_context ioContext;
Proxy my_proxy(ioContext);
my_proxy.start();
ioContext.run();
return 0;
}
with the class definition as
#pragma once
#define BOOST_ASIO_ENABLE_HANDLER_TRACKING
#include <iostream>
#include <boost/asio.hpp>
#include <chrono>
using namespace boost::asio;
using ip::udp;
using std::chrono::milliseconds;
class Proxy {
public:
Proxy(io_context& ioContext): localSocket_(ioContext) { };
void start() {
localSocket_.open(udp::v4());
localSocket_.bind(udp::endpoint(ip::address::from_string("127.0.0.1"), 12345));
receive();
};
private:
void receive() {
udp::endpoint receive_endpoint;
std::array<char, 8192> buf;
localSocket_.async_receive_from(buffer(buf, buf.size()), receive_endpoint,
[&](const boost::system::error_code& error, std::size_t bytesReceived) {
if (!error && bytesReceived > 0) {
std::cout << "Got a package" << std::endl;
boost::asio::steady_timer timer = boost::asio::steady_timer(ioContext, boost::asio::chrono::milliseconds(100));
timer.async_wait([&](const boost::system::error_code& ec) {
std::cout << "Timer hit" << std::endl;
});
receive();
} else {
std::cout << "Error receiving data from client" << std::endl;
}
});
}
io_context ioContext;
udp::socket localSocket_;
};
This receives a datagram, notes that to the console but the timer never hits.
The output from the boost asio debug looks as follows:
@asio|1720035449.638484|0*1|socket@0x16b0ef4f8.async_receive_from
@asio|1720035449.638495|.1|non_blocking_recvfrom,ec=system:35,bytes_transferred=0
@asio|1720035451.692791|.1|non_blocking_recvfrom,ec=system:0,bytes_transferred=12
@asio|1720035451.692827|>1|ec=system:0,bytes_transferred=12
@asio|1720035451.692951|1*2|deadline_timer@0x16b0ef038.async_wait
@asio|1720035451.692976|1*3|socket@0x16b0ef4f8.async_receive_from
@asio|1720035451.692987|.3|non_blocking_recvfrom,ec=system:35,bytes_transferred=0
@asio|1720035451.692992|1|deadline_timer@0x16b0ef038.cancel
@asio|1720035451.692998|<1|
I have been stuck on this and similar issues now for a few days, and cant really wrap my head around what I am doing wrong. I took my whole code and distilled down this example which hopefully represents what the issue is as concisely as possible.
You're creating a new io_context
inside the Proxy class, but using the one passed to the constructor for the socket. This mismatch is causing problems.
fixed class:
#pragma once
#define BOOST_ASIO_ENABLE_HANDLER_TRACKING
#include <iostream>
#include <boost/asio.hpp>
#include <chrono>
using namespace boost::asio;
using ip::udp;
using std::chrono::milliseconds;
class Proxy {
public:
Proxy(io_context& io): ioContext(io), localSocket_(io) { };
void start() {
localSocket_.open(udp::v4());
localSocket_.bind(udp::endpoint(ip::address::from_string("127.0.0.1"), 12345));
receive();
};
private:
void receive() {
udp::endpoint receive_endpoint;
std::array<char, 8192> buf;
localSocket_.async_receive_from(buffer(buf, buf.size()), receive_endpoint,
[this](const boost::system::error_code& error, std::size_t bytesReceived) {
if (!error && bytesReceived > 0) {
std::cout << "Got a package" << std::endl;
auto timer = std::make_shared<boost::asio::steady_timer>(ioContext, boost::asio::chrono::milliseconds(100));
timer->async_wait([timer, this](const boost::system::error_code& ec) {
std::cout << "Timer hit" << std::endl;
});
receive();
} else {
std::cout << "Error receiving data from client" << std::endl;
}
});
}
io_context& ioContext;
udp::socket localSocket_;
};
and main
:
int main() {
boost::asio::io_context ioContext;
Proxy my_proxy(ioContext);
my_proxy.start();
ioContext.run();
return 0;
}