2010-05-07 11 views
11

Estoy aprendiendo Boost :: asio y todas esas cosas asincrónicas. ¿Cómo puedo leer asincrónicamente la variable user_ del tipo std :: string? Boost::asio::buffer(user_) funciona solo con async_write(), pero no con async_read(). Funciona con el vector, entonces, ¿cuál es la razón para que no funcione con una cadena? ¿Hay otra manera de hacerlo además de declarar char user_[max_len] y usar Boost::asio::buffer(user_, max_len)?Cómo leer asincrónicamente std :: string usando Boost :: asio?

Además, ¿cuál es el punto de heredar de boost::enable_shared_from_this<Connection> y usando en lugar de shared_from_this()this en async_read() y async_write()? Lo he visto mucho en los ejemplos.

Aquí es una parte de mi código:

class Connection 
{ 
    public: 

     Connection(tcp::acceptor &acceptor) : 
      acceptor_(acceptor), 
      socket_(acceptor.get_io_service(), tcp::v4()) 
     { } 

     void start() 
     { 
      acceptor_.get_io_service().post(
       boost::bind(&Connection::start_accept, this)); 
     } 

    private: 

     void start_accept() 
     { 
      acceptor_.async_accept(socket_, 
       boost::bind(&Connection::handle_accept, this, 
       placeholders::error)); 
     } 

     void handle_accept(const boost::system::error_code& err) 
     { 
      if (err) 
      { 
       disconnect(); 
      } 
      else 
      { 
       async_read(socket_, boost::asio::buffer(user_), 
        boost::bind(&Connection::handle_user_read, this, 
        placeholders::error, placeholders::bytes_transferred)); 
      } 
     } 

     void handle_user_read(const boost::system::error_code& err, 
      std::size_t bytes_transferred) 
     { 
      if (err) 
      { 
       disconnect(); 
      } 
      else 
      { 
       ... 
      } 
     } 

     ... 

     void disconnect() 
     { 
      socket_.shutdown(tcp::socket::shutdown_both); 
      socket_.close(); 
      socket_.open(tcp::v4()); 
      start_accept(); 
     } 

     tcp::acceptor &acceptor_; 
     tcp::socket socket_; 
     std::string user_; 
     std::string pass_; 
     ... 
}; 

Respuesta

30

La documentación Boost.Asio afirma:

Un objeto búfer representa una región contigua de la memoria como una 2-tupla que consiste en un puntero y tamaño en bytes. Una tupla de la forma {void *, size_t} especifica una región de memoria mutable (modificable).

Esto significa que para que una llamada a async_read para escribir datos en una memoria intermedia, que debe ser (en el objeto tampón subyacente) un bloque contiguo de memoria. Además, el objeto de memoria intermedia debe poder escribir en ese bloque de memoria.

std::string no permite que las escrituras arbitrarias en su memoria intermedia, por lo async_read no se puede escribir trozos de memoria en búfer de una cadena (tenga en cuenta que std::stringqué dan la persona que llama acceso de sólo lectura a la memoria intermedia subyacente a través del método data(), que garantiza que el puntero devuelto será válido hasta la próxima llamada a una función de miembro no const. Por esta razón, Asio puede crear fácilmente un const_buffer envolviendo un std::string, y puede usarlo con async_write).

