asio async operations aren't processed - c++

I am following ASIO's async_tcp_echo_server.cpp example to write a server.
My server logic looks like this (.cpp part):
1.Server startup:
bool Server::Start()
{
mServerThread = std::thread(&Server::ServerThreadFunc, this, std::ref(ios));
//ios is asio::io_service
}
2.Init acceptor and listen for incoming connection:
void Server::ServerThreadFunc(io_service& service)
{
tcp::endpoint endp{ address::from_string(LOCAL_HOST),MY_PORT };
mAcceptor = acceptor_ptr(new tcp::acceptor{ service,endp });
// Add a job to start accepting connections.
StartAccept(*mAcceptor);
// Process event loop.Hang here till service terminated
service.run();
std::cout << "Server thread exiting." << std::endl;
}
3.Accept a connection and start reading from the client:
void Server::StartAccept(tcp::acceptor& acceptor)
{
acceptor.async_accept([&](std::error_code err, tcp::socket socket)
{
if (!err)
{
std::make_shared<Connection>(std::move(socket))->StartRead(mCounter);
StartAccept(acceptor);
}
else
{
std::cerr << "Error:" << "Failed to accept new connection" << err.message() << std::endl;
return;
}
});
}
void Connection::StartRead(uint32_t frameIndex)
{
asio::async_read(mSocket, asio::buffer(&mHeader, sizeof(XHeader)), std::bind(&Connection::ReadHandler, shared_from_this(), std::placeholders::_1, std::placeholders::_2, frameIndex));
}
So the Connection instance finally triggers ReadHandler callback where I perform actual read and write:
void Connection::ReadHandler(const asio::error_code& error, size_t bytes_transfered, uint32_t frameIndex)
{
if (bytes_transfered == sizeof(XHeader))
{
uint32_t reply;
if (mHeader.code == 12345)
{
reply = (uint32_t)12121;
size_t len = asio::write(mSocket, asio::buffer(&reply, sizeof(uint32_t)));
}
else
{
reply = (uint32_t)0;
size_t len = asio::write(mSocket, asio::buffer(&reply, sizeof(uint32_t)));
this->mSocket.shutdown(tcp::socket::shutdown_both);
return;
}
}
while (mSocket.is_open())
{
XPacket packet;
packet.dataSize = rt->buff.size();
packet.data = rt->buff.data();
std::vector<asio::const_buffer> buffers;
buffers.push_back(asio::buffer(&packet.dataSize,sizeof(uint64_t)));
buffers.push_back(asio::buffer(packet.data, packet.dataSize));
auto self(shared_from_this());
asio::async_write(mSocket, buffers,
[this, self](const asio::error_code error, size_t bytes_transfered)
{
if (error)
{
ERROR(200, "Error sending packet");
ERROR(200, error.message().c_str());
}
}
);
}
}
Now, here is the problem. The server receives data from the client and sends ,using sync asio::write, fine. But when it comes to to asio::async_read or asio::async_write inside the while loop, the method's lambda callback never gets triggered, unless I put io_context().run_one(); immediately after that. I don't understand why I see this behaviour. I do call io_service.run() right after acceptor init, so it blocks there till the server exit. The only difference of my code from the asio example, as far as I can tell, is that I run my logic from a custom thread.

Your callback isn't returning, preventing the event loop from executing other handlers.
In general, if you want an asynchronous flow, you would chain callbacks e.g. callback checks is_open(), and if true calls async_write() with itself as the callback.
In either case, the callback returns.
This allows the event loop to run, calling your callback, and so on.
In short, you should make sure your asynchronous callbacks always return in a reasonable time frame.

Related

QUdpSocket client connected to echo server not working

