【问题标题】:boost:asio::read or boost:asio::async_read with timeoutboost:asio::read 或 boost:asio::async_read 超时
【发布时间】:2017-04-03 19:36:20
【问题描述】:

是的。我知道在boost::asio 中有一些关于这个time_out 的问题。我的问题可能太简单了,asio 的人在这里无法解决。

我在 TCP 协议上使用boost::asio 以尽可能快地循环连续读取网络上的数据。

以下函数 ReadData() 在 while 循环中从工作人员 std::thread 连续调用。

std::size_t ReadData(std::vector<unsigned char> & buffer, unsigned int size_to_read) {

 boost::system::error_code error_code;
 buffer.resize(size_to_read);

 // Receive body
 std::size_t bytes_read = boost::asio::read(*m_socket, boost::asio::buffer(buffer), error_code);

 if (bytes_read == 0) {
   // log error
   return;
 }

 return bytes_read;
}

效果很好。返回数据。一切都很好。

我想要的只是为boost::asio::read 使用time_out。我了解到我需要使用 boost::asio::async_read 和 boost::asio::async_wait 才能使 time_out 技术起作用。

一位boost example建议使用boost::asio::async_read_until?

我应该使用boost::asio::async_read 还是boost::asio::async_read_until?

我使用boost::asio::async_read 或boost::asio::async_read_until 或boost::asio::read 都没有关系。但我希望在对我的方法ReadData 的调用中触发并完成asio::read 调用,这样客户端代码就不会受到影响。

我怎样才能做到这一点?请推荐

【问题讨论】:

  • 你知道你可以使用socket::cancel() 来取消异步操作,对吧?
  • 是的。我知道如果达到超时,我应该取消套接字。但是我如何首先在异步读取中使用 time_out 呢?
  • @TheQuantumPhysicist。 socket::cancel() 会处理同步读取操作吗?
  • @SegmentationFault 同时启动 async_wait 和 async_read。第一个完成应该取消另一个。您需要一个标志来表明您已取消,并且您需要使用一个链来管理线程争用。
  • @TheQuantumPhysicist 已发布答案。建议您查看我在此处使用的每种技术的 asio 文档。 asio 很深,文档也很简洁。

标签: c++ boost-asio asio


【解决方案1】:

好的,这样的事情应该适合你的目的:

理由:

您似乎想要使用阻塞操作。既然是这种情况,很有可能您没有运行线程来泵送 io 循环。

所以我们在套接字的 io 循环上同时启动两个异步任务,然后生成一个线程:

a) 重置(实际上是重新启动)循环,以防循环已用完

b) 将循环运行到筋疲力尽(我们可以在这里更聪明,只在处理程序指示满足某些条件之前运行它,但这是另一天的教训)

#include <type_traits>

template<class Stream, class ConstBufferSequence, class Handler>
auto async_read_with_timeout(Stream& stream, ConstBufferSequence&& sequence, std::size_t millis, Handler&& handler)
{
    using handler_type = std::decay_t<Handler>;
    using buffer_sequence_type = std::decay_t<ConstBufferSequence>;
    using stream_type = Stream;

    struct state_machine : std::enable_shared_from_this<state_machine>
    {
        state_machine(stream_type& stream, buffer_sequence_type sequence, handler_type handler)
                : stream_(stream)
                , sequence_(std::move(sequence))
                , handler_(std::move(handler))
        {}
        void start(std::size_t millis)
        {
            timer_.expires_from_now(boost::posix_time::milliseconds(millis));
            timer_.async_wait(strand_.wrap([self = this->shared_from_this()](auto&& ec) {
                self->handle_timeout(ec);
            }));
            boost::asio::async_read(stream_, sequence_,
                                    strand_.wrap([self = this->shared_from_this()](auto&& ec, auto size){
                self->handle_read(ec, size);
            }));
        }

        void handle_timeout(boost::system::error_code const& ec)
        {
            if (not ec and not completed_)
            {
                boost::system::error_code sink;
                stream_.cancel(sink);
            }
        }

        void handle_read(boost::system::error_code const& ec, std::size_t size)
        {
            assert(not completed_);
            boost::system::error_code sink;
            timer_.cancel(sink);
            completed_ = true;
            handler_(ec, size);
        }

        stream_type& stream_;
        buffer_sequence_type sequence_;
        handler_type handler_;
        boost::asio::io_service::strand strand_ { stream_.get_io_service() };
        boost::asio::deadline_timer timer_ { stream_.get_io_service() };
        bool completed_ = false;
    };

    auto psm = std::make_shared<state_machine>(stream,
                                               std::forward<ConstBufferSequence>(sequence),
                                               std::forward<Handler>(handler));
    psm->start(millis);
}

std::size_t ReadData(boost::asio::ip::tcp::socket& socket,
                     std::vector<unsigned char> & buffer,
                     unsigned int size_to_read,
                     boost::system::error_code& ec) {

    buffer.resize(size_to_read);

    ec.clear();
    std::size_t bytes_read = 0;
    auto& executor = socket.get_io_service();
    async_read_with_timeout(socket, boost::asio::buffer(buffer),
                            2000, // 2 seconds for example
                            [&](auto&& err, auto size){
        ec = err;
        bytes_read = size;
    });

    // todo: use a more scalable executor than spawning threads
    auto future = std::async(std::launch::async, [&] {
        if (executor.stopped()) {
            executor.reset();
        }
        executor.run();
    });
    future.wait();

    return bytes_read;
}

【讨论】:

  • @SegmentationFault stream_type 是作为参数传入的套接字/流的别名
  • @SegmentationFault 使用 asio 编码时最好将套接字视为“恰好是套接字的流”
  • @SegmentationFault 最后一件事。请注意,我正在使用免费函数 asio::read - 如果完成但出现错误,则仍有可能读取了一些字节。通常它并不重要,因为你正在寻找一个固定长度的框架,但它曾经让我很着迷。这是因为 asio::read 是一个组合操作(请参阅文档)。祝你好运。
  • @SegmentationFault 它应该读取与向量中可用的字节数一样多的字节。但是,超时为 2 秒。也许您需要更长的时间?
  • @SegmentationFault 没问题。可能您包含的其他内容包括它的某个位置 - boost/asio 可能是一个候选者。很高兴能帮上忙。
猜你喜欢
  • 1970-01-01
  • 2010-09-19
  • 2014-01-24
  • 1970-01-01
  • 2012-04-10
  • 2021-11-29
  • 1970-01-01
  • 2013-02-08
  • 1970-01-01
相关资源
最近更新 更多