SO_RCVTIME y SO_RCVTIMEO no afectan las operaciones de Boost.Asio

A continuación está mi código

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(); 

Cuando uso Telnet para conectarme, pero no para enviar datos, no me desconecto de un tiempo de espera de Telnet. ¿Tendré que hacer esto para que setsockopt se active? ¡Gracias!

Modifiqué SO_RCVTIMEO a SO_SNDTIMEO. Todavía no puede agotar el tiempo de espera en el tiempo especificado

Usar las opciones de socket SO_RCVTIMEO y SO_SNDTIMEO con Boost.Asio raramente producirá el comportamiento deseado. Considere usar cualquiera de los siguientes dos patrones:

Operación compuesta con async_wait()

Uno puede componer una operación de lectura asíncrona con tiempo de espera utilizando un temporizador Boost.Asio y una operación async_wait() con una operación async_receive() . Este enfoque se demuestra en los ejemplos de tiempo de espera de Boost.Asio, algo similar a:

 // 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()). }); 

Tenga en cuenta que es posible que ambas operaciones asincrónicas se completen en la misma iteración, lo que hace que ambos controladores de finalización estén listos para ejecutarse con éxito. Por lo tanto, la razón por la cual ambos controladores de finalización necesitan actualizar y verificar el estado. Consulte esta respuesta para obtener más detalles sobre cómo administrar el estado.

Usar std::future

Boost.Asio’s brinda soporte para C ++ 11 futuros . Cuando boost::asio::use_future se proporciona como el controlador de finalización de una operación asincrónica, la función de inicio devolverá un std::future que se cumplirá una vez que la operación finalice. Como std::future admite esperas temporizadas, se puede aprovechar para agotar el tiempo de una operación. Tenga en cuenta que como el hilo que llama se bloqueará esperando por el futuro, al menos otro hilo debe procesar el io_service para permitir que la operación async_receive() progrese y cumpla la promesa:

 // Use an asynchronous operation so that it can be cancelled on timeout. std::future 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 } 

Por qué SO_RCVTIMEO no funcionará

Comportamiento del sistema

La documentación de SO_RCVTIMEO indica que la opción solo afecta a las llamadas al sistema que realizan E / S de socket, como read() y recvmsg() . No afecta a los demultiplexores de eventos, como select() y poll() , que solo miran los descriptores de archivos para determinar cuándo se pueden producir E / S sin bloquear. Además, cuando se produce un tiempo de espera, la llamada de E / S no devuelve -1 y establece errno en EAGAIN o EWOULDBLOCK .