I am developing a QT 6 Widget based UDP audio application that repeatedly sends out a single UDP audio frame sample (4K bytes sine wave tone) to a remote UDP echo server at a predetermined rate - (right now the echo server is hosted locally though).
The UDP echo server is based on the asynchronous UDP echo server sample developed by the asio author (not me). This is shown below (slightly modified to include a hard coded 4K block for testing purposes). The application is also launched with a port parameter 1234 - so it listens on port 1234 for the incoming audio packet that it will echo back to client.
//
// async_udp_echo_server.cpp
// ~~~~~~~~~~~~~~~~~~~~~~~~~
//
// Copyright (c) 2003-2022 Christopher M. Kohlhoff (chris at kohlhoff dot com)
//
// Distributed under the Boost Software License, Version 1.0. (See accompanying
// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
//
#include <cstdlib>
#include <iostream>
#include <asio/ts/buffer.hpp>
#include <asio/ts/internet.hpp>
using asio::ip::udp;
class server {
public:
server(asio::io_context& io_context, short port)
: socket_(io_context, udp::endpoint(udp::v4(), port)) {
do_receive();
}
void do_receive() {
socket_.async_receive_from(
asio::buffer(data_, max_length), sender_endpoint_,
[this](std::error_code ec, std::size_t bytes_recvd) {
if (!ec && bytes_recvd > 0) {
do_send(bytes_recvd);
} else {
do_receive();
}
});
}
void do_send(std::size_t length) {
socket_.async_send_to(
asio::buffer(data_, length), sender_endpoint_,
[this](std::error_code /*ec*/, std::size_t /*bytes_sent*/) {
do_receive();
});
}
private:
udp::socket socket_;
udp::endpoint sender_endpoint_;
enum { max_length = 4096 };
char data_[max_length]{};
};
int main(int argc, char* argv[]) {
try {
if (argc != 2) {
std::cerr << "Usage: async_udp_echo_server <port>\n";
return 1;
}
asio::io_context io_context;
server s(io_context, std::atoi(argv[1]));
io_context.run();
} catch (std::exception& e) {
std::cerr << "Exception: " << e.what() << "\n";
}
return 0;
}
I currently have this working successfully in the client as a stand alone asio worker thread, however since I need to graphically display the returned audio packets, I cannot use the stand alone asio thread approach; I need to use QT with its signals/slots async magic instead.
For the purposes of illustration, I also include my working asio client code that runs in a separate joinable thread. This client thread uses a asio::steady_timer that fires an asynchronous 4k UDP packet repeatedly to the echo server. The code also compares the echoed back contents to this outgoing audio sample successfully.
void
RTPClient::start() {
mpSendEndpoint = std::make_unique<ip::udp::endpoint>(
ip::address::from_string(mConfig.mHostName),
mConfig.mPortNum);
mpSocket = std::make_unique<ip::udp::socket>(
mIOContext, mpSendEndpoint->protocol());
mpSocketTimer = std::make_unique<steady_timer>(
mIOContext);
mWorker = std::thread([this]() {
mIOContext.run();
});
if (!mShutdownFlag) {
// kick off the async chain by immediate timeout
mpSocketTimer->expires_after(std::chrono::seconds(0));
mpSocketTimer->async_wait([this]<typename T0>(T0&& ec) {
handle_timeout(std::forward<T0>(ec));
});
}
}
void
RTPClient::handle_timeout(const error_code& ec)
{
if (!ec && !mShutdownFlag) {
if (!mpAudioOutput) {
// check to see if there is new audio test data waiting in queue
if (const auto audioData = mIPCQueue->try_pop(); audioData) {
// new audio waiting, copy the data to mpAudioTXData and allocate an identically
// sized receive buffer to receive the echo replies from the server
mpAudioInput = std::make_unique<AudioDatagram>(audioData->first.size());
mpAudioOutput = std::make_unique<AudioDatagram>(std::move(audioData->first));
mAudioBlockUSecs = audioData->second;
} else {
mpSocketTimer->expires_after(seconds(1));
mpSocketTimer->async_wait([this]<typename T0>(T0&& ec) {
handle_timeout(std::forward<T0>(ec));
});
// nothing to send as waveform data not received from GUI.
// short circuit return with a 1 sec poll
return;
}
}
mpSocket->async_send_to(asio::buffer(
mpAudioOutput.get(), mpAudioOutput->size()),
*mpSendEndpoint, [this]<typename T0, typename T1>(T0&& ec, T1&& bytes_transferred) {
handle_send_to(std::forward<T0>(ec), std::forward<T1>(bytes_transferred));
});
}
}
void
RTPClient::handle_send_to(const error_code& ec, std::size_t bytes_transferred) {
if (!ec && bytes_transferred > 0 && !mShutdownFlag) {
mpSocketTimer->expires_after(microseconds(mAudioBlockUSecs));
mpSocketTimer->async_wait([this]<typename T0>(T0&& ec) {
handle_timeout(std::forward<T0>(ec));
});
mpSocket->async_receive_from(asio::buffer(
mpAudioInput.get(), mpAudioInput->size()), *mpSendEndpoint,
[this]<typename T0, typename T1>(T0&& ec, T1&& bytes_transferred) {
handle_receive(std::forward<T0>(ec), std::forward<T1>(bytes_transferred));
});
}
}
void
RTPClient::handle_receive(const error_code& ec, std::size_t bytes_transferred) {
if (!ec && bytes_transferred > 0) {
double foo = 0.0;
for (const auto next : *mpAudioOutput) {
foo += (double)next;
}
double bar = 0.0;
for (const auto next : *mpAudioInput) {
bar += (double)next;
}
if (foo != bar)
{
auto baz = 0;
(void)baz;
}
}
}
/**
* Shutdown the protocol instance by shutting down the IPC
* queue and closing the socket and associated timers etc.
*
* <p>This is achieved by setting a flag which is read by the
* busy loop as an exit condition.
*/
void
RTPClient::shutdown() {
// set the shared shutdown flag
mShutdownFlag = true;
// wake up any locked threads so they can see the above flag
if (mIPCQueue) {
mIPCQueue->shutdown();
}
// stop the socket timer - do not reset it
// as there are some time sensitive parts in the code
// where mpSocketTimer is dereferenced
if (mpSocketTimer) {
mpSocketTimer->cancel();
}
std::error_code ignoredError;
// close the socket if we created & opened it, making
// sure that we close down both ends of the socket.
if (mpSocket && mpSocket->is_open()) {
mpSocket->shutdown(ip::udp::socket::shutdown_both, ignoredError);
// reset so we will reallocate and then reopen
// via boost::async_connect(...) later.
mpSocket.reset();
}
// wait for the any other detached threads to see mShutdownFlag
// as it is running in a detached mWorkerThread which sleeps
// for 50ms CDU key polling requests.
std::this_thread::sleep_for(milliseconds(200));
}
I need to replace this separate asio client thread code with a QUdpSocket based client code to do the equivalent, as I need to use signals/slots to notify the GUI when the blocks arrive and display the returned waveform in a widget. To this end I have the following QT worker thread. I can see that the asio echo server receives the datagram, however I do not know how to receive the echoed contents back into the client. Is there some bind or connect call that I need to do on the client side. I am totally confused with when to call bind and when to call connect on UDP sockets.
// SYSTEM INCLUDES
//#include <..>
// APPLICATION INCLUDES
#include "RTPSession.h"
// DEFINES
// EXTERNAL FUNCTIONS
// EXTERNAL VARIABLES
// CONSTANTS
// STRUCTS
// FUNCTIONS
// NAMESPACE USAGE
using namespace std::chrono;
// STATIC VARIABLE INITIALIZATIONS
std::mutex RTPSession::gMutexGuard;
RTPSession::RTPSession(QObject* parent)
: QObject(parent)
, mpSocket{ std::make_unique<QUdpSocket>(parent) }
{
mpSocket->bind(45454, QUdpSocket::DefaultForPlatform);
connect(mpSocket.get(), &QUdpSocket::readyRead,
this, &RTPSession::processPendingDatagrams);
}
/**
* Thread function that listens RTP session updates.
*
* <p>The implementation polls for shutdown every second.
*
* #param rRTPInfo [in] qt thread parameters.
*/
void
RTPSession::doWork(
const std::tuple<int32_t, int32_t, int32_t>& /*rRTPInfo*/)
{
try {
// just dispatched, so reset exit flag
mExitWorkLoop = false;
int frameCounter = 0;
while (!mExitWorkLoop) {
constexpr auto gPollMillis = 1000;
// poll using shortest (non zero) interval in schedule
std::unique_lock<std::mutex> lk(gMutexGuard);
mCondVariable.wait_for(lk, milliseconds(gPollMillis),
[this] { return mExitWorkLoop; });
QByteArray datagram = "Broadcast message " + QByteArray::number(frameCounter++);
mpSocket->writeDatagram(datagram.data(), datagram.size(),
QHostAddress::LocalHost, 1234);
if (mpSocket->hasPendingDatagrams()) {
//mpSocket->readDatagram()
int t = 0;
(void)t;
}
// update GUI with the audio stats - add more later
emit updateProgress(frameCounter++);
}
} catch (const std::exception& rEx) {
// exit thread with the exception details
emit finishWork(tr("exiting worker, error:") + rEx.what());
}
// exit thread with status bar message
emit finishWork(tr("finished"));
}
void
RTPSession::shutdown()
{
// Critical section.
std::scoped_lock<std::mutex> lock(gMutexGuard);
mExitWorkLoop = true;
// Notify the potentially sleeping thread that is
// waiting for up to 1 second
mCondVariable.notify_one();
}
void
RTPSession::processPendingDatagrams() {
QByteArray datagram;
while (mpSocket->hasPendingDatagrams()) {
datagram.resize(int(mpSocket->pendingDatagramSize()));
mpSocket->readDatagram(datagram.data(), datagram.size());
//statusLabel->setText(tr("Received datagram: \"%1\"")
// .arg(datagram.constData()));
}
}

Boost.Asio: Why the timer is executed only once?

I have a function called read_packet. This function remains blocked while there is no connection request or the timer is signaled.
The code is the following:
std::size_t read_packet(const std::chrono::milliseconds& timeout,
boost::system::error_code& error)
{
// m_timer_ --> boost::asio::high_resolution_timer
if(!m_is_first_time_) {
m_is_first_time = true;
// Set an expiry time relative to now.
m_timer_.expires_from_now( timeout );
} else {
m_timer_.expires_at( m_timer_.expires_at() + timeout );
}
// Start an asynchronous wait.
m_timer_.async_wait(
[ this ](const boost::system::error_code& error){
if(!error) m_is_timeout_signaled_ = true;
}
);
auto result = m_io_service_.run_one();
if( !m_is_timeout_signaled_ ) {
m_timer_.cancel();
}
m_io_service_.reset();
return result;
}
The function works correctly while not receiving a connection request. All acceptances of requests are asynchronous.
After accepting a connection, the run_one() function does not remains blocked the time set by the timer. The function always returns 1 (one handle has been processed). This handle corresponds to the timer.
I do not understand why this situation occurs.
Why the function is not blocked the time required for the timer?
Cheers.
NOTE: This function is used in a loop.
UPDATE:
I have my own io_service::run() function. This function performs other actions and tasks. I want to listen and process the network level for a period of time:
If something comes on the network level, io_service::run_one() returns and read_packet() returns the control to my run() function.
Otherwise, the timer is fired and read_packet() returns the control to my run() function.
Everything that comes from the network level is stored in a data structure. Then my run() function operates on that data structure.
It also runs other options.
void run(duration timeout, boost::system::error_code& error)
{
time_point start = clock_type::now();
time_point deadline = start + timeout;
while( !stop() ) {
read_packet(timeout, error);
if(error) return;
if(is_timeout_expired( start, deadline, timeout )) return;
// processing network level
// other actions
}
}
In my case, the sockets are always active until a client requests the closing of the connection.
During a time slot, you manage the network level and for another slot you do other things.
After reading the question more closely I got the idea that you are actually trying to use Asio to get synchronous IO, but with a timeout on each read operation.
That's not what Asio was intended for (hence, the name "Asynchronous IO Library").
But sure, you can do it if you insist. Like I said, I feel you're overcomplicating things.
In the completion handler of your timer, just cancel the socket operation if the timer had expired. (Note that if it didn't, you'll get operation_aborted, so check the error code).
Small selfcontained example (which is what you should always do when trying to get help, by the way):
Live On Coliru
#include <boost/asio.hpp>
#include <boost/asio/high_resolution_timer.hpp>
#include <iostream>
struct Program {
Program() { sock_.connect({ boost::asio::ip::address_v4{}, 6771 }); }
std::size_t read_packet(const std::chrono::milliseconds &timeout, boost::system::error_code &error) {
m_io_service_.reset();
boost::asio::high_resolution_timer timer { m_io_service_, timeout };
timer.async_wait([&](boost::system::error_code) {
sock_.cancel();
});
size_t transferred = 0;
boost::asio::async_read(sock_, boost::asio::buffer(buffer_), [&](boost::system::error_code ec, size_t tx) {
error = ec;
transferred = tx;
});
m_io_service_.run();
return transferred;
}
private:
boost::asio::io_service m_io_service_;
using tcp = boost::asio::ip::tcp;
tcp::socket sock_{ m_io_service_ };
std::array<char, 512> buffer_;
};
int main() {
Program client;
boost::system::error_code ec;
while (!ec) {
client.read_packet(std::chrono::milliseconds(100), ec);
}
std::cout << "Exited with '" << ec.message() << "'\n"; // operation canceled in case of timeout
}
If the socket operation succeeds you can see e.g.:
Exited with 'End of file'
Otherwise, if the operation didn't complete within 100 milliseconds, it will print:
Exited with 'Operation canceled'
See also await_operation in this previous answer, which generalizes this pattern a bit more:
boost::asio + std::future - Access violation after closing socket
Ok, The code is incorrect. When the timer is canceled, the timer handler is always executed. For this reason io_service::run_one() function is never blocked.
More information: basic_waitable_timer::cancel
Thanks for the help.

