Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

asio service handler for stdin keypress

I have adapted step 3 of the Boost asio tutorial to run forever, and display "tick" and "tock" once per second instead of the counter:

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

void print(const boost::system::error_code& /*e*/,
    boost::asio::deadline_timer* t, int* count)
{
    if( !((*count) % 2) )
        std::cout << "tick\n";
    else
        std::cout << "tock\n";

    ++(*count);

    t->expires_at(t->expires_at() + boost::posix_time::seconds(1));
    t->async_wait(boost::bind(print,
          boost::asio::placeholders::error, t, count));
}

int main()
{
  boost::asio::io_service io;

  int count = 0;
  boost::asio::deadline_timer t(io, boost::posix_time::seconds(1));
  t.async_wait(boost::bind(print,
        boost::asio::placeholders::error, &t, &count));

  io.run();

  std::cout << "Final count is " << count << "\n";

  return 0;
}

Now I want to asynchronously be able to handle a keypress on stdin. Is there an io_service handler I can use to respond to keypresses, without blocking sleeps or waits?

For example, I'd like to be able to implement a handler function similar to:

void handle_keypress(const boost::error_code&,
    char c)
{
    std::cout << "Tap '" << c << "'\n";
}

And I would expect my invocation of this handler to be something along the lines of:

  char c = 0;
  boost::asio::stdin_receiver sr(io);
  st.async_wait(boost::bind(handle_keypress, boost::asio::placeholders::error, &c));

  io.run();

Is this something I can do with asio, either by using a builtin service handler, or writing my own?

EDIT, ELABORATION:

I have seen this question, but the linked-to code in the accpeted answer simply does this in main:

 while (std::cin.getline(

The application I'm writing isn't this simple tick-tock-tap gizmo I've outlined above, but will be a multicast server. Several worker threads will be sending packets to multicast groups, responding to messages from the main thread, and sending messages back to the main thread. The application, in turn, will be "driven" by input from the stdin -- for example, when the user presses the "P" key, multicast broadcast will be paused, and when the hit "Q" the whole thing will shut down. In the main thread, all I'll do in response to these inputs is send messages to the worker threads.

The while loop above won't work in my scenario because while it's waiting for stdin input from the user, the main thread will not be able to process messages coming in from the worker threads. Some of those messages from the worker thread will generate output to stdout.

like image 265
John Dibling Avatar asked Jun 07 '12 15:06

John Dibling


2 Answers

I don't believe the posix chat client uses a while loop or invokes std::getline, which is the sample code you linked to in my previous answer. Perhaps you are referring to another example? In any case, you don't need to use io_service::dispatch or even a separate thread. The built-in facilities of a stream descriptor work just fine here. See my previous answer to a similar question: Use a posix::stream_descriptor and assign STDIN_FILENO to it. Use async_read and handle the requests in the read handlers.

I've modified your sample code with one way to accomplish this

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

void print(const boost::system::error_code& /*e*/,
    boost::asio::deadline_timer* t, int* count)
{
    if( !((*count) % 2) )
        std::cout << "tick\n";
    else
        std::cout << "tock\n";

    ++(*count);

    t->expires_at(t->expires_at() + boost::posix_time::seconds(1));
    t->async_wait(boost::bind(print,
          boost::asio::placeholders::error, t, count));
}

class Input : public boost::enable_shared_from_this<Input>
{
public:
    typedef boost::shared_ptr<Input> Ptr;

public:
    static void create(
            boost::asio::io_service& io_service
            )
    {
        Ptr input(
                new Input( io_service )
                );
        input->read();
    }

private:
    explicit Input(
            boost::asio::io_service& io_service
         ) :
        _input( io_service )
    {
        _input.assign( STDIN_FILENO );
    }

    void read()
    {
        boost::asio::async_read(
                _input,
                boost::asio::buffer( &_command, sizeof(_command) ),
                boost::bind(
                    &Input::read_handler,
                    shared_from_this(),
                    boost::asio::placeholders::error,
                    boost::asio::placeholders::bytes_transferred
                    )
                );
    }

    void read_handler(
            const boost::system::error_code& error,
            const size_t bytes_transferred
            )
    {
        if ( error ) {
            std::cerr << "read error: " << boost::system::system_error(error).what() << std::endl;
            return;
        }

        if ( _command != '\n' ) {
            std::cout << "command: " << _command << std::endl;
        }

        this->read();
    }

private:
    boost::asio::posix::stream_descriptor _input;
    char _command;
};

int main()
{
  boost::asio::io_service io;

  int count = 0;
  boost::asio::deadline_timer t(io, boost::posix_time::seconds(1));
  t.async_wait(boost::bind(print,
        boost::asio::placeholders::error, &t, &count));

  Input::create( io);

  io.run();

  std::cout << "Final count is " << count << "\n";

  return 0;
}

compile, link, and run

samm:stackoverflow samm$ g++ -I /opt/local/include stdin.cc -L /opt/local/lib -lboost_system -Wl,-rpath,/opt/local/lib
samm:stackoverflow samm$ echo "hello world" | ./a.out
command: h
command: e
command: l
command: l
command: o
command:  
command: w
command: o
command: r
command: l
command: d
read error: End of file
tick
tock
tick
tock
tick
tock
tick
tock
tick
tock
^C
samm:stackoverflow samm$
like image 88
Sam Miller Avatar answered Nov 05 '22 10:11

Sam Miller


There is always the option of handling stdin in a separate thread and posting any keypresses to your main event loop via io_service::dispatch

This function is used to ask the io_service to execute the given handler.

The io_service guarantees that the handler will only be called in a thread in which the run(), run_one(), poll() or poll_one() member functions is currently being invoked.

like image 44
cmeerw Avatar answered Nov 05 '22 11:11

cmeerw