Как выполнить неблокирующее чтение с помощью asio?
-
19-08-2019 - |
Вопрос
Я пытаюсь использовать boost :: asio для чтения и записи с устройства через последовательный порт. Оба блока boost :: asio: read () и boost :: asio :: serial_port :: read_some (), когда читать нечего. Вместо этого я хотел бы обнаружить это условие и записать команду в порт для запуска устройства.
Как я могу обнаружить, что данные недоступны?
Если необходимо, я могу делать все асинхронно, я бы предпочел избежать дополнительной сложности, если смогу.
Решение
На самом деле у вас есть пара вариантов. Вы можете использовать встроенную функцию async_read_some
последовательного порта или автономную функцию boost::asio::async_read
(или deadline_timer
).
Вы все равно столкнетесь с ситуацией, когда вы фактически & заблокированы " поскольку ни один из них не вызовет обратный вызов, если (1) данные не были прочитаны или (2) произошла ошибка , Чтобы обойти это, вы можете использовать объект read_some
для установки времени ожидания. Если тайм-аут срабатывает первым, данные недоступны. В противном случае вы будете читать данные.
Сложность не так уж и плоха. Вы получите два обратных вызова с похожим поведением. Если либо & Quot; прочитайте & Quot; или " timeout " обратный вызов срабатывает с ошибкой, вы знаете, что это проигравший гонки. Если один из них стреляет без ошибки, то вы знаете, что он победитель гонки (и вам следует отменить другой колл). В том месте, где у вас был бы блокирующий звонок на io_svc.run()
, теперь вы будете звонить на run
. Ваша функция будет по-прежнему блокироваться, как и раньше, когда она вызывает run()
, но на этот раз вы контролируете продолжительность.
Вот пример:
void foo()
{
io_service io_svc;
serial_port ser_port(io_svc, "your string here");
deadline_timer timeout(io_svc);
unsigned char my_buffer[1];
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::milliseconds(<<your_timeout_here>>));
timeout.async_wait(boost::bind(&wait_callback, boost::ref(ser_port),
boost::asio::placeholders::error));
io_svc.run(); // will block until async callbacks are finished
if (!data_available)
{
kick_start_the_device();
}
}
void read_callback(bool& data_available, 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(); // will cause wait_callback to fire with an error
data_available = true;
}
void wait_callback(serial_port& ser_port, const boost::system::error_code& error)
{
if (error)
{
// Data was read and this timeout was canceled
return;
}
ser_port.cancel(); // will cause read_callback to fire with an error
}
Это должно помочь вам начать с нескольких настроек здесь и там, чтобы удовлетворить ваши конкретные потребности. Надеюсь, это поможет!
Еще одно примечание: для обработки обратных вызовов не требуется никаких дополнительных потоков. Все обрабатывается в вызове на <=>. Не уверен, что вы уже знали об этом ...
Другие советы
На самом деле это намного проще, чем подразумевали ответы, и вы можете сделать это синхронно:
Предположим, что ваше блокирующее чтение было примерно таким:
size_t len = socket.receive_from(boost::asio::buffer(recv_buf), sender_endpoint);
Затем вы замените его на
socket.non_blocking(true);
size_t len = 0;
error = boost::asio::error::would_block;
while (error == boost::asio::error::would_block)
//do other things here like go and make coffee
len = socket.receive_from(boost::asio::buffer(recv_buf), sender_endpoint, 0, error);
std::cout.write(recv_buf.data(), len);
Вы используете альтернативную перегруженную форму receive_from, которую имеют почти все методы send / receive. К сожалению, они принимают аргумент flags, но 0, кажется, работает нормально.
Вы должны использовать свободную функцию asio :: async_read.