2016-11-23 11 views
0

Я пишу приложение UDP-сервера с Boost, которое должно прослушивать сокет в течение 5 секунд, и если в течение этих 5 секунд не было получено дейтаграмм, перейдите к выполнению другие вещи.std :: future не работает в режиме приема буфера UDP-асинхронного приема

Вдохновленный some answers Я решил попробовать решение на основе std :: future.

Проблема в том, что вызов wait_for() всегда истекает так, как если бы данные не были получены. Но если я устанавливаю точку останова на строке, которая выполняется после таймаута, и что я проверяю переменные, я вижу, что буфер содержит полученную датаграмму, а объект remote_endpoint содержит адрес клиента. Другими словами, прием сокетов работает так, как ожидалось, но std :: future не срабатывает. Зачем?

Вот мой тестовый код сервера:

#include <future> 
#include <boost/asio.hpp> 
#include <boost/asio/use_future.hpp> 

using boost::asio::ip::udp; 

int main() 
{ 
    try 
    { 
     boost::asio::io_service io_service; 
     udp::socket socket(io_service, udp::endpoint(udp::v4(), 10000)); 
     char recv_buf[8]; 

     for (;;) 
     { 
      ZeroMemory(recv_buf, 8); 
      udp::endpoint remote_endpoint; 
      std::future<std::size_t> recv_length; 

      recv_length = socket.async_receive_from(
       boost::asio::buffer(recv_buf), 
       remote_endpoint, 
       0, 
       boost::asio::use_future); 

      if (recv_length.wait_for(
       std::chrono::seconds(5)) == std::future_status::timeout) 
      { 
       printf("time out. Nothing received.\n"); 
      } 
      else 
      { 
       printf("received something: %s\n", recv_buf); 
      } 
     } 
    } 
    catch (std::exception& e) 
    { 
     printf("Error: %s\n", e.what()); 
    } 
    return 0; 
} 

Я стучал голову на этом некоторое время, так что любая помощь будет оценена. Я нахожусь в Windows 10 с Visual Studio 2015.

Вот мой тестовый клиентский код (в python, извините).

import socket 
import time 

HOST = "server"   # The remote host 
PORT = 10000    # The same port as used by the server 
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s: 
    address = socket.getaddrinfo(HOST, PORT)[0][-1] 

    while True: 
     s.sendto("ping\0", address) 
     time.sleep(1) 
+0

Связанный вопрос и ответ, требующий запуска 'io_service':", поскольку вызывающий поток будет заблокирован в ожидании будущего, по крайней мере один другой поток должен обрабатывать 'io_service', чтобы разрешить асинхронный [... ] для прогресса и выполнения обещания ». –

ответ

0

я нашел решение. Поэтому, чтобы завершить это, вот что нужно сделать. Мой первоначальный код требует 2 модификации.

(1) добавление 2 линии в начале, чтобы запустить отдельный поток с io_service для контроля тайм-аут (как это было предложено Таннера Sansbury)

boost::asio::io_service::work work(io_service); 
std::thread thread([&io_service](){ io_service.run(); }); 

(2) называют socket.cancel(); в состоянии розетки time_out. Если операция сокета не отменена, сокет будет блокироваться, несмотря на возобновленные вызовы на wait_for() (решение получено в списке рассылки Boost).

Вот исправленный код для справки:

#include <future> 
#include <boost/asio.hpp> 
#include <boost/asio/use_future.hpp> 

using boost::asio::ip::udp; 

int main() 
{ 
    try 
    { 
     boost::asio::io_service io_service; 
     boost::asio::io_service::work work(io_service); 
     std::thread thread([&io_service](){ io_service.run(); }); 

     udp::socket socket(io_service, udp::endpoint(udp::v4(), 10000)); 

     char recv_buf[8]; 

     for (;;) 
     { 
      ZeroMemory(recv_buf, 8); 
      udp::endpoint remote_endpoint; 
      std::future<std::size_t> recv_length; 

      recv_length = socket.async_receive_from(
       boost::asio::buffer(recv_buf), 
       remote_endpoint, 
       0, 
       boost::asio::use_future); 

      if (recv_length.wait_for(
       std::chrono::seconds(5)) == std::future_status::timeout) 
      { 
       printf("time out. Nothing received.\n"); 
       socket.cancel(); 
      } 
      else 
      { 
       printf("received something: %s\n", recv_buf); 
      } 
     } 
    } 
    catch (std::exception& e) 
    { 
     printf("Error: %s\n", e.what()); 
    } 
    return 0; 
} 

Спасибо всем за вашу помощь.

3

Вы не вызывая run метод io_service объекта. Поэтому asio не работает. Создайте поток, который вызывает метод run, а затем повторите попытку.

0

