SO_RCVTIME и SO_RCVTIMEO не влияют на операции Boost.Asio

Ниже мой код

boost::asio::io_service io;
boost::asio::ip::tcp::acceptor::reuse_address option(true);
boost::asio::ip::tcp::acceptor accept(io);
boost::asio::ip::tcp::resolver resolver(io);
boost::asio::ip::tcp::resolver::query query("0.0.0.0", "8080");
boost::asio::ip::tcp::endpoint endpoint = *resolver.resolve(query);
accept.open(endpoint.protocol());
accept.set_option(option);
accept.bind(endpoint);
accept.listen(30);

boost::asio::ip::tcp::socket ps(io);

accept.accept(ps);

struct timeval tv;
tv.tv_sec = 1;
tv.tv_usec = 0;
//setsockopt(ps.native(), SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
setsockopt(ps.native(), SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
char buf[1024];
ps.async_receive(boost::asio::buffer(buf, 1024), boost::bind(fun));
io.run();

Когда я использую Telnet для подключения, но не отправляю данные, он не отключается от тайм-аута Telnet. Что нужно сделать, чтобы установил сетцокопт? Спасибо!

Я изменил SO_RCVTIMEO на SO_SNDTIMEO. Все еще не удается выполнить тайм-аут в указанное время

2 ответа

Решение

С помощью SO_RCVTIMEO а также SO_SNDTIMEO Параметры сокетов с Boost.Asio редко приводят к желаемому поведению. Рассмотрите возможность использования любого из следующих двух шаблонов:

Сложенная операция с async_wait()

Можно составить асинхронную операцию чтения с тайм-аутом, используя таймер Boost.Asio и async_wait() операция с async_receive() операция. Этот подход демонстрируется в примерах тайм-аута Boost.Asio, что-то похожее на:

// Start a timeout for the read.
boost::asio::deadline_timer timer(io_service);
timer.expires_from_now(boost::posix_time::seconds(1));
timer.async_wait(
  [&socket, &timer](const boost::system::error_code& error)
  {
    // On error, such as cancellation, return early.
    if (error) return;

    // Timer has expired, but the read operation's completion handler
    // may have already ran, setting expiration to be in the future.
    if (timer.expires_at() > boost::asio::deadline_timer::traits_type::now())
    {
      return;
    } 

    // The read operation's completion handler has not ran.
    boost::system::error_code ignored_ec;
    socket.close(ignored_ec);
  });

// Start the read operation.
socket.async_receive(buffer,
  [&socket, &timer](const boost::system::error_code& error,
    std::size_t bytes_transferred)
  {
    // Update timeout state to indicate the handler has ran.  This
    // will cancel any pending timeouts.
    timer.expires_at(boost::posix_time::pos_infin);

    // On error, such as cancellation, return early.
    if (error) return;

    // At this point, the read was successful and buffer is populated.
    // However, if the timeout occurred and its completion handler ran first,
    // then the socket is closed (!socket.is_open()).
  });

Имейте в виду, что обе асинхронные операции могут выполняться в одной итерации, что делает оба обработчика завершения готовыми к успешному выполнению. Следовательно, причина, по которой оба обработчика завершения должны обновлять и проверять состояние. Смотрите этот ответ для более подробной информации о том, как управлять состоянием.

использование std::future

Boost.Asio обеспечивает поддержку C++11 фьючерсов. когда boost::asio::use_future предоставляется в качестве обработчика завершения асинхронной операции, инициирующая функция возвращает std::future это будет выполнено после завершения операции. Как std::future поддерживает время ожидания, его можно использовать для тайм-аута операции. Обратите внимание: поскольку вызывающий поток будет заблокирован в ожидании будущего, по крайней мере один другой поток должен обрабатывать io_service чтобы позволить async_receive() операция, чтобы прогрессировать и выполнить обещание:

// Use an asynchronous operation so that it can be cancelled on timeout.
std::future<std::size_t> read_result = socket.async_receive(
   buffer, boost::asio::use_future);

// If timeout occurs, then cancel the read operation.
if (read_result.wait_for(std::chrono::seconds(1)) == 
    std::future_status::timeout)
{
  socket.cancel();
}
// Otherwise, the operation completed (with success or error).
else
{
  // If the operation failed, then read_result.get() will throw a
  // boost::system::system_error.
  auto bytes_transferred = read_result.get();
  // process buffer
}

Зачем SO_RCVTIMEO Не будет работать

Поведение системы

SO_RCVTIMEO В документации отмечается, что этот параметр влияет только на системные вызовы, которые выполняют ввод-вывод сокетов, например read() а также recvmsg(), Это не влияет на демультиплексоры событий, такие как select() а также poll(), который только наблюдает за файловыми дескрипторами, чтобы определить, когда ввод / вывод может происходить без блокировки. Кроме того, когда происходит тайм-аут, вызов ввода-вывода не возвращается -1 и устанавливает errno в EAGAIN или же EWOULDBLOCK,

Укажите время ожидания получения или отправки до сообщения об ошибке. [...] если данные не были переданы и истекло время ожидания, -1 возвращается с errno установленным в EAGAIN или же EWOULDBLOCK [...] Тайм-ауты действуют только для системных вызовов, которые выполняют ввод-вывод сокетов (например, read(), recvmsg(), [...]; таймауты не влияют на select(), poll(), epoll_wait(), и так далее.

Когда базовый дескриптор файла установлен неблокирующим, системные вызовы, выполняющие операции ввода-вывода через сокет, немедленно возвращаются с EAGAIN или же EWOULDBLOCK если ресурсы не доступны сразу. Для неблокирующей розетки, SO_RCVTIMEO не окажет никакого влияния, так как звонок немедленно вернется с успехом или неудачей. Таким образом, для SO_RCVTIMEO чтобы повлиять на системные вызовы ввода / вывода, сокет должен быть заблокирован.

Boost.Asio Поведение

Во-первых, асинхронные операции ввода-вывода в Boost.Asio будут использовать демультиплексор событий, такой как select() или же poll(), Следовательно, SO_RCVTIMEO не повлияет на асинхронные операции.

Далее, сокеты Boost.Asio имеют концепцию двух неблокирующих режимов (оба из которых по умолчанию имеют значение false):

  • native_non_blocking() режим, который примерно соответствует неблокирующему состоянию дескриптора файла. Этот режим влияет на системные вызовы ввода / вывода. Например, если кто-то вызывает socket.native_non_blocking(true), затем recv(socket.native_handle(), ...) может потерпеть неудачу с errno установлен в EAGAIN или же EWOULDBLOCK, Каждый раз, когда в сокете инициируется асинхронная операция, Boost.Asio включит этот режим.
  • non_blocking() режим, который влияет на синхронные операции сокетов Boost.Asio. Когда установлено true Boost.Asio установит базовый дескриптор файла неблокирующим, и синхронные операции с сокетом Boost.Asio могут завершиться с ошибкой boost::asio::error::would_block (или эквивалентная системная ошибка). Когда установлено false Boost.Asio будет блокировать, даже если базовый файловый дескриптор неблокирующий, путем опроса файлового дескриптора и повторной попытки системных операций ввода-вывода, если EAGAIN или же EWOULDBLOCK возвращаются.

Поведение non_blocking() предотвращает SO_RCVTIMEO от получения желаемого поведения. Если предположить, socket.receive() вызывается и данные не доступны и не получены:

  • Если non_blocking() ложно, системный вызов ввода-вывода будет превышен SO_RCVTIMEO, Однако Boost.Asio немедленно заблокирует опрос дескриптора файла, чтобы его можно было прочитать, на что не влияет SO_RCVTIMEO, Окончательный результат - вызывающий абонент заблокирован в socket.receive() до тех пор, пока либо данные не будут получены, либо произойдет сбой, такой как удаленный узел, закрывающий соединение.
  • Если non_blocking() Значение true, тогда базовый файловый дескриптор также не блокируется. Следовательно, системный вызов ввода / вывода будет игнорироваться SO_RCVTIMEO, немедленно вернитесь с EAGAIN или же EWOULDBLOCK, вызывая socket.receive() потерпеть неудачу с boost::asio::error::would_block,

В идеале, для SO_RCVTIMEO чтобы работать с Boost.Asio, нужно native_non_blocking() установить в ложь так, чтобы SO_RCVTIMEO может повлиять, но также non_blocking() установите значение true, чтобы предотвратить опрос дескриптора. Однако Boost.Asio не поддерживает это:

socket::native_non_blocking(bool mode)

Если режим false, но текущая стоимость non_blocking() является true эта функция не работает с boost::asio::error::invalid_argument, так как комбинация не имеет смысла.

Поскольку вы получаете данные, вы можете установить: SO_RCVTIMEO вместо SO_SNDTIMEO

Хотя смешивание наддува и системных вызовов может не дать ожидаемых результатов.

Для справки:

SO_RCVTIMEO

Устанавливает значение тайм-аута, которое указывает максимальное время ожидания функции ввода до ее завершения. Он принимает временную структуру с числом секунд и микросекунд, определяющих ограничение на время ожидания завершения операции ввода. Если операция получения заблокирована в течение этого времени без получения дополнительных данных, она должна вернуться с частичным счетом или значением ошибки, равным [EAGAIN] или же [EWOULDBLOCK] если данные не получены Значение по умолчанию для этой опции равно нулю, что указывает на то, что операция приема не должна иметь тайм-аут. Эта опция требует временной структуры. Обратите внимание, что не все реализации позволяют устанавливать эту опцию.

Однако эта опция влияет только на операции чтения, но не на другие низкоуровневые функции, которые могут ожидать сокет в асинхронной реализации (например, select и epoll), и кажется, что она также не влияет на асинхронные операции asio.

Я нашел пример кода от boost, который может работать для вашего случая здесь.

Упрощенный пример (будет скомпилирован в C++11):

#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <iostream>

void myclose(boost::asio::ip::tcp::socket& ps) { ps.close(); }

int main()
{
  boost::asio::io_service io;
  boost::asio::ip::tcp::acceptor::reuse_address option(true);
  boost::asio::ip::tcp::acceptor accept(io);
  boost::asio::ip::tcp::resolver resolver(io);
  boost::asio::ip::tcp::resolver::query query("0.0.0.0", "8080");
  boost::asio::ip::tcp::endpoint endpoint = *resolver.resolve(query);
  accept.open(endpoint.protocol());
  accept.set_option(option);
  accept.bind(endpoint);
  accept.listen(30);
  boost::asio::ip::tcp::socket ps(io);
  accept.accept(ps);
  char buf[1024];
  boost::asio::deadline_timer timer(io, boost::posix_time::seconds(1));
  timer.async_wait(boost::bind(myclose, boost::ref(ps))); 
  ps.async_receive(boost::asio::buffer(buf, 1024),
           [](const boost::system::error_code& error,
              std::size_t bytes_transferred )
           {
             std::cout << bytes_transferred << std::endl;
           });
  io.run();
  return 0;
}
Другие вопросы по тегам