Especifique los tiempos de espera de recepción o envío hasta informar un error. […] si no se han transferido datos y se ha alcanzado el tiempo de espera, se devuelve -1 con errno configurado en EAGAIN o EWOULDBLOCK […] Los tiempos de espera solo tienen efecto para las llamadas al sistema que realizan E / S de socket (por ejemplo, read() , recvmsg() , […]; los tiempos de espera no tienen efecto para select() , poll() , epoll_wait() , y así sucesivamente.

Cuando el descriptor de archivo subyacente está configurado como no bloqueante, las llamadas al sistema que realizan E / S de socket regresarán inmediatamente con EAGAIN o EWOULDBLOCK si los recursos no están disponibles de inmediato. Para un socket sin locking, SO_RCVTIMEO no tendrá ningún efecto, ya que la llamada volverá inmediatamente con éxito o error. Por lo tanto, para que SO_RCVTIMEO afecte las llamadas de E / S del sistema, el socket debe estar bloqueando.

Boost.Asio Comportamiento

Primero, las operaciones de E / S asíncronas en Boost.Asio usarán un demultiplexor de eventos, como select() o poll() . Por lo tanto, SO_RCVTIMEO no afectará las operaciones asincrónicas.

A continuación, los sockets de Boost.Asio tienen el concepto de dos modos sin locking (ambos predeterminados a falso):

  • native_non_blocking() que corresponde aproximadamente al estado de no locking del descriptor de archivo. Este modo afecta a las llamadas de E / S del sistema. Por ejemplo, si uno invoca socket.native_non_blocking(true) , entonces recv(socket.native_handle(), ...) puede fallar con errno establecido en EAGAIN o EWOULDBLOCK . Cada vez que se inicia una operación asincrónica en un socket, Boost.Asio habilitará este modo.
  • non_blocking() que afecta las operaciones de socket síncrono de Boost.Asio. Cuando se establece en true , Boost.Asio configurará el descriptor de archivo subyacente para que no sea bloqueante y las operaciones de socket Boost.Asio sincrónicas puedan fallar con boost::asio::error::would_block (o el error equivalente del sistema). Cuando se establece en false , Boost.Asio bloqueará, incluso si el descriptor de archivo subyacente no es de locking, al sondear el descriptor de archivo y volver a intentar las operaciones de E / S del sistema si se devuelven EAGAIN o EWOULDBLOCK .

El comportamiento de non_blocking() impide que SO_RCVTIMEO produzca el comportamiento deseado. Suponiendo que se invoca socket.receive() y los datos no están disponibles ni recibidos:

  • Si non_blocking() es falso, la llamada de E / S del sistema SO_RCVTIMEO por SO_RCVTIMEO . Sin embargo, Boost.Asio bloqueará inmediatamente el sondeo en el descriptor del archivo para que sea legible, lo que no se verá afectado por SO_RCVTIMEO . El resultado final es que la persona que llama se bloquea en socket.receive() hasta que se reciben datos o se socket.receive() error, como el par remoto que cierra la conexión.
  • Si non_blocking() es verdadero, entonces el descriptor de archivo subyacente también es no bloqueante. Por lo tanto, la llamada de E / S del sistema ignorará SO_RCVTIMEO , regresará inmediatamente con EAGAIN o EWOULDBLOCK , haciendo que socket.receive() falle con boost::asio::error::would_block .

Idealmente, para que SO_RCVTIMEO funcione con Boost.Asio, se necesita native_non_blocking() establecido en falso para que SO_RCVTIMEO pueda tener efecto, pero también tenga non_blocking() establecido en verdadero para evitar el sondeo en el descriptor. Sin embargo, Boost.Asio no es compatible con esto :

socket::native_non_blocking(bool mode)

Si el modo es false , pero el valor actual de non_blocking() es true , esta función falla con boost::asio::error::invalid_argument , ya que la combinación no tiene sentido.

Como está recibiendo datos, puede configurar: SO_RCVTIMEO lugar de SO_SNDTIMEO

Aunque las llamadas al sistema y el boost de mezcla pueden no producir los resultados esperados.

Para referencia:

SO_RCVTIMEO

Establece el valor de tiempo de espera que especifica la cantidad máxima de tiempo que una función de entrada espera hasta que se completa. Acepta una estructura timeval con el número de segundos y microsegundos que especifica el límite de cuánto tiempo esperar para que finalice una operación de entrada. Si una operación de recepción se ha bloqueado durante este tiempo sin recibir datos adicionales, se devolverá con un conteo parcial o errno establecido en [EAGAIN] o [EWOULDBLOCK] si no se reciben datos. El valor predeterminado para esta opción es cero, lo que indica que una operación de recepción no deberá exceder el tiempo de espera. Esta opción toma una estructura timeval. Tenga en cuenta que no todas las implementaciones permiten configurar esta opción.

Sin embargo, esta opción solo tiene efecto en las operaciones de lectura, no en otras funciones de bajo nivel que pueden esperar en el socket en una implementación asíncrona (por ejemplo, select y epoll) y parece que no afecta a las operaciones asio asincrónicas también.

Encontré un código de ejemplo de boost que puede funcionar para su caso aquí .

Un ejemplo sobre simplificado (para ser comstackdo en c ++ 11):

 #include  #include  #include  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; }