2015-02-12 15 views
5

Auf der Suche nach einem boost :: asio (und mit sich selbst Boost) beschlossen, asynchronen Server zu schreiben. Um eingehende Daten zu speichern, verwende ich boost :: asio :: streambuf. Hier habe ich ein Problem. Wenn ich eine zweite Nachricht vom Client empfange und später sehe ich, dass im Puffer eine Daten aus früheren Nachrichten enthält. Obwohl ich Consume-Methode im Eingabepuffer aufrufen. Was ist los mit dir?Arbeiten mit boost :: asio :: streambuf

class tcp_connection 
// Using shared_ptr and enable_shared_from_this 
// because we want to keep the tcp_connection object alive 
// as long as there is an operation that refers to it. 
: public boost::enable_shared_from_this<tcp_connection> 
{ 
... 

boost::asio::streambuf receive_buffer; 

boost::asio::io_service::strand strand; 
} 

... 

void tcp_connection::receive() 
{ 
// Read the response status line. The response_ streambuf will 
// automatically grow to accommodate the entire line. The growth may be 
// limited by passing a maximum size to the streambuf constructor. 
boost::asio::async_read_until(m_socket, receive_buffer, "\r\n", 
    strand.wrap(boost::bind(&tcp_connection::handle_receive, shared_from_this()/*this*/, 
    boost::asio::placeholders::error, 
    boost::asio::placeholders::bytes_transferred))); 

} 


void tcp_connection::handle_receive(const boost::system::error_code& error, 
std::size_t bytes_transferred) 
{ 

if (!error) 
{ 
    // process the data 

    /* boost::asio::async_read_until remarks 

    After a successful async_read_until operation, 
    the streambuf may contain additional data beyond the delimiter. 
    An application will typically leave that data in the streambuf for a 
    subsequent async_read_until operation to examine. 
    */ 

    /* didn't work  
    std::istream is(&receive_buffer); 
    std::string line; 
    std::getline(is, line); 
    */ 


    // clean up incomming buffer but it didn't work 
    receive_buffer.consume(bytes_transferred); 

    receive(); 

} 
else if (error != boost::asio::error::operation_aborted) 
{ 
    std::cout << "Client Disconnected\n"; 

    m_connection_manager.remove(shared_from_this()); 
} 
} 

Antwort

17

entweder mit einem std::istream und daraus zu lesen, wie beispielsweise durch std::getline() oder explizit boost::asio::streambuf::consume(n) aufruft, werden die Daten aus der Eingabesequenz entfernen.
Wenn die Anwendung eine dieser beiden Operationen ausführt und die nachfolgenden read_until() Operationen doppelte Daten in der Eingangssequenz receive_buffer ergeben, stammen die duplizierten Daten wahrscheinlich von der Gegenstelle. Wenn der Remote-Peer in den Socket schreibt und direkt die Eingabesequenz eines Streambufs verwendet, muss der Remote-Peer nach jeder erfolgreichen Schreiboperation explizit consume() aufrufen.


Wie in der Dokumentation erwähnt, erfolgreich read_until() Operationen zusätzliche Daten über das Trennzeichen enthalten, einschließlich zusätzlicher Begrenzer. Wenn beispielsweise "[email protected]@" in einen Socket geschrieben wird, kann ein read_until()-Vorgang, bei dem '@' als Trennzeichen verwendet wird, "[email protected]@" lesen und an die Eingangssequenz des Streambufs übergeben. Die Operation zeigt jedoch an, dass die Anzahl der übertragenen Bytes diejenige bis einschließlich des ersten Begrenzers ist. Somit wäre bytes_transferred2 und streambuf.size() wäre 4. Nachdem Bytes verbraucht wurden, würde die Eingabesequenz des Streambufs "[email protected]" enthalten, und ein nachfolgender Aufruf an read_until() wird sofort zurückgeben, da das Streambuf bereits das Trennzeichen enthält.

ist das komplette Beispiel demonstratingstreambuf Verwendung zum Lesen und Schreiben, und wie die Eingangssequenz verbraucht wird:

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

// This example is not interested in the handlers, so provide a noop function 
// that will be passed to bind to meet the handler concept requirements. 
void noop() {} 

