2012-06-25 26 views
24

Estoy trabajando en una aplicación multiproceso en la que un hilo actúa como un servidor tcp que recibe comandos de un cliente. El subproceso usa un socket y aceptador de Boost para esperar a que un cliente se conecte, recibe un comando del cliente, pasa el comando al resto de la aplicación y luego espera nuevamente. Aquí está el código:Boost :: asio: ¿cómo interrumpir un hilo del servidor tcp bloqueado?

void ServerThreadFunc() 
{ 
    using boost::asio::ip::tcp; 
    boost::asio::io_service io_service; 
    tcp::acceptor acceptor(io_service, tcp::endpoint(tcp::v4(), port_no)); 

    for (;;) 
    { 
     // listen for command connection 
     tcp::socket socket(io_service); 
     acceptor.accept(socket); 

     // connected; receive command 
     boost::array<char,256> msg_buf; 
     socket.receive(boost::asio::buffer(msg_buf)); 

     // do something with received bytes here 
    } 
} 

Este hilo pasa la mayor parte de su tiempo de bloqueo en la llamada a acceptor.accept(). Por el momento, el hilo solo finaliza cuando la aplicación finaliza. Desafortunadamente, esto causa un bloqueo después de que main() regrese, creo que porque el subproceso intenta acceder al singleton de inicio de sesión de la aplicación después de que se destruye el singleton. (Fue así cuando llegué aquí, honesto).

¿Cómo puedo cerrar este hilo limpiamente cuando es hora de que la aplicación salga? He leído que una llamada de bloqueo de accept() en un socket sin procesar se puede interrumpir cerrando el socket de otro hilo, pero esto no parece funcionar en un socket Boost. Intenté convertir la lógica del servidor en E/S asíncrona usando el Boost asynchronous tcp echo server example, pero eso parece intercambiar una llamada de bloqueo al acceptor::accept() por una llamada de bloqueo al io_service::run(), así que me queda el mismo problema: una llamada bloqueada que no puede interrumpir ¿Algunas ideas?

+1

