Как установить таймаут на блокировку сокетов в boost asio? - PullRequest
25 голосов
/ 15 ноября 2008

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

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

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

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

Ответы [ 9 ]

16 голосов
/ 28 декабря 2012

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

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

Превышение времени ожидания операции asio tcp

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

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

9 голосов
/ 26 ноября 2008

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

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

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

8 голосов
/ 20 апреля 2011

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

        ...

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

8 голосов
/ 15 ноября 2008

В 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.
4 голосов
/ 15 августа 2018

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, но это все еще деталь реализации). UGLY!

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 будет использовать барьер памяти.

2 голосов
/ 09 января 2012

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

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

1 голос
/ 11 сентября 2017

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

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

  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

0 голосов
/ 16 сентября 2011

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

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

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

0 голосов
/ 15 ноября 2008

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

...