std::string make_string(boost::asio::streambuf& streambuf) 
{ 
return {buffers_begin(streambuf.data()), 
     buffers_end(streambuf.data())}; 
} 

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

    // Create all I/O objects. 
    tcp::acceptor acceptor(io_service, tcp::endpoint(tcp::v4(), 0)); 
    tcp::socket server_socket(io_service); 
    tcp::socket client_socket(io_service); 

    // Connect client and server sockets. 
    acceptor.async_accept(server_socket, boost::bind(&noop)); 
    client_socket.async_connect(acceptor.local_endpoint(), boost::bind(&noop)); 
    io_service.run(); 

    // Write to server. 
    boost::asio::streambuf write_buffer; 
    std::ostream output(&write_buffer); 
    output << "[email protected]" 
      "[email protected]"; 
    write(server_socket, write_buffer.data()); 
    std::cout << "Wrote: " << make_string(write_buffer) << std::endl; 
    assert(write_buffer.size() == 4); // Data not consumed. 

    // Read from the client. 
    boost::asio::streambuf read_buffer; 

    // Demonstrate consuming via istream. 
    { 
    std::cout << "Read" << std::endl; 
    auto bytes_transferred = read_until(client_socket, read_buffer, '@'); 
    // Verify that the entire write_buffer (data pass the first delimiter) was 
    // read into read_buffer. 
    auto initial_size = read_buffer.size(); 
    assert(initial_size == write_buffer.size()); 

    // Read from the streambuf. 
    std::cout << "Read buffer contains: " << make_string(read_buffer) 
       << std::endl; 
    std::istream input(&read_buffer); 
    std::string line; 
    getline(input, line, '@'); // Consumes from the streambuf. 
    assert("a" == line); // Note getline discards delimiter. 
    std::cout << "Read consumed: " << line << "@" << std::endl; 
    assert(read_buffer.size() == initial_size - bytes_transferred); 
    } 

    // Write an additional message to the server, but only consume '[email protected]' 
    // from write buffer. The buffer will contain '[email protected]@'. 
    write_buffer.consume(2); 
    std::cout << "Consumed write buffer, it now contains: " << 
       make_string(write_buffer) << std::endl; 
    assert(write_buffer.size() == 2); 
    output << "[email protected]"; 
    assert(write_buffer.size() == 4); 
    write(server_socket, write_buffer.data()); 
    std::cout << "Wrote: " << make_string(write_buffer) << std::endl; 

    // Demonstrate explicitly consuming via the streambuf. 
    { 
    std::cout << "Read" << std::endl; 
    auto initial_size = read_buffer.size(); 
    auto bytes_transferred = read_until(client_socket, read_buffer, '@'); 
    // Verify that the read operation did not attempt to read data from 
    // the socket, as the streambuf already contained the delimiter. 
    assert(initial_size == read_buffer.size()); 

    // Read from the streambuf. 
    std::cout << "Read buffer contains: " << make_string(read_buffer) 
       << std::endl; 
    std::string line(
     boost::asio::buffers_begin(read_buffer.data()), 
     boost::asio::buffers_begin(read_buffer.data()) + bytes_transferred); 
    assert("[email protected]" == line); 
    assert(read_buffer.size() == initial_size); // Nothing consumed. 
    read_buffer.consume(bytes_transferred); // Explicitly consume. 
    std::cout << "Read consumed: " << line << std::endl; 
    assert(read_buffer.size() == 0); 
    } 

    // Read again. 
    { 
    std::cout << "Read" << std::endl; 
    read_until(client_socket, read_buffer, '@'); 

    // Read from the streambuf. 
    std::cout << "Read buffer contains: " << make_string(read_buffer) 
       << std::endl; 
    std::istream input(&read_buffer); 
    std::string line; 
    getline(input, line, '@'); // Consumes from the streambuf. 
    assert("b" == line); // Note "b" is expected and not "c". 
    std::cout << "Read consumed: " << line << "@" << std::endl; 
    std::cout << "Read buffer contains: " << make_string(read_buffer) 
       << std::endl; 
    } 
} 

Ausgang:

Wrote: [email protected]@ 
Read 
Read buffer contains: [email protected]@ 
Read consumed: [email protected] 
Consumed write buffer, it now contains: [email protected] 
Wrote: [email protected]@ 
Read 
Read buffer contains: [email protected] 
Read consumed: [email protected] 
Read 
Read buffer contains: [email protected]@ 
Read consumed: [email protected] 
Read buffer contains: [email protected] 
+0

Danke, das ist ein sehr gutes Beispiel für Arbeiten mit streambuf. Ich habe gefunden, warum ich Daten aus früheren Nachrichten nach dem Senden sehe. Auf dem Client verwendet die Funktion Send() einen globalen Puffer streambuf (send_buffer in meinem Beispiel). Wie mache ich das jeden Aufruf von Send() verwendet eigenes streambuf Objekt? So etwas wie boost :: make_shared? Das ist der richtige Ansatz? Haben Sie ein gutes Beispiel für Server und Client, die boost :: asio verwenden? Das ist, was in der Dokumentation von Boost d oes nicht mir passt, da Puffer von konstanter Länge verwendet, und ich möchte ein Beispiel für die Verwendung von Streambuf sehen. – vint

+2

Super, sehr hilfreiche Antwort. Ich wünschte, die Boost-Leute würden Sie ihre gesamte Dokumentation schreiben lassen! ;-) – Dronz

+0

Wie hast du auf die Eingabefolge zugegriffen, ohne jemals zu committen ('commit()')? [data() -Dokumentation] (http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/basic_streambuf/data.html) sagt 'data()' gibt eine Liste von Eingabepuffern zurück und Laut [commit() -Dokumentation] (http://www.boost.org/doc/libs/1_61_0/doc/html/boost_asio/reference/basic_streambuf/commit.html) werden die Zeichen tatsächlich von der Ausgabesequenz in die Eingabe kopiert Sequenz. –