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