Как установить тайм-аут для блокировки сокетов в boost asio?

StackOverflow https://stackoverflow.com/questions/291871

  •  08-07-2019
  •  | 
  •  

Вопрос

Есть ли способ отменить ожидающую операцию (без отключения) или установить тайм-аут для функций библиотеки boost?

То есть.Я хочу установить тайм-аут для блокировки сокета в boost asio?

сокет.read_some(boost::asio::buffer(pData, maxSize), ошибка_);

Пример:Я хочу прочитать некоторые данные из сокета, но я хочу выдать сообщение об ошибке, если прошло 10 секунд.

Это было полезно?

Решение

В Linux / BSD тайм-аут операций ввода-вывода на сокетах напрямую поддерживается операционной системой.Эта опция может быть включена через setsocktopt().Я не знаю, если boost::asio предоставляет метод для его настройки или предоставляет скриптор сокета, позволяющий вам устанавливать его напрямую - последний случай на самом деле не переносим.

Для полноты картины приведем описание со справочной страницы:

SO_RCVTIMEO СО_RCVTIMEO и ТАК_СНДТИМЕО

          Specify the receiving or sending  timeouts  until  reporting  an
          error.  The argument is a struct timeval.  If an input or output
          function blocks for this period of time, and data has been  sent
          or  received,  the  return  value  of  that function will be the
          amount of data transferred; if no data has been transferred  and
          the  timeout has been reached then -1 is returned with errno set
          to EAGAIN or EWOULDBLOCK just as if the socket was specified  to
          be  non-blocking.   If  the timeout is set to zero (the default)
          then the operation  will  never  timeout.   Timeouts  only  have
          effect  for system calls that perform socket I/O (e.g., read(2),
          recvmsg(2), send(2), sendmsg(2)); timeouts have  no  effect  for
          select(2), poll(2), epoll_wait(2), etc.

Другие советы

Когда был задан этот вопрос, я предполагаю, что у ASIO не было никакого примера о том, как выполнить то, что нужно OP, то есть отключить операцию блокировки, такую как операция блокировки сокета.Теперь существуют примеры, которые точно покажут вам, как это сделать.пример кажется длинным, но это потому, что он ХОРОШО прокомментирован.В нем показано, как использовать ioservice в режиме "one shot".

Я думаю, что этот пример - отличное решение.Другие решения здесь нарушают переносимость и не используют преимущества ioservice.если переносимость не важна, а ioservice, похоже, требует больших накладных расходов - ТОГДА - вам не следует использовать ASIO.Несмотря ни на что, у вас будет создан ioservice (от него зависит почти вся функциональность ASIO, даже синхронизация сокетов), так что воспользуйтесь этим.

Тайм-аут блокирующей операции asio tcp

Тайм-аут блокирующей asio udp операции

Документация ASIO была обновлена, поэтому ознакомьтесь с ней, чтобы найти новые примеры того, как преодолеть некоторые "подводные камни", с которыми сталкивается ASIO.

Вы могли бы выполнить async_read, а также установить таймер на желаемый тайм-аут.Затем, если сработает таймер, вызовите cancel для вашего объекта socket.В противном случае, если ваше чтение произойдет, вы можете отменить свой таймер.Конечно, для этого требуется, чтобы вы использовали объект io_service.

Редактировать:Нашел для вас фрагмент кода, который делает это

http://lists.boost.org/Archives/boost/2007/04/120339.php

