2017-08-28 79 views
1

我已經廣泛使用了Boost.Asio,但是我遇到了一個單元測試的問題,我不明白。我已經減少了問題降到很做作例如:Boost.Asio做作的例子莫名其妙地阻止

#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(); 
    } 
} 

我與編譯:

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

輸出是:

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

然後超時。

通過調試程序運行表明async_read處理程序從不被調用。在似乎沒有做任何事情的階段暫停執行,表明主線程正在等待condition_variablecv),並且io_service線程位於epoll_wait上。

我似乎是僵局,但看不到如何。

+0

第一次猜測:客戶端未發出關閉,因此讀取未完成,因爲它正在嘗試讀取比發送的更多數據(4096)(send_string.size())。 –

+0

@RichardHodges,但是如果我需要響應,那麼確實發出關閉會導致連接在我收到之前關閉?我想我是因爲發送緩衝區在操作系統的MTU之下,服務器套接字會在接收到數據包後觸發讀處理程序 - 我是否假設太多了? – cmannett85

+0

@RichardHodges我應該補充一點,我嘗試在寫入處理程序的底部添加一個'client_sock.shutdown(..)',但它只是在讀取處理程序中觸發了'結束流'錯誤。將讀緩衝區設置爲5字節使得系統正常工作,所以你原則上是絕對正確的,但顯然我無法預先知道發送大小。 – cmannett85

回答

4

這是如何定義函數的功能,它等待緩衝區有足夠空間的字節數(http://www.boost.org/doc/libs/1_62_0/doc/html/boost_asio/reference/async_read/overload1.html)。

試試這個來代替:http://www.boost.org/doc/libs/1_62_0/doc/html/boost_asio/reference/async_read/overload2.html

你可以給一個回調來決定讀是否已完成,並可能包括等待和檢查由另一通道提供的長度,一旦作家寫的信息(如果您已經確定了無死鎖的方式來做到這一點),或者恰好在消息之前。

添加此完成條件使得它的工作:

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

由@codeshot提供的答案是正確的,但它是幾個解決方案之一 - 這是最合適的是全依賴於你正在使用的協議跨TCP連接。

例如,在一個傳統的密鑰長度值樣式協議,可以執行兩次讀取:

  1. 使用boost::asio::async_read(或等同物)來讀入的固定長度緩衝器以獲得固定長度的報頭
  2. 使用由報頭中指定創建所需大小的緩衝區,以及使用它

有此在chat server example code一個很好的例子重複步驟1中的長度。

如果您使用的是HTTP或RTSP(後者是我正在嘗試做的),那麼您不知道數據會到達多少,您關心的只是接收數據包的數據(我知道這是由於響應中的Content-Length標題,分塊傳輸編碼等而過於簡單化,但與我一起承擔)。爲此,您需要async_read_some(或等效),請參閱HTTP server example

相關問題