Siempre hay ['io_service :: stop'] (http://www.boost.org/doc/libs/1_49_0/doc/html/boost_asio/reference/io_service/stop.html), se puede llamar de otro hilo. –

+1

La idea de tener un bucle de evento es que * todo * sucede en el bucle de evento, es decir, ejecutar(). Lo que significa que no tiene un segundo hilo de qué preocuparse en primer lugar. (aunque a veces tiene sentido hacer ciertas cosas fuera del ciclo de eventos, como cálculos exigentes) –

+2

Use async_accept. –

Respuesta

26

En resumen, hay dos opciones:

  • Cambiar código para ser asincrónico (acceptor::async_accept() y async_read), ejecutarse dentro del ciclo de eventos a través del io_service::run(), y cancelar a través de io_service::stop().
  • Fuerza las llamadas de bloqueo para interrumpir con mecanismos de nivel inferior, como las señales.

Recomendaría la primera opción, ya que es más probable que sea portátil y más fácil de mantener. El concepto importante para entender es que el io_service::run() solo bloquea mientras haya trabajo pendiente. Cuando se invoca io_service::stop(), intentará hacer que todos los hilos bloqueados en io_service::run() vuelvan lo antes posible; no interrumpirá las operaciones sincrónicas, como acceptor::accept() y socket::receive(), incluso si las operaciones síncronas se invocan dentro del bucle de evento. Es importante tener en cuenta que io_service::stop() es una llamada no bloqueante, por lo que la sincronización con los hilos que se bloquearon en io_service::run() debe usar otra mecánica, como thread::join().

Aquí es un ejemplo de que tendrá una duración de 10 segundos y escucha en el puerto 8080:

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

void StartAccept(boost::asio::ip::tcp::acceptor&); 

void ServerThreadFunc(boost::asio::io_service& io_service) 
{ 
    using boost::asio::ip::tcp; 
    tcp::acceptor acceptor(io_service, tcp::endpoint(tcp::v4(), 8080)); 

    // Add a job to start accepting connections. 
    StartAccept(acceptor); 

    // Process event loop. 
    io_service.run(); 

    std::cout << "Server thread exiting." << std::endl; 
} 

void HandleAccept(const boost::system::error_code& error, 
        boost::shared_ptr<boost::asio::ip::tcp::socket> socket, 
        boost::asio::ip::tcp::acceptor& acceptor) 
{ 
    // If there was an error, then do not add any more jobs to the service. 
    if (error) 
    { 
    std::cout << "Error accepting connection: " << error.message() 
       << std::endl; 
    return; 
    } 

    // Otherwise, the socket is good to use. 
    std::cout << "Doing things with socket..." << std::endl; 

    // Perform async operations on the socket. 

    // Done using the socket, so start accepting another connection. This 
    // will add a job to the service, preventing io_service::run() from 
    // returning. 
    std::cout << "Done using socket, ready for another connection." 
      << std::endl; 
    StartAccept(acceptor); 
}; 

void StartAccept(boost::asio::ip::tcp::acceptor& acceptor) 
{ 
    using boost::asio::ip::tcp; 
    boost::shared_ptr<tcp::socket> socket(
           new tcp::socket(acceptor.get_io_service())); 

    // Add an accept call to the service. This will prevent io_service::run() 
    // from returning. 
    std::cout << "Waiting on connection" << std::endl; 
    acceptor.async_accept(*socket, 
    boost::bind(HandleAccept, 
     boost::asio::placeholders::error, 
     socket, 
     boost::ref(acceptor))); 
} 

int main() 
{ 
    using boost::asio::ip::tcp; 

    // Create io service. 
    boost::asio::io_service io_service; 

    // Create server thread that will start accepting connections. 
    boost::thread server_thread(ServerThreadFunc, boost::ref(io_service)); 

    // Sleep for 10 seconds, then shutdown the server. 
    std::cout << "Stopping service in 10 seconds..." << std::endl; 
    boost::this_thread::sleep(boost::posix_time::seconds(10)); 
    std::cout << "Stopping service now!" << std::endl; 

    // Stopping the io_service is a non-blocking call. The threads that are 
    // blocked on io_service::run() will try to return as soon as possible, but 
    // they may still be in the middle of a handler. Thus, perform a join on 
    // the server thread to guarantee a block occurs. 
    io_service.stop(); 

    std::cout << "Waiting on server thread..." << std::endl; 
    server_thread.join(); 
    std::cout << "Done waiting on server thread." << std::endl; 

    return 0; 
} 

Mientras se ejecuta, me abrieron dos conexiones. Aquí está la salida:

Stopping service in 10 seconds... 
Waiting on connection 
Doing things with socket... 
Done using socket, ready for another connection. 
Waiting on connection 
Doing things with socket... 
Done using socket, ready for another connection. 
Waiting on connection 
Stopping service now! 
Waiting on server thread... 
Server thread exiting. 
Done waiting on server thread.
+0

Brillante: exactamente el código y la explicación que necesitaba. ¡Gracias! – bythescruff

+0

se merece +100 rep :-) –

+0

Agregaría a esto la opción de conectarse al servidor desde dentro del servidor para completar el accept: [un ejemplo que utiliza este enfoque en un servidor Asio completamente síncrono] (https: // stackoverflow.com/questions/48076068/crash-terminate-called-after-throwing-an-instance-of-stdsystem-error-what/48084444#comment83150086_48084444) – sehe

3

Cuando recibe un evento de que es hora de salir, puede llamar al acceptor.cancel(), que cancelará la aceptación pendiente (con un código de error de operation_canceled). En algunos sistemas, también puede tener que aceptar close() el aceptador para estar seguro.

+0

Ni ['acceptor :: cancel()'] (http://www.boost.org/doc/libs/1_49_0/doc/html/boost_asio/reference/basic_socket_acceptor/cancel/overload1.html) ni ['acceptor: : close() '] (http://www.boost.org/doc/libs/1_49_0/doc/html/boost_asio/reference/basic_socket_acceptor/close/overload1.html) interrumpirá _synchronous_ operations. Esas llamadas solo provocarán que las _actividades_asincrónicas_ pendientes estén listas para ejecutarse dentro del bucle de evento de servicio con un error de 'operation_canceled'. –

3

Si se trata de eso, podría abrir una conexión de cliente temporal en localhost - eso lo despertará. Incluso se puede enviar un mensaje especial para que pueda apagar el servidor desde el bar - no debe haber una aplicación para eso :)

0

llamar simplemente apagado con mango nativa y la opción SHUT_RD, para cancelar la operación existente recibir (aceptar).

Cuestiones relacionadas