boost asio(初学示例)

Timer.1 - 使用同步定时器

这个示例程序通过展示在定时器中执行一个阻塞等待来介绍Asio

让我们从必须包含的头文件开始。

所有的Asio类只要简单的包含"asio.hpp"头文件便可使用。

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

因为本程序中使用了定时器,我们需要包含相应的的Boost.Date_Time 头文件来处理时间操作。

#include <boost/date_time/posix_time/posix_time.hpp>

使用Asio的所有程序都至少需要一个提供访问I/O功能的io_service 对象。因此在主函数中我们做的第一件事就是声明一个这个类型的对象。

int main()
{
  boost::asio::io_service io;

接下来我们声明一个boost::asio::deadline_timer类型的对象。作为 Asio的核心类,它提供的I/O功能(在此为定时器功能)通常用一个io_service 的引用作为其构造函数的第一个参数。第二个参数设置一个从现在开始5秒后终止的定时器。

  boost::asio::deadline_timer t(io, boost::posix_time::seconds(5));

在这个简单的程序中,我们用定时器演示一个阻塞等待。deadline_timer::wait() 函数调用直到定时器终止(从定时器被创建算起,五秒后终止)才会返回。

一个deadline timer 通常是下面两种状态中的一种:"expired(终止)" "not expired(不终止)"。如果deadline_timer::wait() 函数被一个已经终止的定时器调用, 它将立即返回。

  t.wait();

最后我们打印出 "Hello, world!" 信息以显示定时器已经终止。

  std::cout <<"Hello, world!\n";

  return0;
}





Timer.2 - 使用异步定时器

这个示例程序示范了如何通过修改Timer.1 中的程序,使用Asio的异步回调功能在定时器中演示一个异步等待。

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

使用Asio的异步功能意味着当一个异步操作完成时一个回调函数将被调用。在本程序中我们定义一个名为print 的函数,在异步等待结束后这个函数将被调用。

void print(const boost::system::error_code& /*e*/)
{
  std::cout <<"Hello, world!\n";
}

int main()
{
  boost::asio::io_service io;

  boost::asio::deadline_timer t(io, boost::posix_time::seconds(5));

接下来,我们调用 deadline_timer::async_wait() 函数执行一个异步等待去取代Timer.1例中的阻塞等待。当调用这个函数时我们传入上面定义的print回调句柄。

  t.async_wait(print);

最后,我们必须在io_service对象上调用io_service::run() 成员函数。

Asio保证回调句柄仅仅能被io_service::run()启动的当前线程所调用。 因此,如果io_service::run() 函数不执行,用于异步等待完成时的回调函数(在本例中为print函数)将永远不会被调用。

当仍旧有“工作”可做时,io_service::run() 函数会继续运行。在本例中,“工作”是定时器的异步等待,因此,直到定时器终止和回调函数执行完成,程序才会返回。

在调用io_service::run()之前确保给 io_service 一些工作去做,这非常重要。 例如,如果我们省略了上面调用的deadline_timer::async_wait() 函数,io_service对象将没有任何事情去做,因此io_service::run() 将立即返回。

  io.run();

  return0;
}

Timer.3 - 回调函数绑定参数

在本示例程序中我们将修改Timer.2中的例子,使定时器每秒被激活一次。例子将示范如何给你的函数指针传递附加参数。

#include <iostream>
#include <boost/asio.hpp>
#include <boost/bind.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>

使用Asio实现一个重复定时器,你必须在你的回调函数中去改变定时器的终止时间,然后开始一个新的异步等待。显然这意味着回调函数必须拥有改变定时器对象的权限。为此我们为 print函数增加两个新参数:

  • 一个指向定时器对象的指针。

  • 一个用于当定时器第6次被激活时我们可以中止程序的计数器。

void print(const boost::system::error_code& /*e*/,
    boost::asio::deadline_timer* t,int* count)
{

如上所示,示例程序使用了一个计数器,当定时器被第6次激活时,用来中止程序。然而,你将看到这里并没有显式地要求io_service对象中止。回忆示例2中,当没有更多“工作”去做时,io_service::run() 函数完成。在计数器达到5时,定时器并没有启动一个新的异步等待。该io_service执行完工作后停止运行。

  if(*count <5)
  {
    std::cout <<*count <<"\n";
    ++(*count);

接着,我们推迟定时器的终止时间。通过在原先的终止时间上增加延时,我们可以确保定时器不会在处理回调函数所需时间内到期。

    t->expires_at(t->expires_at()+ boost::posix_time::seconds(1));

接着我们在定时器中启动一个新的异步等待。我们必须使用boost::bind() 函数给你的回调函数绑定额外的参数,因为deadline_timer::async_wait() 函数只期望得到一个拥用 void(const boost::system::error_code&)签名的函数指针(或函数对象)。为你的print函数绑定附加的参数后,它就成为与签名精确匹配的函数对象。

查看Boost.Bind 文档文档以获得更多如何使用boost::bind()的信息。

在本例中,boost::bind()的boost::asio::placeholders::error参数是为了给回调函数传入一个error对象。当开始异步操作时,如果使用boost::bind(),你必须指定和回调函数的参数列表相匹配的一个参数。在示例4中,如果在回调函数中,这个参数不是必需的,这个占位符会被省略。

    t->async_wait(boost::bind(print,
          boost::asio::placeholders::error, t, count));
  }
}

