Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

asio::read with timeout

I need to know how to read (sync or async doesn't matters) with a timeout. I want to check if a device is connected with a serial port or not.

For that I use asio::write and then I wait for the response of the device.

If a device is connected asio::read(serial, boost::asio::buffer(&r,1)) works fine but if there is no device the program stops, which is is why I need the timeout

I know that I need a deadline_timer but I have no idea how to use it in the async_read function.

An example of how it works would be really helpful.

I know that there are many similar threads and I read lot of them but I can't find a solution that helps me solving my problem!

like image 276
Chris K. Avatar asked Oct 29 '12 17:10

Chris K.


3 Answers

The code posted by Igor R. did not compile for me. Here is my improved version of his code, which works perfectly. It uses lambdas to get rid of the set_result helper function.

template <typename SyncReadStream, typename MutableBufferSequence>
void readWithTimeout(SyncReadStream& s, const MutableBufferSequence& buffers, const boost::asio::deadline_timer::duration_type& expiry_time)
{
    boost::optional<boost::system::error_code> timer_result;
    boost::asio::deadline_timer timer(s.get_io_service());
    timer.expires_from_now(expiry_time);
    timer.async_wait([&timer_result] (const boost::system::error_code& error) { timer_result.reset(error); });

    boost::optional<boost::system::error_code> read_result;
    boost::asio::async_read(s, buffers, [&read_result] (const boost::system::error_code& error, size_t) { read_result.reset(error); });

    s.get_io_service().reset();
    while (s.get_io_service().run_one())
    { 
        if (read_result)
            timer.cancel();
        else if (timer_result)
            s.cancel();
    }

    if (*read_result)
        throw boost::system::system_error(*read_result);
}
like image 121
Robert Hegner Avatar answered Nov 03 '22 08:11

Robert Hegner


Once upon a time, the library author proposed the following way to read synchronously with timeout (this example involves tcp::socket, but you can use serial port instead):

  void set_result(optional<error_code>* a, error_code b) 
  { 
    a->reset(b); 
  } 


  template <typename MutableBufferSequence> 
  void read_with_timeout(tcp::socket& sock, 
      const MutableBufferSequence& buffers) 
  { 
    optional<error_code> timer_result; 
    deadline_timer timer(sock.io_service()); 
    timer.expires_from_now(seconds(1)); 
    timer.async_wait(boost::bind(set_result, &timer_result, _1)); 


    optional<error_code> read_result; 
    async_read(sock, buffers, 
        boost::bind(set_result, &read_result, _1)); 

    sock.io_service().reset(); 
    while (sock.io_service().run_one()) 
    { 
      if (read_result) 
        timer.cancel(); 
      else if (timer_result) 
        sock.cancel(); 
    } 


    if (*read_result) 
      throw system_error(*read_result); 
  } 
like image 7
Igor R. Avatar answered Nov 03 '22 09:11

Igor R.


You don't use deadline_timer in async_read. But you can start two async processes:

  1. An async_read process on serial port. boost::asio::serial_port has a cancel method that cancels all async operations on it.
  2. A deadline timer with required timeout. In completion handler for deadline_timer you can cancel the serial port. This should close the async_read operation and call its completion handler with an error.

Code:

#include <iostream>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>
#include <boost/array.hpp>

class timed_connection
{
    public:
        timed_connection( int timeout ) :
            timer_( io_service_, boost::posix_time::seconds( timeout ) ),
            serial_port_( io_service_ )
        {
        }

        void start()
        {
              timer_.async_wait
                (
                 boost::bind
                 (
                  &timed_connection::stop, this
                 )
                );

            // Connect socket
            // Write to socket

            // async read from serial port
            boost::asio::async_read
                (
                 serial_port_, boost::asio::buffer( buffer_ ),
                 boost::bind
                 (
                  &timed_connection::handle_read, this,
                  boost::asio::placeholders::error
                 )
                );

            io_service_.run();
        }

    private:
        void stop()
        {  
            serial_port_.cancel();
        }

        void handle_read ( const boost::system::error_code& ec)
        {  
            if( ec )
            {  
                // handle error
            }
            else
            {  
                // do something
            }
        }

    private:
        boost::asio::io_service io_service_;
        boost::asio::deadline_timer timer_;
        boost::asio::serial_port serial_port_;
        boost::array< char, 8192 > buffer_;
};

int main()
{
    timed_connection conn( 5 );
    conn.start();

    return 0;
}
like image 6
Vikas Avatar answered Nov 03 '22 10:11

Vikas