存档在 ‘boost.asio’ 分类

异步编程时boost::bind中慎用this指针

2014年4月18日

在类的成员里有时候实现了异步操作,异步的回调函数仍为该类的成员函数,比如下面。

    async_write(m_socket, boost::asio::buffer(msg.c_str(), msg.size()),
        bind(&Games::HandSend, this,
            boost::asio::placeholders::error));

上面这个例子是一个很常见的boost.asio的异步执行代码,如果没认真看很容易忽略了bind参数中的“this”参数,异步执行过程中另一个线程会持有这个”this”指针并在未知的时间里回调HandSend方法,而在持有这个指针的时间内,这个线程并不知道当前对象有可能已经被销毁,所以造成很低级的悬空指针。

正确的做法应该是:

    async_write(m_socket, boost::asio::buffer(msg.c_str(), msg.size()),
        bind(&Games::HandSend, shared_from_this(),
            boost::asio::placeholders::error)
    );

在asio中使用shared_from_this智能指针,该使用的时候必须使用,否则几乎肯定会发生问题。

来分析分析什么时候该用this,什么使用该用shared_from_this()。

1.使用this的地方举例

1.1示例链接

在主函数中定义了tcp_server对象,这个对象会一直存在栈中,而对于每次accept的连接,都是new出来的tcp_connection。

  void start_accept()
  {
    tcp_connection::pointer new_connection =
      tcp_connection::create(acceptor_.get_io_service());

    acceptor_.async_accept(new_connection->socket(),
        boost::bind(&tcp_server::handle_accept, this, new_connection,
          boost::asio::placeholders::error));
  }

这里使用了一个工厂方法隐藏了new的代码,在bind中使用了this,因为tcp_server对象存在栈中,而且tcp_server对象并不是一个局部变量,不用担心他会在其他地方被释放掉。

1.2示例链接

server(boost::asio::io_service& io_service, short port)
    : io_service_(io_service),
      acceptor_(io_service, tcp::endpoint(tcp::v4(), port))
  {
    session_ptr new_session(new session(io_service_));
    acceptor_.async_accept(new_session->socket(),
        boost::bind(&server::handle_accept, this, new_session,
          boost::asio::placeholders::error));
  }

  void handle_accept(session_ptr new_session,
      const boost::system::error_code& error)
  {
    if (!error)
    {
      new_session->start();
    }

    new_session.reset(new session(io_service_));
    acceptor_.async_accept(new_session->socket(),
        boost::bind(&server::handle_accept, this, new_session,
          boost::asio::placeholders::error));
  }

类server中的两个函数和以上的解释一样。

2.未使用this的地方,使用share_from_this举例

以下的两个链接还是和上边两个链接分别相同,因为这两段程序中都使用了this与share_from_this,这能很好对比。

2.1示例链接,与1.1对比

void start()
  {
    message_ = make_daytime_string();

    boost::asio::async_write(socket_, boost::asio::buffer(message_),
        boost::bind(&tcp_connection::handle_write, shared_from_this(),
          boost::asio::placeholders::error,
          boost::asio::placeholders::bytes_transferred));
  }

tcp_server的bind中使用了this,而这个tcp_connection中使用share_from_this(),因为这个cp_connection对象是存在堆中,被new出来的。

 2.2示例链接,与1.2对比

void start()
  {
    using namespace std; // For time_t, time and ctime.
    time_t now = time(0);
    shared_const_buffer buffer(ctime(&now));
    boost::asio::async_write(socket_, buffer,
        boost::bind(&session::handle_write, shared_from_this()));
  }

server的bind中使用了this,而这个session中使用share_from_this(),因为这个session对象是存在堆中,被new出来的。

结论:

1.如果对象是存在于栈中则必须使用this,否则出现bad_weak_ptr异常。
2.如果对象存在于堆中则尽量使用share_from_this(),使用this的前提是你能保证这个对象一直存在(比如全局对象,主函数中具有全局生命的对象,静态对象)

异步时使用局部变量注意点

2014年4月17日

在类udp_server中,有下面一段异步接收数据的代码。

		socket_.async_receive_from(
			boost::asio::buffer(recv_buffer_), remote_endpoint_,
			boost::bind(&udp_server::handle_receive, this,
			boost::asio::placeholders::error));

在处理函数handle_receive中,需要将当前的日期字符串发给客户端,异步编程一般都需要使用bind来实现回调,以下为正确的代码:

	void handle_receive(const boost::system::error_code& error)
	{
		if (!error || error == boost::asio::error::message_size)
		{
			boost::shared_ptr<std::string> message(
				new std::string(make_daytime_string()));
			socket_.async_send_to(boost::asio::buffer(*message), remote_endpoint_,
				boost::bind(&udp_server::handle_send, this, message));
			start_receive();
		}
	}

