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