2017-08-28 2 views
1

Ich habe Boost.Asio ausgiebig verwendet, aber ich bin auf ein Problem mit einem Komponententest gestoßen, den ich nicht verstehe. Ich habe das Problem auf eine sehr konstruiertes Beispiel reduziert:Boost.Asio erfunden Beispiel unerklärlicherweise blockiert

#include <string> 
#include <chrono> 
#include <thread> 
#include <mutex> 
#include <condition_variable> 

#include <boost/asio.hpp> 
#define BOOST_TEST_MODULE My_Module 
#define BOOST_TEST_DYN_LINK 
#include <boost/test/unit_test.hpp> 
#include <boost/test/auto_unit_test.hpp> 

using namespace std::string_literals; 
using namespace std::chrono_literals; 

namespace BA = boost::asio; 
namespace BAI = BA::ip; 

BOOST_AUTO_TEST_CASE(test) 
{ 
    std::mutex m; 
    std::condition_variable cv; 

    BA::io_service servicer; 
    auto io_work = std::make_unique<BA::io_service::work>(servicer); 

    auto thread = std::thread{[&]() { 
     servicer.run(); 
    }}; 

    auto received_response = false; 

    auto server_buf = std::array<std::uint8_t, 4096>{}; 
    auto server_sock = BAI::tcp::socket{servicer}; 
    auto acceptor = BAI::tcp::acceptor{servicer, 
             BAI::tcp::endpoint{BAI::tcp::v4(), 20123}}; 
    acceptor.async_accept(server_sock, [&](auto&& ec) { 
     if (ec) { 
      BOOST_TEST_MESSAGE(ec.message()); 
     } 
     BOOST_REQUIRE(!ec); 

     BOOST_TEST_MESSAGE("Accepted connection from " << server_sock.remote_endpoint() << 
          ", reading..."); 

     BA::async_read(server_sock, 
         BA::buffer(server_buf), 
         [&](auto&& ec, auto&& bytes_read){ 
      std::unique_lock<decltype(m)> ul(m); 
      received_response = true; 

      if (ec) { 
       BOOST_TEST_MESSAGE(ec.message()); 
      } 
      BOOST_REQUIRE(!ec); 

      const auto str = std::string{server_buf.begin(), 
             server_buf.begin() + bytes_read}; 
      BOOST_TEST_MESSAGE("Read: " << str); 

      ul.unlock(); 
      cv.notify_one(); 
     }); 
    }); 

    const auto send_str = "hello"s; 
    auto client_sock = BAI::tcp::socket{servicer, BAI::tcp::v4()}; 
    client_sock.async_connect(BAI::tcp::endpoint{BAI::tcp::v4(), 20123}, 
           [&](auto&& ec) { 
     if (ec) { 
      BOOST_TEST_MESSAGE(ec.message()); 
     } 
     BOOST_REQUIRE(!ec); 

     BOOST_TEST_MESSAGE("Connected..."); 
     BA::async_write(client_sock, 
         BA::buffer(send_str), 
         [&](auto&& ec, auto&& bytes_written) { 
      if (ec) { 
       BOOST_TEST_MESSAGE(ec.message()); 
      } 
      BOOST_REQUIRE(!ec); 

      BOOST_TEST_MESSAGE("Written " << bytes_written << " bytes"); 
     }); 
    }); 

    std::unique_lock<decltype(m)> ul(m); 
    cv.wait_for(ul, 2s, [&](){ return received_response; }); 
    BOOST_CHECK(received_response); 

    io_work.reset(); 
    servicer.stop(); 
    if (thread.joinable()) { 
     thread.join(); 
    } 
} 

, die ich kompilieren mit:

g++ -std=c++17 source.cc -l boost_unit_test_framework -pthread -l boost_system -ggdb 

Die Ausgabe lautet:

Accepted connection from 127.0.0.1:51688, reading... 
Connected... 
Written 5 bytes 

Und dann ist es mal aus.

Das Ausführen durch den Debugger zeigt, dass der async_read-Handler nie aufgerufen wird. Die Ausführung in der Phase anhalten, in der es scheinbar nichts zu tun hat, zeigt an, dass der Haupt-Thread auf dem condition_variable (cv) und der io_service-Thread auf einem epoll_wait wartet.

Ich bin festgefahren, kann aber nicht sehen, wie.

+0

Erste Vermutung: Client gibt kein Shutdown aus, so dass das Lesen nicht abgeschlossen wird, weil es versucht, mehr Daten zu lesen (4096) als gesendet wurde (send_string.size()). –

+0

@RichardHodges, aber was ist, wenn ich eine Antwort brauche, wird sicher ein Shutdown ausstellen, dass die Verbindung geschlossen wird, bevor ich eine bekomme? Ich habe angenommen, dass ich, weil der Sender-Puffer unter der MTU des Betriebssystems ist, der Server-Socket den Lese-Handler nach einem empfangenen Paket auslösen würde - nehme ich zu viel an? – cmannett85

+0

@RichardHodges Ich sollte hinzufügen, dass ich habe versucht, eine 'client_sock.shutdown (..)' an der Unterseite des Schreib-Handler hinzufügen, aber es hat nur einen 'Ende des Streams' Fehler im Lese-Handler ausgelöst. Wenn Sie den Lesepuffer auf 5 Byte setzen, hat das System funktioniert, also sind Sie im Prinzip richtig, aber offensichtlich kann ich die Sendegröße nicht im Voraus wissen. – cmannett85

Antwort

4

So funktioniert die Funktion, sie wartet genau auf die Anzahl der Bytes, für die der Puffer Platz hat (http://www.boost.org/doc/libs/1_62_0/doc/html/boost_asio/reference/async_read/overload1.html).

versucht diese statt: http://www.boost.org/doc/libs/1_62_0/doc/html/boost_asio/reference/async_read/overload2.html

Sie einen Rückruf, ob die Lese abgeschlossen ist und schließen und eine Länge von einem anderen Kanal bereitgestellt Überprüfung warten, dass könnte zu entscheiden geben kann, sobald der Autor seine Nachricht geschrieben hat (wenn Sie habe einen deadlockfreien Weg gefunden, dies zu tun oder kurz vor der eigentlichen Nachricht.

diese Komplettierungsbedingung Hinzufügen macht es Arbeit:

[&](auto&& ec, auto&& bytes_read){ 
    return bytes_read < 5 ? 5 - bytes_read : 0; 
}, 
1

Die Antwort von @codeshot vorgesehen ist richtig, aber es ist eine von mehreren Lösungen - die am besten geeignet ist abhängig ist ganz auf das Protokoll Sie verwenden über die TCP-Verbindung.

Zum Beispiel in einem traditionellen Key-Length-Value-Stil-Protokoll, würden Sie tun, zwei lautet:

  1. Mit boost::asio::async_read (oder gleichwertig) in fester Länge Puffer zu lesen, die mit fester Länge Header zu erhalten
  2. verwenden sie die durch den Header angegebene Länge einen Puffer der erforderlichen Größe zu erstellen, und wiederholen sie Schritt 1 es

es ist ein gutes Beispiel dafür in den chat server example code verwenden.

Wenn Sie HTTP oder RTSP verwenden (letzteres ist, was ich versucht habe), dann wissen Sie nicht, wie viel Daten kommen, alles, was Sie interessiert, ist das Empfangen von Daten eines Pakets (ich weiß Dies ist eine zu starke Vereinfachung aufgrund der Content-Length Header in Antworten, Chunked Transfer Encoding, etc. aber mit mir). Dazu benötigen Sie async_read_some (oder gleichwertig), siehe HTTP server example.