但是往往在非异步编程中,我们对message的处理并不会用智能指针来包裹,而是在栈上定义一个临时变量,比如

	void handle_receive(const boost::system::error_code& error)
	{
		if (!error || error == boost::asio::error::message_size)
		{
			std::string message = make_daytime_string();
			socket_.async_send_to(boost::asio::buffer(message), remote_endpoint_,
				boost::bind(&udp_server::handle_send, this, message));
			start_receive();
		}
	}

在handle_receive函数结构后,栈上的对象message就会被析构,但是异步asyc_仍旧在使用它,所以会出错。

我们可以有几种解决办法:

1.使用文章头部的代码,用智能指针。

2.将message定义成类的数据成员。

boost::asio::ip::tcp::resolver解析

2014年4月16日

Resolver是Asio的域名解析系统,它将指定的URL和服务转化为相应的ip和port的endpoint端点。

query指定主机与服务,比如

tcp::resolver::query query("www.unix8.net", "http");

endpoint指定主机IP地址与端口,比如

boost::asio::ip::address add;  
add = boost::asio::ip::address::from_string("222.216.160.69");
tcp::endpoint endpoint(add, 80);

//或者
tcp::endpoint endpoint(boost::asio::ip::address::from_string("222.216.160.69"), 80);

切记更新endpoint的第一个参数不能使用如下的方式

boost::asio::ip::address add;  
add.from_string("222.216.160.69");
tcp::endpoint endpoint(add, 80);

而要使用

boost::asio::ip::address add;  
add = add.from_string("222.216.160.69");
tcp::endpoint endpoint(add, 80);

或者上边说的方法,这是因为非静态的from_string返回的是一个临时对象,并不会修改add。

1.同步解析

同步解析共4个重载,返回值都为endpoint列表的头迭代器。

  1. 重载1,根据query来解析,未指定error_code
    iterator resolve(const query & q);

    出错会抛出异常。

  2. 重载2,指定query,指定error_cod
    iterator resolve(
        const query & q,
        boost::system::error_code & ec);

    出错会置error_code。
    以下两个重载函数为逆向解析。

  3. 重载3
    iterator resolve(
        const endpoint_type & e);

    根据endpoint逆向解析,出错抛出异常

  4. 重载4
    iterator resolve(
        const endpoint_type & e,
        boost::system::error_code & ec);

    根据endpoint逆向解析,出错置error_code.
    来看同步解析的例子

    #include "stdafx.h"
    #include <iostream>
    #include <boost/array.hpp>
    #include <boost/asio.hpp>
    #include <boost/bind.hpp>
    using boost::asio::ip::tcp;
    
    int main()
    {
    	try
    	{
    		boost::asio::io_service io_service;
    		tcp::resolver resolver(io_service);
    		tcp::resolver::query query("www.unix8.net", "http");
    		boost::system::error_code e;
    		tcp::resolver::iterator iter = resolver.resolve(query, e);
    		tcp::resolver::iterator end;
    		while (iter != end)
    		{
    			tcp::endpoint ep = *iter++;
    			std::cout<<ep<<std::endl;
    		}
    	}
    	catch (std::exception& e)
    	{
    		std::cerr << e.what() << std::endl;
    	}
    	system("pause");
    	return 0;
    }

2.异步解析

异步解析有两个重载,异步解析的结构是通过迭代器返回。

  1. 重载1,正向解析,根据query来解析。
    template<
        typename ResolveHandler>
    void-or-deduced async_resolve(
        const query & q,
        ResolveHandler handler);
    void handler(
      const boost::system::error_code& error, 
      resolver::iterator iterator          
    );
  2. 重载2,逆向解析
    template<
        typename ResolveHandler>
    void-or-deduced async_resolve(
        const endpoint_type & e,
        ResolveHandler handler);

    异步回调函数的需要如下的结构

    void handler(
      const boost::system::error_code& error,
      resolver::iterator iterator    
    );

    来看下异步解析的例子

    #include "stdafx.h"
    #include <iostream>
    #include <boost/array.hpp>
    #include <boost/asio.hpp>
    
    using boost::asio::ip::tcp;
    void printErr(const boost::system::error_code& error, tcp::resolver::iterator iter)
    {
    	if(!error)
    	{
    		tcp::resolver::iterator end;
    		while (iter != end)
    		{
    			tcp::endpoint endpoint = *iter++;
    			std::cout << endpoint << std::endl;
    		}
    	}
    }
    
    int main()
    {
    	try
    	{
    		boost::asio::io_service io_service;
    		tcp::resolver resolver(io_service);
    		tcp::resolver::query query("www.unix8.net", "http");
    		resolver.async_resolve(query, 
    			boost::bind(&printErr,boost::asio::placeholders::error,
    			boost::asio::placeholders::iterator));
    		io_service.run();
    	}
    	catch (std::exception& e)
    	{
    		std::cerr << e.what() << std::endl;
    	}
    	system("pause");
    	return 0;
    }

    这里通过解析www.unix8.net的http服务,返回IP与端口。这里特别注意就是再bind中的参数。异步中要用boost::asio::placeholders::error与boost::asio::placeholders::iterator。

    namespace
    {
      boost::arg<1>& error
        = boost::asio::placeholders::detail::placeholder<1>::get();
      boost::arg<2>& bytes_transferred
        = boost::asio::placeholders::detail::placeholder<2>::get();
      boost::arg<2>& iterator
        = boost::asio::placeholders::detail::placeholder<2>::get();
      boost::arg<2>& signal_number
        = boost::asio::placeholders::detail::placeholder<2>::get();
    } // namespace

    以上为在异步中,和同步中不同的参数。下面的代码是逆向解析的片段。

		tcp::endpoint endpoint(boost::asio::ip::address::from_string("222.216.160.69"), 80);
		boost::system::error_code e;
		tcp::resolver::iterator iter = resolver.resolve(endpoint, e);

Boost::Asio::Error 与boost::asio::placeholders

2014年4月14日

一般而言我们创建用于接收error的类型大多声明如下:

boost::system::error_code error

我们用这个类型去接受在函数中产生的错误,如

socket.connect(endpoint, error);

如果连接失败,错误类型会保存到error中,比如连接主机失败可能会返回这样的错误:

boost::asio::error::host_not_found;

通过if (error)检测到error后,抛出异常

throw boost::system::system_error(error);

需要注意的是,我们的error被 转化成 system_error了,显示错误很简单了

std::cout << e.what();

然而还有一点在异步调用的时候产生的异常error 的传递是个问题,因为异步会立刻返回,局部变量是会被销毁的,boost::asio::placeholders::error,将会保存异常的状态,这样我们使用异步调用时如socket::async_write_some的时候不用自己创建boost::system::error_co error 了,直接使用boost::asio::placeholders::error作为参数即可,同理,我们sync_write_some需要返回读写数据的大小,令人开心的是boost::asio::placeholders::bytes_transferred直接作为参数就可以保存数据大小。

实例如下:

boost::asio::async_write(socket_, boost::asio::buffer(message_),
  boost::bind(&tcp_connection::handle_write, shared_from_this(),
  boost::asio::placeholders::error,
  boost::asio::placeholders::bytes_transferred));
}

参考手册上说的很明确, boost::asio::placeholders::error,boost::asio::placeholders::bytes_transferred就是为异步调用使用bind的时候设计的。当然了boost::system::error_co error还用有用的,同步调用的时候我们就用它作为参数如:

boost::system::error_code error;
size_t len = socket.read_some(boost::asio::buffer(buf), error);

同样在异步调用的回调handle中也用它作参数如

void handle_write(const boost::system::error_code& /*error*/,size_t /*bytes_transferred*/)
{
}

总结就是说异步就用 boost::asio::placeholders::error,boost::asio::placeholders::bytes_transferred,同步就用boost::system::error_code。

boost.asio中io_service分析

2014年4月14日

本文从源代码的角度进行来看看io_service

boost::asio::io_service io_service;

当使用同步方式编程时,没有io_service.run()调用;使用异步方式编程时,则有此调用,来看看这个函数实际上在做什么

std::size_t io_service::run()
{
  boost::system::error_code ec;
  std::size_t s = impl_.run(ec);
  boost::asio::detail::throw_error(ec);
  return s;
}

可以看出,实际上的主要工作已经委托给impl_,impl的本来意思就是执行,实施的意思。

既然这里使用了数据成员impl_,就不得不先来看看io_service类的组成为何方圣神,抛开一切函数,来看实际的数据成员,

#if defined(BOOST_ASIO_WINDOWS) || defined(__CYGWIN__)
  detail::winsock_init<> init_;
#elif defined(__sun) || defined(__QNX__) || defined(__hpux) || defined(_AIX) \
  || defined(__osf__)
  detail::signal_init<> init_;
#endif

  // The service registry.
  boost::asio::detail::service_registry* service_registry_;

  // The implementation.
  impl_type& impl_;

其中的impl_实际上为

typedef detail::io_service_impl impl_type;

既然调用的是impl_.run(),不得不看这个run函数在干什么,在看run函数之前,得来看看impl_这个类型?

#if defined(BOOST_ASIO_HAS_IOCP)
  typedef class win_iocp_io_service io_service_impl;
  class win_iocp_overlapped_ptr;
#else
  typedef class task_io_service io_service_impl;
#endif

从这里看出,impl_在windows下使用的是windows IOCP模型,即win_iocp_io_service,在Linux下估计是使用的是EPOLL模型,但是没有细看,不知道是不是,反正这里命名为task_io_service。

class win_iocp_io_service
  : public boost::asio::detail::service_base<win_iocp_io_service>

class task_io_service
  : public boost::asio::detail::service_base<task_io_service>

在win_iocp_io_service类里可以看见调用的是windows系统提供的SDK里的文件。

这说明asio在windows里使用win_iocp_io_service来处理任务,因为io_service.run实际上调用的是win_iocp_io_service.run(),所以来看看该函数。

size_t win_iocp_io_service::run(boost::system::error_code& ec)
{
  if (::InterlockedExchangeAdd(&outstanding_work_, 0) == 0)
  {
    stop();
    ec = boost::system::error_code();
    return 0;
  }

  win_iocp_thread_info this_thread;
  thread_call_stack::context ctx(this, this_thread);

  size_t n = 0;
  while (do_one(true, ec))
    if (n != (std::numeric_limits<size_t>::max)())
      ++n;
  return n;
}

到这里可以看出,最终交给了以下函数来处理:

size_t win_iocp_io_service::do_one(bool block, boost::system::error_code& ec)

从这个函数的详细实现里可以看出,大概意思就是从队列中取操作来执行。

综上可以得出,io_service实际上就是利用iocp模型从队列中获取操作来执行。

 

io_servie 任务队列中的任务由io_service类的post函数提供

Post向队列中投递任务,然后激活空闲线程执行任务。其实现流程如下:

1. Post接收handler作为参数,实际上是个仿函数,通过此仿函数构造出completion_handler对象,completion_handler继承自operation。然后调用post_immediate_completion。

 typename op::ptr p = { boost::asio::detail::addressof(handler),
    boost_asio_handler_alloc_helpers::allocate(
      sizeof(op), handler), 0 };
  p.p = new (p.v) op(handler);

  BOOST_ASIO_HANDLER_CREATION((p.p, "io_service", this, "post"));

  post_immediate_completion(p.p, false);

2.post_immediate_completion首先将outstanding_work_增加,然后调用post_deferred_completion。

  void post_immediate_completion(win_iocp_operation* op, bool)
  {
    work_started();
    post_deferred_completion(op);
  }

3.post_deferred_completion首先加锁将任务入列,然后调用wake_one_thread_and_unlock

void win_iocp_io_service::post_deferred_completion(win_iocp_operation* op)
{
  // Flag the operation as ready.
  op->ready_ = 1;

  // Enqueue the operation on the I/O completion port.
  if (!::PostQueuedCompletionStatus(iocp_.handle, 0, 0, op))
  {
    // Out of resources. Put on completed queue instead.
    mutex::scoped_lock lock(dispatch_mutex_);
    completed_ops_.push(op);
    ::InterlockedExchange(&dispatch_required_, 1);
  }
}

4.wake_one_thread_and_unlock尝试唤醒当前空闲的线程,其实现中特别之处在于,若没有空闲线程,但是有线程在执行task->run,先阻塞。

5.  first_idle_thread_维护了所有当前空闲线程,每次唤醒时只唤醒空闲线程的第一个。

Boost“无法打开文件“libboost_thread-vc110-mt-gd-1_55.lib””配置

2014年4月8日

使用Boost库编译时出错“无法打开文件“libboost_thread-vc110-mt-gd-1_55.lib”

解决办法:

重新使用VS2012开发人员命令提示进入boost目录后运行bjam.exe,如果先前没有运行过“b2.exe  link=shared threading=multi variant=debug,release”,则先运行“b2.exe  link=shared threading=multi variant=debug,release”。

另外还得配置好VS工程,以我的为例:

添加包含目录D:\boost_1_55_0

添加库目录:D:\boost_1_55_0\libs;D:\boost_1_55_0\stage\lib

 

很多懒人不太想给每个工程都进行配置,可以把boost的库和头文件拷贝到VS安装目录下

VS2012默认include目录为:C:\Program Files\Microsoft Visual Studio 11.0\VC\include

VS2012默认lib目录为:C:\Program Files\Microsoft Visual Studio 11.0\VC\lib

把D:\boost_1_55_0(你的boost路径) boost头文件目录整个拷贝到VS2012 include目录中

把D:\boost_1_55_0\stage\lib目录里面所有lib文件拷贝到VS2012 lib目录中。