Is there any way to know the number of bytes transfered in an async_read function if the read handler won't get invoked? - c++

I have coded the following DoRead function which reads data from the opened serial port, and it works as expected except one thing:
When the timeout elapses before the read completes, then no read handler will be invoked and I can not get the number of bytes read at this point.
Here is my code:
std::size_t wxSerialPort::DoRead(std::string& str, const int timeout)
{
m_bytes_transferred_read = 0;
boost::asio::async_read(m_serialPort, boost::asio::buffer(str),
std::bind(&wxSerialPort::AsyncReadHandler, this,
std::placeholders::_1, std::placeholders::_2));
m_io_context.restart();
if (timeout == wxTIMEOUT_INFINITE)
{
m_io_context.run_until(std::chrono::steady_clock::time_point::max());
}
else
{
m_io_context.run_for(std::chrono::milliseconds(timeout));
}
return m_bytes_transferred_read; // At this point I always get 0 bytes read.
}
void wxSerialPort::AsyncReadHandler(const boost::system::error_code& error, std::size_t bytes_transferred)
{
m_bytes_transferred_read = bytes_transferred;
}
Keep in mind that any variable preceded with m_ is a member variable.
But if I give a small buffer for example to the function, then the read handler will be invoked before the timeout, and I get the actual number of bytes read.
Thank you in advance.

It sounds like you need to call async_read_some instead of async_read.
The async_read function ensures that the requested amount of data is read before the asynchronous operation completes, i.e. it needs enough data to fill the buffer before it calls the read handler.
The basic_serial_port::async_read_some method calls the read handler whenever data has been received, regardless of whether the buffer is full or not.
So simply replace the call to async_read with:
m_serialPort.async_read_some(boost::asio::buffer(str),
std::bind(&wxSerialPort::AsyncReadHandler, this,
std::placeholders::_1, std::placeholders::_2));

it turns out that, boost-asio -by design-, won't call any IO handler for any of the io_context::run_for, io_context::run_one_for, io_context::run_until and io_context::run_one_until functions when the timeout elapses.
And the solution for this problem, would be to provide our own wait handler and cancel (basic_serial_port::cancel) all asynchronous operations associated with the serial port in that wait handler, that in turn will trigger our read handler with a boost::asio::error::operation_aborted error code.
And the resulting code will be as follows:
std::size_t wxSerialPort::DoRead(std::string& str, const int timeout)
{
m_bytes_transferred_read = 0;
if (timeout == wxTIMEOUT_INFINITE)
{
m_timer.expires_at(std::chrono::steady_clock::time_point::max());
}
else
{
m_timer.expires_from_now(std::chrono::milliseconds(timeout));
}
m_timer.async_wait(std::bind(&wxSerialPort::AsyncWaitHandler, this,
std::placeholders::_1));
boost::asio::async_read(m_serialPort, boost::asio::buffer(str),
std::bind(&wxSerialPort::AsyncReadHandler, this,
std::placeholders::_1, std::placeholders::_2));
m_io_context.restart();
m_io_context.run();
return m_bytes_transferred_read;
}
void wxSerialPort::AsyncReadHandler(const boost::system::error_code& error, std::size_t bytes_transferred)
{
if (error != boost::asio::error::operation_aborted)
{
m_timer.cancel();
}
m_bytes_transferred_read = bytes_transferred;
}
void wxSerialPort::AsyncWaitHandler(const boost::system::error_code& error)
{
if (error != boost::asio::error::operation_aborted)
{
m_serialPort.cancel();
}
}
Thank you.

Related

asio async operations aren't processed

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.

Why boost::asio::async_read completion sometimes executed with bytes_transferred=0 and ec=0?

Recently I have found an annoying problem with boost:asio::async_read.
When I transfer large amounts of data, say a file of about 9GB, boost:asio::async_read works well in most of the time, but sometimes the completion of async_read is executed with bytes_transferred=0 and ec=0! And sometimes the completion of async_read is executed with bytes_transferred!=sp_recv_data->size() and ec still equal to 0.
My code just like below:
void CNetProactorClientImpl::async_recv()
{
auto sp_vec_buf = make_shared<std::vector<unsigned char>>(1 * 1024 * 1024);
/*
boost::asio::async_read
this function is used to asynchronously read a certain number of bytes of data from a stream.
the function call always returns immediately. the asynchronous operation will continue until one of the following conditions is true:
*the supplied buffers are full. that is, the bytes transferred is equal to the sum of the buffer sizes.
*an error occurred.
*/
boost::asio::async_read(*sp_socket,
boost::asio::buffer(sp_vec_buf),
bind(&CNetProactorClientImpl::async_recv_complete_handler, this,
sp_vec_buf,
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred));
}
void CNetProactorClientImpl::async_recv_complete_handler(shared_ptr<std::vector<unsigned char>> sp_recv_data, const boost::system::error_code& ec, size_t bytes_transferred)
{
if (!ec)
{
//no error
if (bytes_transferred == 0)
{
//Sometimes it trigger here, ec is 0 but also bytes_transferred! Why???
assert(bytes_transferred == sp_recv_data->size());
}
if (bytes_transferred != sp_recv_data->size())
{
/*
Sometimes it trigger here, it is also a strange behavior that bytes_transferred NOT equal to sp_recv_data->size() because the Boost document say:
The asynchronous operation will continue until one of the following conditions is true:
*The supplied buffers are full. That is, the bytes transferred is equal to the sum of the buffer sizes.
*An error occurred.
*/
assert(bytes_transferred == sp_recv_data->size());
}
//do normal action
}
else
{
//error handling
disconnect();
}
}
My boost version is 1.61. Testing environment: win10 pro +VS2015 Update3.

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.

Consume only part of data in boost::asio basic_stream_socket::async_read_some handler

I am new into boost::asio so my question maight be dumb - sorry if it is such.
I am writing asynchronous server application with keepalive (multiple requests may be sent on single connection).
Connection handling routine is simple:
In a loop:
schedule read request with socket->async_read_some(buffer, handler)
from handler schedule write response with async_write.
The problem I am facing is that when
handler passed to async_read_some is called by on of io_service threads, buffers may actually contain more data than single request (e.g. part of next request sent by client).
I do not want to (and cannot if it is only part of request) handle this remaining bytes at the moment.
I would like to do it after handling previous request is finished.
It would be easy to address this if I had the possiblity to reinject unnecessary remainging data back to the socket. So it is handled on next async_read_some call.
Is there such possiblity in boost::asio or do I have to store the remaining data somewhere aside, and handle it myself with extra code.
I think what you are looking for is asio::streambuf.
Basically, you can inspect your seeded streambuf as a char*, read as much as you see fit, and then inform how much was actually processed by consume(amount).
Working code-example to parse HTTP-header as a client:
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <iostream>
#include <string>
namespace asio = boost::asio;
std::string LINE_TERMINATION = "\r\n";
class Connection {
asio::streambuf _buf;
asio::ip::tcp::socket _socket;
public:
Connection(asio::io_service& ioSvc, asio::ip::tcp::endpoint server)
: _socket(ioSvc)
{
_socket.connect(server);
_socket.send(boost::asio::buffer("GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n"));
readMore();
}
void readMore() {
// Allocate 13 bytes space on the end of the buffer. Evil prime number to prove algorithm works.
asio::streambuf::mutable_buffers_type buf = _buf.prepare(13);
// Perform read
_socket.async_read_some(buf, boost::bind(
&Connection::onRead, this,
asio::placeholders::bytes_transferred, asio::placeholders::error
));
}
void onRead(size_t read, const boost::system::error_code& ec) {
if ((!ec) && (read > 0)) {
// Mark to buffer how much was actually read
_buf.commit(read);
// Use some ugly parsing to extract whole lines.
const char* data_ = boost::asio::buffer_cast<const char*>(_buf.data());
std::string data(data_, _buf.size());
size_t start = 0;
size_t end = data.find(LINE_TERMINATION, start);
while (end < data.size()) {
std::cout << "LINE:" << data.substr(start, end-start) << std::endl;
start = end + LINE_TERMINATION.size();
end = data.find(LINE_TERMINATION, start);
}
_buf.consume(start);
// Wait for next data
readMore();
}
}
};
int main(int, char**) {
asio::io_service ioSvc;
// Setup a connection and run
asio::ip::address localhost = asio::ip::address::from_string("127.0.0.1");
Connection c(ioSvc, asio::ip::tcp::endpoint(localhost, 80));
ioSvc.run();
}
One way of tackling this when using a reliable and ordered transport like TCP is to:
Write a header of known size, containing the size of the rest of the message
Write the rest of the message
And on the receiving end:
Read just enough bytes to get the header
Read the rest of the message and no more
If you know the messages are going to be of a fixed length, you can do something like the following:
//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
void
Connection::readMore()
{
if (m_connected)
{
// Asynchronously read some data from the connection into the buffer.
// Using shared_from_this() will prevent this Connection object from
// being destroyed while data is being read.
boost::asio::async_read(
m_socket,
boost::asio::buffer(
m_readMessage.getData(),
MessageBuffer::MESSAGE_LENGTH
),
boost::bind(
&Connection::messageBytesRead,
shared_from_this(),
boost::asio::placeholders::error,
boost::asio::placeholders::bytes_transferred
),
boost::bind(
&Connection::handleRead,
shared_from_this(),
boost::asio::placeholders::error
)
);
}
}
//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
std::size_t
Connection::messageBytesRead(const boost::system::error_code& _errorCode,
std::size_t _bytesRead)
{
return MessageBuffer::MESSAGE_LENGTH - _bytesRead;
}
//-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~-~
void
Connection::handleRead(const boost::system::error_code& _errorCode)
{
if (!_errorCode)
{
/// Do something with the populated m_readMessage here.
readMore();
}
else
{
disconnect();
}
}
The messageBytesRead callback will indicate to boost::asio::async_read when a complete message has been read. This snippet was pulled from an existing Connection object from running code, so I know it works...

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.