为什么没有 "cursor" 调用者使用 ConstBufferSequence 缓冲区 async_send_some?

Why is there not a "cursor" for caller to async_send_some with ConstBufferSequence buffers?

在 boost asio 中,async_write_some 程序员可以以任何方式实现自己的类似于 async_write 的细粒度逻辑,等等:

class koebenhavn : public std::enable_shared_from_this<koebenhavn>
{
private:
    boost::asio::ip::tcp::socket socket;
public:
    ...
    void writing(std::vector<const_buffer> & buffers, boost::asio::io_context::strand & strand){
        /* io worker may call back at arbitrary amount of transferred bytes */
        socket->async_write_some(buffers, 
             boost::asio::bind_executor(strand, [self=shared_from_this(), &buffers, &strand](boost::system::error_code error, size_t transferred){
                 /* lambda instead of std::bind for specification if any possible overload */
                 self->wrote(buffers, strand, error);
             }));
    }
    void wrote(std::vector<const_buffer> & buffers, 
               boost::asio::io_context::strand & strand,
               boost::system::error_code error, size_t transferred){
               /* we need to advance buffers to send the rest bytes in reference */
               if(!error){
                 /* transfer occurred */
                 if(transferred){
  /* ----------------> here we need to construct new "cursor" of buffers based on the previous */
                   std::vector<const_buffer> cursor = advance(buffers, transferred);
  /* <---------------- repeat! */
                   writing(cursor, strand);
                 }
               }
    }
    ...
};

我想要的是一个简单的函数,用于为下一次重复调用推进缓冲区序列。现在我怀疑在 asio 中没有任何 present wheel,是吗?

我有充分的理由不使用 async_write。回复请不填

你所期望的存在。这是 DynamicBuffer 的概念。

read|wrte_some 成员函数是低级的,不采用动态缓冲区(它采用一系列 MutableBuffer)。事实上,我认为在 99% 的情况下使用这些函数并不是你想要的。例如。见 this remark:

Remarks

The read operation may not read all of the requested number of bytes. Consider using the async_read function if you need to ensure that the requested amount of data is read before the asynchronous operation completes

人们通常对 IO 数据包传递有错误的假设。只需将 async_read 函数与您的动态缓冲区一起使用:

自己动手

当然,您 可以 将动态缓冲区(即带有 read/write “游标”)与任何低级接口(期望直接缓冲区)一起使用。 但是 那么你必须使用 prepare()/consume()/commit() 接口来管理游标(以及可能发生在底层存储中的可选分配) ).

您可以将其与 iostreams 进行比较:您可以使用底层的 streambuf 接口代替 int x; std::cin >> x;,但它只会增加工作量并且容易出错。

当然,有时您想要那个低级接口(例如,当您希望能够更详细地了解什么数据包在什么时间到达时),但我认为那是例外遵守规则。

奖金:演示代码

解决问题代码的最简单方法是使用组合写入操作:

void writing(std::vector<const_buffer> const& buffers, strand& strand)
{
    /* io worker may call back at arbitrary amount of transferred bytes */
    boost::asio::async_write(
        socket, buffers,
        bind_executor(strand, [self = shared_from_this(), strand]
            (error_code ec, size_t transferred) {
                std::cout << "Transferred " << transferred << " bytes "
                          << "(" << ec.message() << ")" << std::endl;
            }));
}

这里的“游标”是由图书馆完成的。没有问题。

备注:

  1. 你可能更能接受buffer类型,接受DynamicBuffer(v1/v2)和ConstBufferSequence的任意模型:

    template <typename Buffers>
    void writing(Buffers&& buffers, strand& strand)
    {
        /* io worker may call back at arbitrary amount of transferred bytes */
        boost::asio::async_write(
            socket, std::forward<Buffers>(buffers),
            bind_executor(strand, [self = shared_from_this(), strand]
                (error_code ec, size_t transferred) {
                    std::cout << "Transferred " << transferred << " bytes "
                              << "(" << ec.message() << ")" << std::endl;
                }));
    }
    
  2. 您可以将执行器与 IO 对象相关联,例如像这样:

    koebenhavn(boost::asio::io_context& ctx)
        : socket_(make_strand(ctx))
    { }
    

    现在您可以使用 (boost::asio::associated_executor(socket_)socket_.get_executor() 当您在该套接字上启动任何异步操作时让库默认执行此操作。

    template <typename Buffers>
        void writing(Buffers&& buffers)
    {
        /* io worker may call back at arbitrary amount of transferred bytes */
        async_write(
            socket, std::forward<Buffers>(buffers),
            [self = shared_from_this()](error_code ec, size_t transferred) {
                std::cout << "Transferred " << transferred << " bytes "
                          << "(" << ec.message() << ")" << std::endl;
            });
    }
    
  3. 注意到事情变得简单了吗?是时候让它复杂化了。您的代码默认假设 writing(...) 将被安全调用,即从链中调用。由于我们不确定,请考虑发布或发送到 strand:

    dispatch( //
        socket_.get_executor(),
        [this, self, b = std::forward_as_tuple(buffers)]() mutable {
            async_write( //
                socket_, std::get<0>(b),
                [self](error_code ec, size_t transferred) {
                    std::cout << "Transferred " << transferred << " bytes "
                              << "(" << ec.message() << ")" << std::endl;
                });
        });
    

    使用 dispatch 的好处是图书馆可以 运行 如果它检测到您已经在路上,则可以立即执行任务。

现场演示

Live On Coliru

#include <boost/asio.hpp>
#include <iostream>

class koebenhavn : public std::enable_shared_from_this<koebenhavn> {
  private:
    boost::asio::ip::tcp::socket socket_;
    using const_buffer = boost::asio::const_buffer;
    using error_code   = boost::system::error_code;
    using strand       = boost::asio::io_context::strand;

  public:
    koebenhavn(boost::asio::io_context& ctx)
        : socket_(make_strand(ctx))
    { }

    // io worker may call back at arbitrary amount of transferred bytes
    template <typename Buffers> void writing(Buffers&& buffers)
    {
        auto self = shared_from_this();
        dispatch( //
            socket_.get_executor(),
            [this, self, b = std::forward_as_tuple(buffers)]() mutable {
                async_write( //
                    socket_, std::get<0>(b),
                    [self](error_code ec, size_t transferred) {
                        std::cout << "Transferred " << transferred << " bytes "
                                  << "(" << ec.message() << ")" << std::endl;
                    });
            });
    }
};

int main() {
    boost::asio::io_context io;
    auto k = std::make_shared<koebenhavn>(io);

    boost::asio::streambuf sb;

    k->writing(sb);

    std::string s = "hello world\n";
    k->writing(boost::asio::buffer(s));

    std::array<float, 7>       ff{};
    std::vector<unsigned char> bb{{0, 1, 2, 3, 4}};

    k->writing(std::vector{
        boost::asio::buffer(ff),
        boost::asio::buffer(bb),
    });
}