asio专题--asio连续异步发送的问题(转)

io_service/io_context如何run?

io_service的run接口是阻塞的,会阻塞当前线程,这对于服务端来说没什么影响,它只要异步接受客户端请求之后调用run接口即可,一直阻塞等待后续的连接到来就行了。

但是对于客户端来说阻塞当前进程会有问题,因为阻塞了当前线程的话,用户就无法自由地发送消息了。所以对于客户端来说一般是放在一个线程中run,这样就可以避免阻塞当前线程了。 其实还有另外一个原因,如果在主线程中调用run之前连续发消息,消息无法及时发送出去,因为io_service并没有启动起来,无法处理异步事件,因此,应该保证io_service在异步操作发起的时候就要run起来,放到线程中 就可以保证让io_service及时跑起来了。

连续异步发送的问题

使用asio的异步接口需要注意一些问题,比如不能同时发起多个异步操作,需要通过异步循环的方式发起异步操作。这对于异步读来说倒不是问题,因为在异步读的回调函数 里发起异步读就行了。但是对于需要连续发送消息的场景就有点问题了,因为用户并不知道异步发送何时完成,用户不可能等待前面的发送结束之后再发送消息,如果用户连续发起多个异步写操作 ,会导致消息的乱序发送,还会导致asio的未定义行为。

因此,如何让用户可以随时连续发送消息是一个需要解决的问题。

对于这个问题boost.asio文档中提供了一个经典的解决方法,在client中放一个发送队列,用户发消息的时候检查一下当前发送队列是否为空,如果为空就直接发送,否则就放到队列中。 内部的发送接口会不断从队列中取出数据发送直到发送完所有数据为止。

  void write(const chat_message& msg)
  {
    boost::asio::post(io_context_,
        [this, msg]()
        {
          bool write_in_progress = !write_msgs_.empty();
          write_msgs_.push_back(msg);
          if (!write_in_progress)
          {
            do_write();
          }
        });
  }

  void do_write()
  {
    boost::asio::async_write(socket_,
        boost::asio::buffer(write_msgs_.front().data(),
          write_msgs_.front().length()),
        [this](boost::system::error_code ec, std::size_t /*length*/)
        {
          if (!ec)
          {
            write_msgs_.pop_front();
            if (!write_msgs_.empty())
            {
              do_write();
            }
          }
          else
          {
            socket_.close();
          }
        });
  }

这里有一个值得注意的细节,write发送msg的时候是通过asio::post方式将msg放到发送队列中的,为什么要用asio::post, 而不是直接放到队列中呢? 之所以这样做,是为了保证发送队列的线程安全。asio::post实际上是一个线程池,把消息入队放到线程池中了,从而保证了发送队列的线程安全。那么, 为什么发送队列会有线程安全问题?前面提到,为了不阻塞当前主线程,将io_service的run放到一个线程中run,而我们发送消息是在主线程中,异步发送是在io线程中, 所以是多线程操作发送队列需要保证线程安全,通过post到线程池可以做到线程安全。除了post方式之外,我们还可以自己加锁来保证发送队列的线程安全性。

    void write0(std::string msg) {
        {
            std::unique_lock<std::mutex> lock(mtx_);
            write_msgs_.emplace_back(std::move(msg));
            if (write_msgs_.size() > 1) {
                return;
            }
        }

        write0();
    }

    void write0() {
        auto& msg = get_msg();
        auto write_size = (uint32_t)msg.size();
        std::array<boost::asio::const_buffer, 2> write_buffers;
        write_buffers[0] = boost::asio::buffer(&write_size, sizeof(uint32_t));
        write_buffers[1] = boost::asio::buffer(msg.data(), write_size);

        boost::asio::async_write(socket_, write_buffers,
            [this](boost::system::error_code ec, std::size_t length) {
            if (ec) {
                close();
                std::cout << ec.message() << '\n';
                return;
            }

            std::unique_lock<std::mutex> lock(mtx_);
            write_msgs_.pop_front();

            if (!write_msgs_.empty()) {
                lock.unlock();
                write0();
            }
        });
    }

    std::string& get_msg() {
        std::unique_lock<std::mutex> lock(mtx_);
        auto& msg = write_msgs_.front();
        return msg;
    }    

两种发送方式性能比较

post方式和自己加锁的方式效率是差不多,实际上自己加锁的方式入队的速度会快一点点,因为加锁的粒度小一点,而异步发消息效率二者是相当的。

 


    出自:purecpp 欢迎关注微信公众号: purecpp

    地址: www.purecpp.org

    转载请注明出处!

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
如果你想在 C++ 中使用 MySQL,并且不依赖于 Boost 库,你可以考虑使用第三方库 mysql-async。mysql-async 提供了一个基于非阻塞的异步操作接口,使用 asio 库进行底层网络通信。 以下是一个简单的示例代码,展示了如何使用 mysql-async 连接到 MySQL 数据库并执行查询操作: ```cpp #include <iostream> #include <mysql_async/mysql_async.h> int main() { boost::asio::io_service io_service; // 创建 MySQL 连接对象 mysql_async::connection conn(io_service); // 异步连接到数据库 conn.async_connect("tcp://127.0.0.1:3306", "user", "password", [](const boost::system::error_code& ec) { if (!ec) { std::cout << "Connected to MySQL server" << std::endl; // 异步执行查询操作 conn.async_query("SELECT * FROM table_name", [](const boost::system::error_code& ec, mysql_async::resultset_ref result) { if (!ec) { std::cout << "Query executed successfully" << std::endl; // 遍历结果集 while (result->next()) { std::cout << "Column 1: " << result->get_string(0) << std::endl; std::cout << "Column 2: " << result->get_string(1) << std::endl; // ... } } else { std::cout << "Error executing query: " << ec.message() << std::endl; } }); } else { std::cout << "Error connecting to MySQL server: " << ec.message() << std::endl; } }); // 运行异步操作 io_service.run(); return 0; } ``` 请注意,你需要在代码中包含 `mysql_async/mysql_async.h` 头文件,并将 mysql-async 库链接到你的项目中。 这只是一个简单的示例,你可以根据 mysql-async 的文档和示例进行更复杂的操作,如插入、更新、删除等。你可以在 mysql-async 的 GitHub 仓库中找到更多的信息和用法示例:https://github.com/mysql-net/mysql-async

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值