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

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

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

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

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


person Brian R. Bondy    schedule 15.11.2008    source источник


Ответы (9)


В 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.
person Nicola Bonelli    schedule 15.11.2008
comment
Это было бы отличным решением, но у них нет таких вариантов сокетов. См. Параметры сокета здесь: boost.org/doc/libs /1_37_0/doc/html/boost_asio/reference.html - person Brian R. Bondy; 15.11.2008
comment
Но из того, что я могу сказать, asio read_some () по-прежнему будет внутренне продолжать цикл бесконечно, если он ничего не читает, тем самым отменяя эффект SO_RCVTIMEO, который вы, возможно, установили на собственный сокет. Похоже, что использование select () с socket.native () по-прежнему будет наиболее очевидным способом получить тайм-аут. - person Stéphane; 14.04.2012
comment
@ Стефан. Это единственно возможное решение, и если да, то у вас есть пример? - person leon22; 13.11.2017
comment
@ leon22 Это было 5 лет назад! Я использую ASIO из старой версии Boost. Я бы надеялся, что к настоящему времени ASIO будет исправлено, чтобы разрешить тайм-ауты - они еще не исправили это? Что касается примеров ... вызвать select () в сокете ASIO просто. int rc = select(s.native() + 1, &fds, NULL, NULL, &timeout); - person Stéphane; 13.11.2017

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

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

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

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

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

person William Symionow    schedule 28.12.2012
comment
В Windows io_service.run_one() никогда не блокируется при async_read (по крайней мере, в Boost 1.59.0), что приводит к 100% загрузке процессора. - person rustyx; 17.03.2016
comment
Примеры длинные, потому что документация избыточна в 3 раза. Я не считаю, что это ХОРОШО прокомментировано. - person ChristophK; 23.03.2016

TL;DR

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

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

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

person Pavel Verevkin    schedule 14.08.2018
comment
Я получаю недопустимый аргумент (boost 1.55 в C ++ 98): int recvTimeoutInMS = 1000; const boost::asio::detail::socket_option::integer<SOL_SOCKET, SO_RCVTIMEO> option = boost::asio::detail::socket_option::integer<SOL_SOCKET, SO_RCVTIMEO>(recvTimeoutInMS); socket_.set_option(option); - person Jayen; 13.02.2019
comment
Я думаю, вам нужно исправление в 3-м пункте. Конечно, мы определенно можем использовать std::future в качестве обходного пути, но для этого не всегда требуется async (особенно в boost-asio), вы можете получить std::future, запустив асинхронную операцию с помощью use_future модель: потоки не задействованы. @Pavel Verevkin - person RaGa__M; 02.07.2019
comment
@Explorer_N, снова читая ответ, не помогает мне преобразовать код в c ++ 98 - person Jayen; 09.07.2019
comment
Не имеет ничего общего с версией C ++. - person RaGa__M; 09.07.2019
comment
в решении 3) деструктор std::future заблокирует поток в случае timeout. - person Felix Xu; 08.09.2020
comment
SO_RCVTIMEO и SO_SNDTIMEO в системах POSIX требуют аргумента struct timeval, а не int. Фактически, int будет корректно работать только на Win32. - person rustyx; 10.03.2021

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

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

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

person grepsedawk    schedule 26.11.2008
comment
Этот фрагмент содержит вызов io_service::reset(). Но в документации к нему написано This function must not be called while there are any unfinished calls to the run(), run_one(), poll() or poll_one() functions. - person Gabriel; 14.03.2013
comment
Если этот код должен запускаться в событии обратного вызова asio async, то есть внутри чего-то, вызываемого io_service::run, я подозреваю, что вы получите неопределенное поведение. - person Gabriel; 15.03.2013

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

        ...

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

person Ty Hoffman    schedule 20.04.2011

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

Счастливой асинхронности :)

person denn    schedule 08.01.2012

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

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

  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/6e9a1690bf68971b809be34dfe432949d9a9f727/standalone_example/boost_block_tcp_client_client_client

- update - Пример ссылки обновлен.

person scinart    schedule 11.09.2017

SO_RCVTIMEO и SO_SNDTIMEO принимают структуру timeval из "sys/time.h" вместо int. Таким образом, для варианта 1 @Pavel Verevkin потребуется timeval вместо int, а для варианта 2 потребуется реализовать класс, поскольку _ 8_ сохраняет только одно целочисленное значение.

person dtsull    schedule 22.02.2020

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

person Ana Betts    schedule 15.11.2008
comment
Разве все ваши вызовы сокетов во всех ваших потоках не завершатся с EINTR? Звучит плохо. - person Rhythmic Fistman; 04.11.2009