简体   繁体   English

Boost.Asio设计的示例莫名其妙地阻塞了

[英]Boost.Asio contrived example inexplicably blocking

I've used Boost.Asio extensively, but I've came across a problem with a unit test that I don't understand. 我已经广泛使用Bo​​ost.Asio,但是遇到了我不理解的单元测试问题。 I've reduced the problem down to a very contrived example: 我将问题简化为一个非常人为的示例:

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

which I compile with: 我用它编译:

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

The output is: 输出为:

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

And then it times out. 然后超时。

Running through the debugger shows that the async_read handler is never called. 通过调试器运行表明,永远不会调用async_read处理程序。 Pausing execution during the phase where it doesn't seem to be doing anything, shows that the main thread is waiting on the condition_variable ( cv ) and the io_service thread is on an epoll_wait . 在似乎什么都没做的阶段中暂停执行,表明主线程在condition_variablecv )上等待,而io_service线程在epoll_wait

I seem to be deadlocking but can't see how. 我似乎陷入僵局,但看不到如何。

This is how the function is defined to work, it waits for exactly the number of bytes that the buffer has space for ( 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 /overload1.html )。

Try this one instead: http://www.boost.org/doc/libs/1_62_0/doc/html/boost_asio/reference/async_read/overload2.html 请尝试以下一种方法: http : //www.boost.org/doc/libs/1_62_0/doc/html/boost_asio/reference/async_read/overload2.html

You can give a callback to decide whether the read is complete and that could include waiting for and checking a length provided by another channel once the writer has written its message (if you've determined a deadlock-free way to do that) or just before the message proper. 您可以提供一个回调函数来决定读取是否完成,并且可以包括在编写者编写其消息后(如果您确定了无死锁的方式)等待并检查另一个通道提供的长度(或者在正确的消息之前。

Adding this completion condition makes it work: 添加此完成条件使其起作用:

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

The answer provided by @codeshot is correct, but it is one of several solutions - which is most appropriate is dependent entirely upon the protocol you're using across the TCP connection. @codeshot提供的答案是正确的,但这是几种解决方案之一-最合适的解决方案完全取决于您在TCP连接上使用的协议。

For example, in a traditional Key-Length-Value style protocol, you would do two reads: 例如,在传统的键长-值样式协议中,您将执行两次读取:

  1. Using boost::asio::async_read (or equivalent) to read into fixed length buffer to obtain the fixed-length header 使用boost::asio::async_read (或等效方法)读入固定长度缓冲区以获得固定长度报头
  2. Use the length specified by the header to create a buffer of the required size, and repeat step 1 using it 使用标头指定的长度来创建所需大小的缓冲区,并使用它重复步骤1

There's a good example of this in the chat server example code . 聊天服务器示例代码中有一个很好的示例。

If you were using HTTP or RTSP (the latter is what I was trying to do), then you don't know how much is data is coming, all you care about is receiving a packet's worth of data (I know this is an oversimplification due to the Content-Length header in responses, chunked transfer encoding, etc. but bear with me). 如果您使用的是HTTP或RTSP(后者是我想做的),那么您就不知道会有多少数据输入,您所关心的只是接收到一个数据包中的数据(我知道这过于简单了)由于响应中包含Content-Length标头,分块的传输编码等,请耐心等待)。 For this you need async_read_some (or equivalent), see the HTTP server example . 为此,您需要async_read_some (或等效方法),请参见HTTP服务器示例

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM