2010-05-07 10 views
11

Sto imparando Boost :: asio e tutta quella roba asincrona. Come posso leggere in modo asincrono la variabile user_ di tipo std :: string? Boost::asio::buffer(user_) funziona solo con async_write(), ma non con async_read(). Funziona con il vettore, quindi qual è la ragione per non lavorare con la stringa? C'è un altro modo per farlo oltre a dichiarare char user_[max_len] e usare Boost::asio::buffer(user_, max_len)?Come leggere in modo asincrono su std :: string usando Boost :: asio?

Inoltre, qual è il punto di ereditare da boost::enable_shared_from_this<Connection> ed usando shared_from_this() invece di this in async_read() e async_write()? Ho visto molto negli esempi.

Ecco una parte del mio codice:

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_; 
     ... 
}; 

risposta

30

La documentazione Boost.Asio afferma:

Un oggetto tampone rappresenta una regione contigua di memoria, come un 2-tupla consistente di un puntatore e la dimensione in byte. Una tupla della forma {void *, size_t} specifica una regione di memoria mutabile (modificabile).

Ciò significa che, per una chiamata a async_read per scrivere i dati in un buffer, deve essere (nell'oggetto tampone sottostante) un blocco di memoria contiguo. Inoltre, l'oggetto buffer deve essere in grado di scrivere su quel blocco di memoria.

std::string non consente di scrivere arbitrarie nel suo buffer, quindi async_read non può scrivere blocchi di memoria nel buffer di una stringa (notare che std::stringfa invia la sola lettura chiamante accesso al buffer sottostante tramite il metodo data(), che garantisce che il puntatore restituito sarà valido fino alla prossima chiamata a una funzione membro non const. Per questo motivo, Asio può facilmente creare unavvolgendo uno std::string e lo si può usare con async_write).

La documentazione di Asio ha un codice di esempio per un semplice programma di "chat" (vedere http://www.boost.org/doc/libs/1_43_0/doc/html/boost_asio/examples.html#boost_asio.examples.chat) che ha un buon metodo per superare questo problema. Fondamentalmente, è necessario che l'invio TCP invii prima la dimensione di un messaggio, in una "intestazione" di ordinamenti, e il gestore di lettura deve interpretare l'intestazione per allocare un buffer di dimensioni fisse adatto per leggere i dati effettivi.

quanto riguarda la necessità di ricorrere a shared_from_this()async_read e async_write, il motivo è che garantisce che il metodo avvolto da boost::bind farà sempre riferimento a un oggetto vivo. Si consideri la seguente situazione:

  1. Il tuo metodo handle_accept chiamate async_read e invia un gestore "nel reattore" - in fondo hai chiesto il io_service invocare Connection::handle_user_read quando finisce la lettura dei dati dal socket. Lo io_service memorizza questo functor e continua il suo ciclo, in attesa del completamento dell'operazione di lettura asincrona.
  2. Dopo la chiamata a async_read, l'oggetto Connection viene deallocato per qualche motivo (chiusura del programma, condizione di errore, ecc.)
  3. Supponiamo il io_service ora determina che la lettura asincrono è completa, dopo dell'oggetto Connection è stato deallocato ma prima il io_service è distrutto (questo può verificarsi, per esempio, se io_service::run è in esecuzione in un thread separato, come è tipico). Ora, il io_service tenta di richiamare il gestore e ha un riferimento non valido a un oggetto Connection.

La soluzione è quello di allocare Connection tramite un shared_ptr Utilizzando shared_from_this() anziché this quando si invia un gestore "nel reattore" - questo permette io_service per memorizzare un riferimento comune all'oggetto, e shared_ptr garantisce che ha vinto' essere deallocato fino alla scadenza dell'ultimo riferimento.

Quindi, il codice dovrebbe probabilmente essere simile:

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

Si noti che è ora necessario assicurarsi che ogni oggetto Connection è assegnato tramite un shared_ptr, ad esempio:

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

Spero che questo aiuti !

8

Questo non è destinato ad essere una risposta per sé, ma solo un commento lungo: un modo molto semplice per convertire da un buffer ASIO per una stringa è per lo streaming da esso:

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

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

Questo è una copia di dati, ovviamente, ma è quello che devi fare se lo vuoi in una stringa.

4

è possibile utilizzare un std:string con async\_read() come questo:

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

Tuttavia, è meglio assicurarsi che la std::string è grande abbastanza per accettare il pacchetto che vi aspettate e riempito con zeri prima di chiamare async\_read().

E per quanto riguarda il motivo per cui si dovrebbe MAI associare una funzione membro di richiamata a un puntatore this se l'oggetto può essere eliminato, una descrizione più completa e un metodo più robusto può essere trovato qui: Boost async_* functions and shared_ptr's.

0

Boost Asio ha due tipi di buffer. C'è boost::asio::buffer(your_data_structure), che non può crescere ed è quindi generalmente inutile per input sconosciuto, e c'è boost::asio::streambuf che può crescere.

Dato un boost::asio::streambuf buf, lo si trasforma in una stringa con std::string(std::istreambuf_iterator<char>(&buf), {});.

Questo non è efficiente quando si finisce di copiare i dati ancora una volta, ma ciò richiederebbe di rendere boost::asio::buffer consapevole di contenitori coltivabili, ovvero i contenitori che hanno un metodo .resize(N). Non puoi renderlo efficiente senza toccare il codice Boost.

Problemi correlati