经过一段时间的Asio使用,理清楚了一些基本的概念,这里和shared_ptr指针一起总结一下
1、Asio中,不管写(Write)还是读(Read)都需要等待相应的事件完成后再发起下一次写或者读。读操作比较好办,在handle_read事件中直接进行下一次async_read操作就可以,但是写的话得自己管理一个deque队例,在写入操作完成后则自动把最顶的数据包弹出,然后开始写下一个(如果在缓冲队列中还有剩余的数据包);
2、Asio中,不管任何的函数调用,若有未涉及error_code和涉及error_code的相同功能函数存在,则使用涉及error_code的函数调用,并且处理错误信息,否则io_service会因为异常而退出消息循环;
3、若session或其它的类是使用shared_ptr来包装的,则需要将该类继承于enable_shared_from_this,否则会有可能在该对象已经被删除的情况下,该对象内的异步回调函数被调用,这样会导致程序崩溃退出;
4、要注意,当类继承了enable_shared_from_this后,在构造函数中千万不要调用shared_from_this()函数,否则程序会抛掷异常;
5、如果类中有方法暴露在外,而有可能是非线程安全调用的,则使用io_service::post函数来调用asio中的函数,以保证asio的回调是线程安全的;

示例代码如下:(注:因为只是代码片断,随便手写,而且只是为了说明问题,所以并未检查过编译是否通过)

#include <deque>
#include <boost/asio.hpp>
#include <boost/shared_ptr.hpp>
#include <boost/bind.hpp>
#include <boost/enable_shared_from_this.hpp>
 
using namespace boost::asio;
using namespace boost::asio::ip;
 
typedef boost::shared_ptr<tcp::socket> socket_ptr;
typedef std::pair<void*, std::size_t> buffer_type;
typedef std::deque<buffer_type> buffer_deque;
 
class session : public boost::enable_shared_ptr_from_this<session>
{
public:
  session(io_service& ios, socket_ptr sp)
    : ios_(ios)
    , sp_(sp)
  {
  }
  void start_read()
  {
    async_read_until(*sp_, sb_, '\n', boost::bind(&session::handle_read, shared_from_this(), placeholders::error);
  }
  void send(void const* p, std::size_t size)
  {
    bool need_write = buffers_.empty();
    buffers_.push_back(std::make_pair(p, size));
    if (need_write) ios_.post(boost::bind(&session::do_send, shared_from_this()));
  }
  void close()
  {
    boost::system::error_code ec;
    sp_->shutdown(tcp::socket::shutdown_both, ec);
    if (ec) std::cout << ec.message().c_str() << std::endl;
    sp_->close(ec);
    if (ec) std::cout << ec.message().c_str() << std::endl;
  }
private: // do functions
  void do_send()
  {
    async_write(*sp, buffer(buffers_.begin()->first, buffers_.begin()->second), boost::bind(&sessions::handle_write, shared_from_this(), placeholders::error));
  }
private: // handlers
  void handle_read(boost::system::error_code const& ec)
  {
    if (!ec)
    {
      std::istream is(&sb_);
      std::string cmd;
      std::getline(is, cmd);
      // todo: handle command
      start_read(); // start next round
    }
    else
    {
      std::cout << ec.message().c_str() << std::endl;
      close();
    }
  }
  void handle_write(boost::system::error_code const& ec)
  {
    if (!ec)
    {
      buffers_.pop_front();
      if (!buffers_.empty()) do_send();
    }
    else
    {
      std::cout << ec.messages().c_str() << std::endl;
      close();
    }
  }
private:
  io_service& ios_;
  socket_ptr sp_;
  streambuf sb_;
  buffer_deque buffers_;
};

在asio库中的很多异步函数是需要调用者保持数据内存有效性一直到该异步方法被handle,这样如果按照正常的做法应该把该数据作为类的成员变量(这样在整个对象的生存期间该数据都有效了)。但是这样做却很不优雅,今天看到有这样的写法

class cmd_writer
{
public:
  template <typename C>
  void write_command(boost::asio::ip::tcp::socket& socket, int const iden, C const& cmd)
  {
    cmd_container_ptr p(new cmd_container(iden, cmd)); // 内存在这里被分配
    boost::asio::async_write(socket, boost::asio::buffer(p->get(), p->size()),
      boost::bind(&cmd_writer::handle_write, this,
        boost::asio::placeholders::error, boost::asio::placeholders::bytes_transferred,
        p // 在这里将该智能指针对象传递下去,直至handle函数启用后才删除
      )
    );
  }
 
private:
  void handle_write(boost::system::error_code const& err, std::size_t const bytes_transferred,
    cmd_container_ptr p)
  {
    if (!err)
    {
      p.reset(); // 最后到了这里才释放内存
    }
  }
};

这种写法使用boost:bind函数将需要保持的对象智能指针对象从异步调用函数内传递给该异步调用函数的handle函数,完美解决了异步调用中保持数据有效性的问题而却又不增加多余的成员函数。

io_service::run函数在没有任何任务的时候将会自动返回,这对于WTL的项目来说并不方便,之前我有一篇文章讲到使用一个循环来运行该run函数,那是十分不优雅的。今天在网上再次查找办法,终于找到io_service::work类可以使io_service::run函数在没有任务的时候仍然不返回,直至work对象被销毁。

boost::asio::io_service ios;
boost::asio::io_service::work work(ios); // 使用work对象
ios.run(); // 就算是当前没有任务,ios.run()也不会马上返回

或者,下面从我的程序中直接拷贝出来的例子,该程序使用WTL框架

boost::asio::io_service m_ios;
std::shared_ptr<boost::thread> m_ios_thread;
std::shared_ptr<tm_client> m_client_ptr;
std::shared_ptr<boost::asio::io_service::work> m_work;
 
m_work.reset(new boost::asio::io_service::work(m_ios));
m_ios_thread.reset(new boost::thread(boost::bind(&boost::asio::io_service::run, &m_ios)));
 
// resolve the hostname
boost::asio::ip::tcp::resolver resolver(m_ios);
boost::asio::ip::tcp::resolver::query q(hostname, boost::lexical_cast<std::string>(x::tl::PORT_TMSRV));
boost::asio::ip::tcp::resolver::iterator res_begin = resolver.resolve(q), res_end;
 
if (res_begin != res_end)
{
  // constructor client connection object
  m_client_ptr.reset(new tm_client(m_ios, tm_window_notifier(*this)));
  m_client_ptr->connect(*res_begin);
}

另外可以参考文章:Stopping the io_service from running out of work