Reading a Serial Port Asynchronously Boost

I am trying to read several bytes asynchronously from a serial port. Currently the code I am using (source is How do I perform a nonblocking read using asio?), only allows me to read 1 byte and then it exits. How do I get it so that the code keeps reading until there is no more incoming data in the serial port? Thanks :)
void foo()
{
boost::asio::io_service io_svc;
boost::asio::serial_port ser_port(io_svc, "/dev/ttyS0");
boost::asio::deadline_timer timeout(io_svc);
unsigned char my_buffer[2];
bool data_available = false;
ser_port.async_read_some(boost::asio::buffer(my_buffer),
boost::bind(&read_callback, boost::ref(data_available),
boost::ref(timeout),boost::asio::placeholders::error(),
boost::asio::placeholders::bytes_transferred()));
timeout.expires_from_now(boost::posix_time::seconds(30));
timeout.async_wait(boost::bind(&wait_callback, boost::ref(ser_port),boost::asio::placeholders::error()));
io_svc.run();
io_svc.
if(!data_available)
{
ser_port.close();
cout << "ser_port was closed";
}
}
void read_callback(bool& data_available, boost::asio::deadline_timer& timeout, const boost::system::error_code& error, std::size_t bytes_transferred)
{
if (error || !bytes_transferred)
{
// No data was read!
data_available = false;
return;
}
timeout.cancel();
data_available = true;
}
void wait_callback(boost::asio::serial_port& ser_port, const boost::system::error_code& error)
{
if (error)
{
// Data was read and this timeout was cancelled
return;
}
ser_port.cancel();
}
In your read_callback you cancel your timer and do not start a new read.
So what do you expect ASIO to do. You just canceled all handlers and like the documentation states, the run method will return when no handlers are left.
So if you want to have more data than just the one byte you receive you can do two things:
First just issue another call to asio to read more from the port.
void read_callback(bool& data_available, boost::asio::deadline_timer& timeout, const boost::system::error_code& error, std::size_t bytes_transferred)
{
if (error || !bytes_transferred)
{
// No data was read!
data_available = false;
return;
}
// do something with the data you just read
ser_port.async_read_some(boost::asio::buffer(my_buffer),
boost::bind(&read_callback, boost::ref(data_available),
boost::ref(timeout),boost::asio::placeholders::error(),
boost::asio::placeholders::bytes_transferred()));
//restart your timer
data_available = true;
}
or you can add use transfer_at_least to get at least the amount of data you need.

Boost asio async_write callback doesn't get called

I'm trying to write a simple server to send messages asynchronously, but my callback for async_write doesn't get called. By the way, data is succesfully transmitted to the client.
Firs, my server initialisation code:
void Server::start()
{
ioService = new boost::asio::io_service();
acceptor = new tcp::acceptor(*ioService, tcp::endpoint(tcp::v4(), port));
serverRunning = true;
serverThread = new boost::thread(&Server::run, this);
startAccept();
}
My start accept and accept handler methods:
void Server::startAccept()
{
serverSocket = new tcp::socket(acceptor->get_io_service());
acceptor->async_accept(*serverSocket,
boost::bind(&Server::handleAccept, shared_from_this(),
boost::asio::placeholders::error));
}
void Server::handleAccept( const boost::system::error_code& error )
{
changeState(Connected);
}
The handleAccept method get called when i connect, the changeState method does nothing for now. After the client connected, i put some data to the toSend vector, and the server's thread sends them:
void Server::run(){
while( serverRunning ){
ioService->poll();
if (toSend.size()>0 ){
mtx.lock();
for (int i=0; i<toSend.size(); i++){
cout << "async write" << endl;
boost::asio::async_write(*serverSocket, boost::asio::buffer(toSend.at(i)),
boost::bind(&Server::handleSend, shared_from_this(),
toSend.at(i),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
toSend.clear();
mtx.unlock();
}
usleep(1000);
}
}
I can see "async write" messages coming on std out, and the client recieves the data as well. My handleSend method is just some cout now, but it never get called. Why?
If you really want to poll the io_service manually, do this after it gets some work, and call reset between the iterations.
Besides, do not call asio::async_write in a loop - the data won't arrive in the correct order. Instead, either prepare a single sequence of buffers and send it at once, or chain async_write - completion handler - async_write, as shown in the examples.

Boost.Asio deadline_timer not working as expected

I'm trying to implement a timeout for a Boost.Asio read on a TCP socket.
I am trying to use a async_read_some with a deadline_timer. My function below is a member of a class that holds a smart pointer to the TCP socket and io_service. What I would expect to happen when called on an active socket that doesn't return any data is wait 2 seconds and return false.
What happens is: If the socket never returns any data it works as expected. How ever if the server returns the data the proceeding calls to the method below return immediately because to timers callback is called without waiting the two seconds.
I tried commenting out the async_read_some call and the function always works as expected. Why would async_read_some change how the timer works?
client::client() {
// Init socket and timer
pSock = boost::shared_ptr<tcp::socket > (new tcp::socket(io_service));
}
bool client::getData() {
// Reset io_service
io_service.reset();
// Init read timer
boost::asio::deadline_timer timer(pSock->io_service());
timer.expires_from_now(boost::posix_time::seconds(2));
timer.async_wait(boost::bind(&client::read_timeout, this, boost::system::error_code(), true));
// // Async read the data
pSock->async_read_some(boost::asio::buffer(buffer_),
boost::bind(&client::read_complete,
this,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred
));
// While io_service runs check read result
while (pSock->io_service().run_one()) {
if (m_read_result > 0) {
// Read success
return m_read_result;
}else if(m_read_result < 0){
return false;
}
}
}
}
void client::read_complete(const boost::system::error_code& error, size_t bytes_transferred) {
if (!error) {
m_read_result = bytes_transferred;
}else{
m_read_result = -1;
}
}
void client::read_timeout(const boost::system::error_code& error, bool timeout) {
if(!error){
m_read_result = -1;
}
}
Simple problem when setting up the timer boost::system::error_code() should be changed to _1 or a error::placeholder
timer.async_wait(boost::bind(&client::read_timeout, this, _1, true));
You have negated condition when you check for connection errors.
It should be:
if(error){
std::cout << "read_timeout Error - " << error.message() << std::endl;
}
Now you will see, that the callback is invoked with error code boost::asio::error::operation_aborted.
This is because, when you receive any data, you return from function getData and deadline_timer's destructor calls the callback with the error code set.