У меня был тот же вопрос, и после некоторых исследований самое простое и понятное решение, которое я мог придумать, состояло в том, чтобы получить базовый собственный сокет и выполнять select до тех пор, пока не появятся данные для чтения.Выбор будет принимать параметр тайм-аута.Конечно, работа с собственным сокетом начинает идти вразрез с целью использования asio в первую очередь, но опять же, это, кажется, самый чистый способ.Насколько я мог судить, asio не предоставляет простого способа сделать это для синхронного использования.Код:

        // socket here is:  boost::shared_ptr<boost::asio::ip::tcp::socket> a_socket_ptr

        // Set up a timed select call, so we can handle timeout cases.

        fd_set fileDescriptorSet;
        struct timeval timeStruct;

        // set the timeout to 30 seconds
        timeStruct.tv_sec = 30;
        timeStruct.tv_usec = 0;
        FD_ZERO(&fileDescriptorSet);

        // We'll need to get the underlying native socket for this select call, in order
        // to add a simple timeout on the read:

        int nativeSocket = a_socket_ptr->native();

        FD_SET(nativeSocket,&fileDescriptorSet);

        select(nativeSocket+1,&fileDescriptorSet,NULL,NULL,&timeStruct);

        if(!FD_ISSET(nativeSocket,&fileDescriptorSet)){ // timeout

                std::string sMsg("TIMEOUT on read client data. Client IP: ");

                sMsg.append(a_socket_ptr->remote_endpoint().address().to_string());

                throw MyException(sMsg);
        }

        // now we know there's something to read, so read
        boost::system::error_code error;
        size_t iBytesRead = a_socket_ptr->read_some(boost::asio::buffer(myVector), error);

        ...

Возможно, это будет полезно в вашей ситуации.

TL; DR

socket.set_option(boost::asio::detail::socket_option::integer<SOL_SOCKET, SO_RCVTIMEO>{ 200 });

ПОЛНЫЙ ОТВЕТ Этот вопрос задают снова и снова на протяжении многих лет.Ответы, которые я видел до сих пор, довольно скудны.Я добавлю эту информацию прямо здесь, в одном из первых появлений этого вопроса.

Все, кто пытается использовать ASIO для упрощения своего сетевого кода, были бы совершенно счастливы, если бы автор просто добавил необязательный параметр timeout ко всем функциям синхронизации и асинхронного ввода-вывода.К сожалению, это вряд ли произойдет (по моему скромному мнению, просто по идеологическим соображениям, в конце концов, КАК и в ASIO, неспроста).

Итак, вот доступные на данный момент способы освежевать эту бедную кошку, но ни один из них не особенно аппетитный.Допустим, нам нужен тайм-аут в 200 мс.

1) Хороший (плохой) старый socket API:

const int timeout = 200;
::setsockopt(socket.native_handle(), SOL_SOCKET, SO_RCVTIMEO, (const char *)&timeout, sizeof timeout);//SO_SNDTIMEO for send ops

Пожалуйста, обратите внимание на эти особенности:- const int для таймаута - в Windows требуемый тип на самом деле DWORD, но, к счастью, текущий набор компиляторов имеет тот же самый, поэтому const int будет работать как в Win, так и в Posix world.- (const char*) для значения.В Windows требуется const char *, для Posix требуется const void *, в C ++ const char * автоматически преобразуется в const void *, в то время как обратное неверно.

Преимущества:работает и, вероятно, всегда будет работать, поскольку socket API старый и стабильный.Достаточно просто.Быстро.Недостатки:технически могут потребоваться соответствующие заголовочные файлы (разные для Win и даже разных версий UNIX) для setsockopt и макросов, но текущая реализация ASIO в любом случае загрязняет ими глобальное пространство имен.Требуется переменная для тайм-аута.Не типобезопасен.В Windows для работы требуется, чтобы сокет находился в режиме перекрытия (который, к счастью, использует текущая реализация ASIO, но это все еще деталь реализации).УРОДЛИВЫЙ!

2) Пользовательский вариант сокета ASIO:

typedef boost::asio::detail::socket_option::integer<SOL_SOCKET, SO_RCVTIMEO> rcv_timeout_option; //somewhere in your headers to be used everywhere you need it
//...
socket.set_option(rcv_timeout_option{ 200 });

Преимущества:Достаточно просто.Быстро.Красивый (с typedef).Недостатки:Зависит от деталей реализации ASIO, которые могут измениться (но OTOH все рано или поздно изменится, и такие детали с меньшей вероятностью изменятся, чем общедоступные API, подлежащие стандартизации).Но в случае, если это произойдет, вам придется либо написать класс в соответствии с https://www.boost.org/doc/libs/1_68_0/doc/html/boost_asio/reference/SettableSocketOption.html (что, конечно, является серьезной проблемой благодаря очевидной чрезмерной разработке этой части ASIO) или, еще лучше, вернитесь к 1.

