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

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

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

socket.read_some (boost:: asio:: buffer (pData, maxSize), error_);

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

11 ответов

Решение

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

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

SO_RCVTIMEO и SO_SNDTIMEO

          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.

TL;DR

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

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

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

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

1) Хороший (плохой) старый 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. - (const char*) для значения. В Windows требуется const char *, Posix требует const void*, в C++ const char* автоматически преобразуется в const void*, в то время как обратное неверно.

Преимущества: работает и, вероятно, всегда будет работать, поскольку 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 (который, конечно, основной PITA благодаря очевидному переобработке этой части ASIO) или еще лучше вернуться к 1.

3) Используйте асинхронные / будущие средства C++.

#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). Требует очень тщательного управления временем жизни всех ваших объектов, используемых как асинхронными операциями, так и их обработчиками завершения, что на практике требует, чтобы все классы, содержащие и использующие такие данные в асинхронных операциях, были получены из enable_shared_from_this, что требует, чтобы все такие классы выделялись в куче, что означает (по крайней мере, для коротких операций) эта масштабируемость начнет уменьшаться примерно после 16 потоков, так как каждая куча alloc/dealloc будет использовать барьер памяти.

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

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

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

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

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

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

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

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

У меня был тот же вопрос, и после некоторого исследования самое простое и чистое решение, которое я смог найти, - это получить базовый собственный сокет и делать выборку, пока не найдутся данные для чтения. Выбор будет принимать параметр времени ожидания. Конечно, работа с нативным сокетом начинает идти против того, чтобы использовать 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);

        ...

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

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

Счастливого Асинсинга:)

SO_RCVTIMEO а также SO_SNDTIMEO взять в timeval структура из "sys/time.h" вместо int. Итак, для варианта 1 @Pavel Verevkin потребуетсяtimeval вместо int и вариант 2. потребует реализации класса, поскольку boost::asio::detail::socket_option::integer хранит только одно целое значение.

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

Использование выбора вручную не является хорошим вариантом

  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 это просто мьютекс и условная переменная.
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

Внимание : ИспользованиеSO_RCVTIMEOне всегда может помочь с тайм-аутом при блокировке вызовов. Я столкнулся с проблемой на*nixсистемы с бесконечным блокирующим вызовом вpool(более подробное объяснение см. в SO_RCVTIME и SO_RCVTIMEO, не влияющих на работу Boost.Asio ), когда все работало наWindows. Использование метода non_blocking и соответствующая обработкаerror::would_block(WSAEWOULDBLOCK) иerror::try_again(EAGAIN) мне помогли ошибки.

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

http://www.boost.org/doc/libs/1_47_0/doc/html/thread/synchronization.html

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

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

Другие вопросы по тегам