Что вы делаете, это сочетание asychronous и синхронных операций, которые не работают:

  • Вы используете операцию асинхронной async_receive_from, который будет работать на ASIO цикл событий (io_service) и закончить, когда что-то было получено. В нормальном случае это вызовет обратный вызов при завершении, если вы дадите ему будущее, оно завершит будущее. Обратите внимание, что это произойдет в потоке, который вызывает io_service.run()
  • Вы используете будущее синхронно. Это заблокирует текущий поток, пока не будет выполнено будущее.
  • Если будущее будет выполнено из той же самой нити, кроме той, которую вы блокируете, чтобы ждать ее, она, очевидно, никогда не будет выполнена.

Возможные шаги для решения этой проблемы:

  • Просто использовать блокирующие операции ASIO с тайм-аута. Они делают именно то, что вы хотите достичь с будущим.
  • Используйте метод фьючерсов then(), чтобы прикрепить к нему продолжение, а не блокировать его. Не будет работать со старыми фьючерсами stdlib, поскольку это расширение C++ 17. Однако форвардные фьючерсы могут это сделать. Вам по-прежнему необходимо вызвать io_service.run() в основном потоке и разделить вашу программу на фазы до и после обратного вызова.
  • Выполнить ASIO и это цикл событий в фоновом потоке, если вам требуется
+0

Не могли бы вы рассказать о том, как использовать блокирующие операции Asio с таймаутами.У меня создалось впечатление, что тайм-ауты требуют асинхронных операций. –

+0

Я догадался, что произошла перегрузка, которая потребовала тайм-аута, но это, похоже, не так. Но есть и другие варианты. Например. вы можете установить параметры таймаута чтения/записи непосредственно в сокете. Тогда будут использоваться операции блокировки. Это описано в принятом ответе здесь: http://stackoverflow.com/questions/291871/how-to-set-a-timeout-on-blocking-sockets-in-boost-asio – Matthias247

+0

Альтернативно, сам по себе показывает подход с поперечной пластинкой для блокировки чтения, которое, однако, использует параметры async и io_service.run_one() под капотом: http://www.boost.org/doc/libs/1_52_0/doc/html/boost_asio/example/timeouts/blocking_tcp_client.cpp Это выглядит нормально, но я бы не использовал его, если у вас также асинхронные операции, выполняемые на одном и том же io_service (потому что тогда обработчики для них могут быть вызваны во время ожидания блокировки). – Matthias247

0

Для асинхронной операции, лежащий в основе ввода/вывода и выполнение обработчика завершения являются дискретными шагами.В этом случае ввод-вывод завершен, но код пользователя никогда не запускает io_service, поэтому обработчик завершения, который установил бы значение recv_length, никогда не будет выполнен. Чтобы решить эту проблему, запустите io_service.


Есть несколько деталей, способствующих наблюдение:

  • когда асинхронная операция инициируется, если он может завершить без блокировки, то он будет делать это, и его обработчик завершения будет поставлен в очередь на io_serviceкак если бы от io_service.post()
  • при использовании boost::asio::use_future, значение по std::future «ы устанавливается в пределах обработчика завершения асинхронной операции по
  • погрузчики, размещенные на io_service вызываются только в потоках, которые в настоящее время Призывая poll(), poll_one(), run() и run_one() функции-члены на io_service

В контексте вопроса, когда

recv_length = socket.async_receive_from(
    boost::asio::buffer(recv_buf), 
    remote_endpoint, 
    0, 
    boost::asio::use_future); 

и данные доступны для чтения (socket.available() > 0), тогда как remote_endpoint, так и recv_buffer будут заполнены правильными данными в течение инициирующего async_receive_from() f соборование. Обработчик завершения, который установит значение recv_length, будет отправлен на номер io_service. Однако, поскольку код не обрабатывает io_service, значение recv_length никогда не устанавливается. Следовательно, recv_length.wait_for() всегда будет иметь статус тайм-аута.


official futures example создает дополнительный поток, который посвящен обработке службы ввода/вывода и ожидает на std::future внутри потока, не обрабатывающим услугу I/O:

// We run the io_service off in its own thread so that it operates 
// completely asynchronously with respect to the rest of the program. 
boost::asio::io_service io_service; 
boost::asio::io_service::work work(io_service); 
std::thread thread([&io_service](){ io_service.run(); }); 

... 

std::future<std::size_t> send_length = 
    socket.async_send_to(..., boost::asio::use_future); 

// Do other things here while the send completes. 

send_length.get(); // Blocks until the send is complete. Throws any errors. 

io_service.stop(); 
thread.join(); 
+0

Очень хорошее объяснение. Я хочу, чтобы это было решением. Я добавил код для создания потока и запустил io_service в начале, а также 'stop()' и 'join()' в конце, но поведение по-прежнему остается таким же, как описано ранее. – jeancf

+0

@jeancf Вы добавили объект 'work'? –

+0

Да, да. Я также пытался использовать 'recv_length.get()' и 'recv_length.wait()', и оба работают как ожидалось: блокируйте до получения данных. это только 'wait_for()', что неправильно. – jeancf

 Смежные вопросы

  • Нет связанных вопросов^_^