La documentación de Asio tiene un código de ejemplo para un programa simple de "chat" (ver http://www.boost.org/doc/libs/1_43_0/doc/html/boost_asio/examples.html#boost_asio.examples.chat) que tiene un buen método para resolver este problema. Básicamente, debe enviar primero el envío de envío TCP a lo largo del tamaño de un mensaje, en un "encabezado" de clases, y su manejador de lectura debe interpretar el encabezado para asignar un búfer de un tamaño fijo adecuado para leer los datos reales.

En cuanto a la necesidad de utilizar shared_from_this() en async_read y async_write, la razón es que garantiza que el método envuelta por boost::bind se referirá siempre a un objeto vivo. Considere la siguiente situación:

  1. Su método handle_accept llama async_read y envía un controlador "en el reactor" - básicamente lo solicitado el io_service para invocar Connection::handle_user_read cuando se termina de leer los datos de la toma. El io_service almacena este funtor y continúa su ciclo, esperando que se complete la operación de lectura asincrónica.
  2. Después de su llamada al async_read, el objeto Connection es desasignado por alguna razón (terminación del programa, una condición de error, etc.)
  3. Supongamos que el io_service ahora determina que la lectura asíncrona es completa, después de el objeto Connection ha sido desasignado pero antes la io_service se destruye (esto puede ocurrir, por ejemplo, si io_service::run se ejecuta en un hilo separado, como es típico). Ahora, el io_service intenta invocar el controlador y tiene una referencia no válida a un objeto Connection.

La solución es asignar Connection a través de un shared_ptr y utilizar shared_from_this() en lugar de this al enviar un controlador "en el reactor" - esto permite io_service para almacenar una referencia común al objeto, y shared_ptr garantiza que ganó' t ser desasignado hasta que caduque la última referencia.

Por lo tanto, el código debe probablemente ser algo como:

class Connection : public boost::enable_shared_from_this<Connection> 
{ 
public: 

    Connection(tcp::acceptor &acceptor) : 
     acceptor_(acceptor), 
     socket_(acceptor.get_io_service(), tcp::v4()) 
    { } 

    void start() 
    { 
     acceptor_.get_io_service().post(
      boost::bind(&Connection::start_accept, shared_from_this())); 
    } 

private: 

    void start_accept() 
    { 
     acceptor_.async_accept(socket_, 
      boost::bind(&Connection::handle_accept, shared_from_this(), 
      placeholders::error)); 
    } 

    void handle_accept(const boost::system::error_code& err) 
    { 
     if (err) 
     { 
      disconnect(); 
     } 
     else 
     { 
      async_read(socket_, boost::asio::buffer(user_), 
       boost::bind(&Connection::handle_user_read, shared_from_this(), 
       placeholders::error, placeholders::bytes_transferred)); 
     } 
    } 
    //... 
}; 

Tenga en cuenta que ahora debe asegurarse de que cada Connection objeto se asigna a través de un shared_ptr, por ejemplo:

boost::shared_ptr<Connection> new_conn(new Connection(...)); 

Espero que esto ayude !

8

Esto no pretende ser una respuesta en sí, sino simplemente un largo comentario: una manera muy simple para convertir desde un búfer ASIO a una cadena es para transmitir de ella:

asio::streambuf buff; 
asio::read_until(source, buff, '\r'); // for example 

istream is(&buff); 
is >> targetstring; 

Este es una copia de datos, por supuesto, pero eso es lo que debe hacer si lo quiere en una cadena.

4

Se puede utilizar un std:string con async\_read() así:

async_read(socket_, boost::asio::buffer(&user_[0], user_.size()), 
      boost::bind(&Connection::handle_user_read, this, 
      placeholders::error, placeholders::bytes_transferred)); 

Sin embargo, es mejor asegurarse de que el std::string es lo suficientemente grande como para aceptar el paquete que usted está esperando y rellenado con ceros antes de llamar async\_read().

Y en cuanto a por qué usted debe NUNCA obligar a una devolución de llamada función miembro de un puntero this si el objeto se puede eliminar, una descripción más completa y un método más robusto se puede encontrar aquí: Boost async_* functions and shared_ptr's.

0

Boost Asio tiene dos estilos de búferes. No es boost::asio::buffer(your_data_structure), que no puede crecer, y por lo tanto es generalmente inútil para la entrada desconocida, y hay boost::asio::streambuf que puede crecer.

Dado un boost::asio::streambuf buf, conviértalo en una cadena con std::string(std::istreambuf_iterator<char>(&buf), {});.

Esto no es eficiente ya que terminará copiando datos una vez más, pero eso requeriría que boost::asio::buffer tenga conocimiento de contenedores cultivables, es decir, contenedores que tienen un método .resize(N). No puede hacerlo eficiente sin tocar el código de Boost.

Cuestiones relacionadas