int main()
{
  boost::asio::io_service io;

为了在定时器第6次被激活时终止程序,我们添加一个新的count变量。

  int count =0;
  boost::asio::deadline_timer t(io, boost::posix_time::seconds(1));

在第四步中,当在主函数中的调用deadline_timer::async_wait() 函数时,我们绑定print函数所需要的附加参数。

  t.async_wait(boost::bind(print,
        boost::asio::placeholders::error,&t,&count));

  io.run();

最后,为了证明count 变量在print 函数句柄中被使用,我们打印出它的值。

  std::cout <<"Final count is "<< count <<"\n";

  return0;
}









Timer.5 - 多线程同步回调

本示例程序示范了使用boost::asio::strand 类来创建多线程程序中的同步回调句柄。

前四个例程只是在单线程下使用io_service::run() 函数来避免处理函同步。 如你所见,Asio库保证回调句柄仅能被当前正在调用 io_service::run(). 函数的线程调用。 因此,在单线程中调用io_service::run() 能确保回调句柄不被并发运行。

单线程通常是使用Asio开发应用程序最好的方式。下面是Asio在程序中的局限性,尤其是服务器方面,包括:

  • 操作需要较长时间处理才能完成时弱响应。

  • 在大规模的多处理机系统中表现不佳。

如果你发现自己陷入这些局限时,一个可供选择的方法是创建一个每个线程都调用io_service::run() 的线程池。 不过,因为这允许并发操作,当访问一个共享、非线程安全的资源时,我们需要一个同步方式。

#include <iostream>
#include <boost/asio.hpp>
#include <boost/thread.hpp>
#include <boost/bind.hpp>
#include <boost/date_time/posix_time/posix_time.hpp>

让我们从定义一个名为printer的类开始,这与前一个示例中的类很相似。这个类是上一个例子的扩展,这里我们使用两个并行的定时器。

class printer
{
public:

除了初始化一对boost::asio::deadline_timer 成员变量外,构造函数还初始化一个boost::asio::strand类型strand_ 成员变量。

boost::asio::strand 对象保证:对于通过它来分派执行的众操作中,只有一个操作执行完成之后才允许进入下一个操作。 这种保证与多少个线程调用io_service::run() 无关。当然,如果不是通过一个boost::asio::strand对象分派, 或者通过其它不同的boost::asio::strand对象分派,这些操作仍旧可能是并发的。

  printer(boost::asio::io_service& io)
    : strand_(io),
      timer1_(io, boost::posix_time::seconds(1)),
      timer2_(io, boost::posix_time::seconds(1)),
      count_(0)
  {

当开始同步操作时,每一个回调句柄都使用boost::asio::strand对象进行“包装”。strand::wrap() 函数返回一个新的通过boost::asio::strand对象自动分派的内部句柄。 通过同一boost::asio::strand对象对句柄进行“ 包装”,我们可以保证操作不会并发执行。

    timer1_.async_wait(strand_.wrap(boost::bind(&printer::print1,this)));
    timer2_.async_wait(strand_.wrap(boost::bind(&printer::print2,this)));
  }

  ~printer()
  {
    std::cout <<"Final count is "<< count_ <<"\n";
  }

在一个多线程程序中,当访问同一共享资源时,异步操作必须是同步的。在本例中,print1 print2)函数使用的共享资源std::cout count_数据成员。

  void print1()
  {
    if(count_ <10)
    {
      std::cout <<"Timer 1: "<< count_ <<"\n";
      ++count_;

      timer1_.expires_at(timer1_.expires_at()+ boost::posix_time::seconds(1));
      timer1_.async_wait(strand_.wrap(boost::bind(&printer::print1,this)));
    }
  }

  void print2()
  {
    if(count_ <10)
    {
      std::cout <<"Timer 2: "<< count_ <<"\n";
      ++count_;

      timer2_.expires_at(timer2_.expires_at()+ boost::posix_time::seconds(1));
      timer2_.async_wait(strand_.wrap(boost::bind(&printer::print2,this)));
    }
  }

private:
  boost::asio::strand strand_;
  boost::asio::deadline_timer timer1_;
  boost::asio::deadline_timer timer2_;
  int count_;
};

