Как асинхронно читать входные данные из командной строки, используя boost asio в Windows? - PullRequest
8 голосов
/ 22 октября 2011

Я нашел этот вопрос , который спрашивает, как читать ввод асинхронно, но будет работать только с дескрипторами потока POSIX, которые не будут работать в Windows.Итак, я нашел этот урок , который показывает, что вместо использования потокового дескриптора POSIX я могу использовать boost::asio::windows::stream_handle.

. Следуя обоим примерам, я придумал приведенный ниже код.Когда я запускаю его, я ничего не могу набрать в командной строке, так как программа немедленно завершает работу.Я бы хотел, чтобы он захватывал любые входные данные от пользователя, возможно, в std::string, позволяя при этом выполнять другую логику в моей программе (т.е. выполнять асинхронный ввод-вывод из консоли Windows).

По существу,Я пытаюсь избежать блокировки моей программы, когда она пытается прочитать из stdin.Я не знаю, возможно ли это в Windows, поскольку я также обнаружил этот пост , в котором подробно описываются проблемы, с которыми столкнулся другой пользователь при попытке сделать то же самое.

#define _WIN32_WINNT 0x0501
#define INPUT_BUFFER_LENGTH 512

#include <cstdio>
#include <iostream>

#define BOOST_THREAD_USE_LIB // For MinGW 4.5 - (https://svn.boost.org/trac/boost/ticket/4878)
#include <boost/bind.hpp>
#include <boost/asio.hpp>

class Example {
    public:
        Example( boost::asio::io_service& io_service)
            : input_buffer( INPUT_BUFFER_LENGTH), input_handle( io_service)
        {
            // Read a line of input.
            boost::asio::async_read_until( input_handle, input_buffer, "\r\n",
                boost::bind( &Example::handle_read, this,
                    boost::asio::placeholders::error,
                    boost::asio::placeholders::bytes_transferred));
        }
        void handle_read( const boost::system::error_code& error, std::size_t length);
        void handle_write( const boost::system::error_code& error);
    private:
        boost::asio::streambuf input_buffer;
        boost::asio::windows::stream_handle input_handle;
};

void Example::handle_read( const boost::system::error_code& error, std::size_t length)
{
    if (!error)
    {
        // Remove newline from input.
        input_buffer.consume(1);
        input_buffer.commit( length - 1);

        std::istream is(&input_buffer);
        std::string s;
        is >> s;

        std::cout << s << std::endl;

        boost::asio::async_read_until(input_handle, input_buffer, "\r\n",
           boost::bind( &Example::handle_read, this,
               boost::asio::placeholders::error,
               boost::asio::placeholders::bytes_transferred));
    }
    else if( error == boost::asio::error::not_found)
    {
        std::cout << "Did not receive ending character!" << std::endl;
    }
}

void Example::handle_write( const boost::system::error_code& error)
{
    if (!error)
    {
        // Read a line of input.
        boost::asio::async_read_until(input_handle, input_buffer, "\r\n",
           boost::bind( &Example::handle_read, this,
               boost::asio::placeholders::error,
               boost::asio::placeholders::bytes_transferred));
    }
}

int main( int argc, char ** argv)
{
    try {
        boost::asio::io_service io_service;
        Example obj( io_service);
        io_service.run();
    } catch( std::exception & e)
    {
        std::cout << e.what() << std::endl;
    }
    std::cout << "Program has ended" << std::endl;
    getchar();
    return 0;
}

Ответы [ 3 ]

5 голосов
/ 22 октября 2011

Вам необходимо вызвать io_service::run() в start цикл обработки событий для асинхронных операций.

class Example {
    public:
        Example( boost::asio::io_service& io_service )
            : io_service(io_service), input_buffer( INPUT_BUFFER_LENGTH), input_handle( io_service)
        {
        }
        void start_reading();
        void handle_read( const boost::system::error_code& error, std::size_t length);
        void handle_write( const boost::system::error_code& error);
    private:
        boost::asio::io_service& io_service;
        boost::asio::streambuf input_buffer;
        boost::asio::windows::stream_handle input_handle;
};

int main( int argc, char * argv)
{
    boost::asio::io_service io_service;
    Example obj( io_service );
    obj.start_reading();

    io_service.run();

    return 0;
}
5 голосов
/ 24 сентября 2015

Я потратил час или два на изучение этой темы, поэтому решил опубликовать, чтобы другие не тратили впустую свое время.

Windows не поддерживает IOCP для стандартных дескрипторов ввода / вывода.Когда вы берете дескриптор GetStdHandle(STD_INPUT_HANDLE), дескриптор не имеет установленного FILE_FLAG_OVERLAPPED, поэтому он не поддерживает перекрывающийся (асинхронный) ввод-вывод.Но даже если вы

CreateFile(L"CONIN$",
    GENERIC_READ,
    FILE_SHARE_READ,
    NULL,
    OPEN_EXISTING,
    FILE_FLAG_OVERLAPPED | FILE_FLAG_NO_BUFFERING,
    NULL);

WinAPI просто игнорируете dwFlagsAndAttributes и снова возвращаете дескриптор, который не поддерживает перекрывающийся ввод-вывод.Единственный способ получить асинхронный ввод-вывод для консольного ввода / вывода - это использовать дескриптор с WaitForSingleObject с таймаутом 0, чтобы вы могли проверить, есть ли что-нибудь для чтения без блокировки.Не совсем асинхронный ввод-вывод, но можно избежать многопоточности, если это цель.

Подробнее об API консоли: https://msdn.microsoft.com/en-us/library/ms686971(v=VS.85).aspx

В чем разница между дескрипторами, возвращаемыми GetStdHandle и CreateFile, описана здесь: https://msdn.microsoft.com/en-us/library/windows/desktop/ms682075(v=vs.85).aspx. Короче говоря, разницатолько для дочерних процессов, когда CreateFile может предоставить доступ к своему буферу ввода консоли, даже если он был перенаправлен в родительский процесс.

3 голосов
/ 22 октября 2011

Вам необходимо инициализировать дескриптор stream_handle для дескриптора ввода консоли.Вы не можете использовать один и тот же дескриптор stream_handle для ввода и вывода, поскольку это два разных дескриптора.

Для ввода:

    Example()
        : /* ... */ input_handle( io_service, GetStdHandle(STD_INPUT_HANDLE) )

Для вывода вы должны использовать CONSOLE_OUTPUT_HANDLE.Но это, вероятно, излишне, вы вряд ли будете помещать столько данных в стандартный вывод в окнах, которые вам понадобятся для асинхронной записи.

...