3) Используйте средства C ++ async / future.

#include <future>
#include <chrono>
//...
auto status = std::async(std::launch::async, [&] (){ /*your stream ops*/ })
    .wait_for(std::chrono::milliseconds{ 200 });
switch (status)
    {
    case std::future_status::deferred:
    //... should never happen with std::launch::async
        break;
    case std::future_status::ready:
    //...
        break;
    case std::future_status::timeout:
    //...
        break;
    }

Преимущества:стандартный.Недостатки:всегда запускает новый поток (на практике), который относительно медленный (может быть достаточно хорош для клиентов, но приведет к уязвимости DoS для серверов, поскольку потоки и сокеты являются "дорогими" ресурсами).Не пытайтесь использовать std::launch::deferred вместо std::launch::async, чтобы избежать запуска нового потока, поскольку wait_for всегда будет возвращать future_status::deferred без попытки запуска кода.

4) Метод, предписанный ASIO - используйте только асинхронные операции (что на самом деле не является ответом на вопрос).

Преимущества:достаточно хорош и для серверов, если не требуется огромная масштабируемость для коротких транзакций.Недостатки:довольно многословно (поэтому я даже не буду приводить примеры - см. ASIO examples).Требует очень тщательного управления временем жизни всех ваших объектов, используемых как асинхронными операциями, так и их обработчиками завершения, что на практике требует, чтобы все классы, содержащие и использующие такие данные в асинхронных операциях, были производными от enable_shared_from_this , для чего требуются все такие классы, размещенные в куче, что означает (по крайней мере, для коротких операций), что масштабируемость начнет снижаться примерно через 16 потоков, поскольку каждое выделение / освобождение кучи будет использовать барьер памяти.

Следуя тому, о чем упомянул grepsedawk.Есть несколько примеров, показывающих, как отменить длительные асинхронные операции по истечении определенного периода времени, в разделе Тайм-ауты раздел в asio doco. Примеры Boost Asio . Асинхронный TCP-клиент помог мне больше всего.

Счастливого Асинхронизации :)

Даже спустя годы после первоначального вопроса по-прежнему нет удовлетворительного ответа.

Ручное использование select не является хорошим вариантом

  1. номер дескриптора файла должен быть меньше 1024
  2. FD может быть ложно сообщен как готовый из-за неправильной контрольной суммы.

Звонить io_service.run_one() это также плохая идея, потому что могут быть другие параметры асинхронности, для которых требуется, чтобы io_service всегда run().А документ boost о блокировке tcp-клиента труден для понимания.

Итак, вот мое решение.Ключевая идея заключается в следующем:

{
    Semaphore r_sem;
    boost::system::error_code r_ec;
    boost::asio::async_read(s,buffer,
                            [this, &r_ec, &r_sem](const boost::system::error_code& ec_, size_t) {
                                r_ec=ec_;
                                r_sem.notify();
                            });
    if(!r_sem.wait_for(std::chrono::seconds(3))) // wait for 3 seconds
    {
        s.cancel();
        r_sem.wait();
        throw boost::system::system_error(boost::asio::error::try_again);
    }
    else if(r_ec)
        throw boost::system::system_error(r_ec);
}

Здесь Semaphore это просто мьютекс и condition_variable .
wait_for реализуется с помощью http://en.cppreference.com/w/cpp/thread/condition_variable/wait_for

Полный код находится по адресу https://github.com/scinart/cpplib/blob/master/include/asio.hpp
Примеры находятся в https://github.com/scinart/cpplib/blob/master/test/test_asio.cpp
Лучший пример в https://github.com/scinart/cpplib/blob/master/test/test_SyncBoostIO.cpp

Вы можете обернуть синхронные вызовы во фьючерсы и дождаться их завершения с таймаутом (wait_timeout).

http://www.boost.org/doc/libs/1_47_0/doc/html/thread/synchronization.html#thread.synchronization .будущее

Конечно, не один размер подходит всем, но хорошо подходит, например, дляобход тайм-аутов медленного подключения.

В * nix вы бы использовали alarm(), чтобы ваш вызов сокета завершился неудачей с EINTR

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top