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.
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()). –
@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
@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