main函数中, io_service::run() 现在被两个线程调用:主线程和一个附加线程。这一切依赖于boost::thread对象来完成。

正如它被一个单线程调用一样,io_service::run()的并发调用会一直持续到无任何“工作”可做。后台线程直到所有异步操作都完成后才会退出。

int main()
{
  boost::asio::io_service io;
  printer p(io);
  boost::thread t(boost::bind(&boost::asio::io_service::run,&io));
  io.run();
  t.join();

  return0;
}







Daytime.1 - 同步TCP daytime客户端

本示例程序显示如何使用Asio来实现一个TCP客户端程序。

让我们从添加必需的头文件开始。

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

这个应用程序的目的是访问一个daytime服务器,因此我们需要用户去指定服务器。(如time-nw.nist.gov,IP亦可)。

using boost::asio::ip::tcp;

int main(int argc, char* argv[])
{
  try
  {
    if(argc !=2)
    {
      std::cerr <<"Usage: client <host>"<< std::endl;
      return1;
    }

所有使用asio的程序都至少需要一个io_service 对象。

    boost::asio::io_service io_service;

我们需要把服务器的名称转化为TCP的节点,而该名称是通过应用程序的参数指定的。我们使用ip::tcp::resolver 对象来完成。

    tcp::resolver resolver(io_service);

一个resolver对象获得一个query对象,并将其转换为节点列表.我们通过argv[1]中的服务器名称和服务名,在这里是 "daytime",构造一个query

    tcp::resolver::query query(argv[1],"daytime");   //相当与用名字换取ip的操作

节点列表用ip::tcp::resolver::iterator类型的迭代器返回。 返回的iterator将采用ip::tcp::resolver::iterator 的默认构造函数来构造。

    tcp::resolver::iterator endpoint_iterator = resolver.resolve(query);
    tcp::resolver::iterator end;

现在我们建立一个socket并连接之,由于获得的节点既有IPv4也有IPv6的。所以,我们需要依次尝试访问它们直到找到一个可以正常工作的。这样做可使得我们的程序独立于特定的IP版本。

    tcp::socket socket(io_service);
    boost::system::error_code error = boost::asio::error::host_not_found;
    while(error && endpoint_iterator != end)
    {
      socket.close();
      socket.connect(*endpoint_iterator++, error);
    }
    if(error)
      throw boost::system::system_error(error);

连接打开后,现在我们需要做的就是读取daytime服务器的响应。

我们使用boost::array来存放接收到的数据。 The boost::asio::buffer() 函数会自动确定array的长度来防止缓冲区溢出。我们也可以使用 char [] std::vector来代替boost::array

    for(;;)
    {
      boost::array<char,128> buf;
      boost::system::error_code error;

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

当服务器关闭连接时,ip::tcp::socket::read_some() 函数会以boost::asio::error::eof错误标志返回, 通过该错误标志,我们知道应该退出循环了。

      if(error == boost::asio::error::eof)
        break;// Connection closed cleanly by peer.
      elseif(error)
        throw boost::system::system_error(error);// Some other error.

      std::cout.write(buf.data(), len);
    }

最后,处理所有可能抛出的异常 。

  }
  catch(std::exception& e)
  {
    std::cerr << e.what()<< std::endl;
  }







Daytime.2 - 同步TCP daytime服务器

本示例示范如何使用Asio来实现一个TCP服务器程序。

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

using boost::asio::ip::tcp;

我们先定义一个make_daytime_string()来产生需要发送给客户端的字符串.这个函数会在我们所有的daytime服务器上被使用。

std::string make_daytime_string()
{
  usingnamespace std;// For time_t, time and ctime;
  time_t now = time(0);
  return ctime(&now);
}

int main()
{
  try
  {
    boost::asio::io_service io_service;

新建一个ip::tcp::acceptor 对象来监听新的连接。该对象应遵守IPv4协议,监听TCP端口13

    tcp::acceptor acceptor(io_service, tcp::endpoint(tcp::v4(),13));

这是一个iterative server,也就是说同一时间只能处理一个连接。建立一个表示与客户端的连接的socket, 然后等待客户端的连接。

    for(;;)
    {
      tcp::socket socket(io_service);
      acceptor.accept(socket);

当客户端访问服务器时,获取当前时间,并传送给客户端。

      std::string message = make_daytime_string();

      boost::system::error_code ignored_error;
      boost::asio::write(socket, boost::asio::buffer(message),
          boost::asio::transfer_all(), ignored_error);
    }
  }

最后,处理异常。

  catch(std::exception& e)
  {
    std::cerr << e.what()<< std::endl;
  }

  return0;
}



posted @ 2012-05-31 22:43  ghost&240  阅读(2127)  评论(0编辑  收藏  举报