Boost Asio 异步输入输出

 C++
时间:

I/O 服务与I/O 对象

使用Boost的Asio进行异步处理数据的应用程序基于两个概念:

  • I/O服务
  • I/O对象

I/O服务抽象了操作系统的接口,允许第一时间进行异步数据处理,而I/O对象则初始化特定的操作。

// I/O服务
boost::asio:io_service

// 用于网络发送和接收数据
boost::asio::ip::tcp::socket

// 计时器
boost::asio::deadline_timer
#include<boost/asio.hpp>
#include<iostream>

void handler1(const boost::system::error_code &ec)
{
    std::cout << "5 s." << std::endl;
}

void handler2(const boost::system::error_code &ec)
{
    std::cout << "10 s." << std::endl;
}

int main()
{
    //1 初始化 I/O服务
    boost::asio::io_service io_service;
    //2 初始化I/O对象timer,使用boost::posix_time进行时间处理
    boost::asio::deadline_timer timer1(io_service, boost::posix_time::seconds(5));
    //3 该函数调用会立即返回
    timer1.async_wait(handler1);
    boost::asio::deadline_timer timer2(io_service, boost::posix_time::seconds(10));
    timer2.async_wait(handler2);
    //4 启动异步处理,进入阻塞状态
    io_service.run();
    return 0;
}

可扩展性与多线程

用boost asio这样的库开发程序,与一般的C++风格不同。哪些可能需要较长时间才返回的函数不再以顺序的方式来调用。不在是调用阻塞式的函数,boost asio是启动一个异步操作。而那些需要在操作结束后调用的函数则实现为相应的句柄。这种方法的缺点是,本来顺序执行的功能变得在物理上分割开来了,从而令相应的代码更难理解。
象 Boost asio 这样的库通常是为了令应用程序具有更高的效率。应用程序不在需要等待特定的函数执行完成,而可以在期间执行其它任务,如开始另一个需要较长时间的操作。

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

void handler1(const boost::system::error_code &ec)
{
    std::cout << "5 s." << std::endl;
}

void handler2(const boost::system::error_code &ec)
{
    std::cout << "5 s." << std::endl;
}

// 使用两个 I/O 服务
boost::asio::io_service io_service1;
boost::asio::io_service io_service2;

void run1()
{
    io_service1.run();
}

void run2()
{
    io_service2.run();
}

int main()
{
    boost::asio::deadline_timer timer1(io_service1, boost::posix_time::seconds(5));
    timer1.async_wait(handler1);
    boost::asio::deadline_timer timer2(io_service2, boost::posix_time::seconds(5));
    timer2.async_wait(handler2);
    boost::thread thread1(run1);
    boost::thread thread2(run2);
    thread1.join();
    thread2.join();
}

这个应用程序的功能与前一个相同。在一定条件下使用多个I/O服务是有好处的,每个I/O服务有自己的线程,最好是运行在各自的处理器内核上,这样每一个异步操作连同它们的句柄就可以局部化执行。如果没有远端的数据或函数需要访问,那么每一个I/O服务就像一个小的自主应用。这里的局部和远端是指像高速缓存,内存页这样的资源。由于在确定优化策略之前需要对底层硬件,操作系统,编译器以及潜在的瓶颈有专门的了解,所以应该仅在清楚这些好处的情况下使用多个I/O服务。

网络编程

虽然 boost.asio 是一个可以异步处理任何种类数据的库,但是它主要被用于网络编程。这是由于,事实上Boost.Asio在加入其他I/O对象之前很久就已经支持网络功能了。网络功能是异步处理的一个很好的例子,因为通过网络进行数据传输可能会需要较长时间,从而不能直接获得确认或错误条件。

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

boost::asio::io_service io_service; 
// 域名解析器
boost::asio::ip::tcp::resolver resolver(io_service); 
boost::asio::ip::tcp::socket sock(io_service); 
boost::array<char, 4096> buffer; 

void read_handler(const boost::system::error_code &ec, std::size_t bytes_transferred) 
{ 
  if (!ec) 
  { 
    std::cout << std::string(buffer.data(), bytes_transferred) << std::endl; 
    sock.async_read_some(boost::asio::buffer(buffer), read_handler); 
  } 
} 

void connect_handler(const boost::system::error_code &ec) 
{ 
  if (!ec) 
  { 
    boost::asio::write(sock, boost::asio::buffer("GET / HTTP 1.1\r\nHost: graycatya.com\r\n\r\n")); 
    sock.async_read_some(boost::asio::buffer(buffer), read_handler); 
  } 
} 

void resolve_handler(const boost::system::error_code &ec, boost::asio::ip::tcp::resolver::iterator it) 
{ 
  if (!ec) 
  { 
    sock.async_connect(*it, connect_handler);
  } 
} 

int main() 
{ 
  boost::asio::ip::tcp::resolver::query query("www.graycatya.com", "80"); 
  resolver.async_resolve(query, resolve_handler); 
  io_service.run(); 
}
  • boost::asio::ip::tcp::resolver

域名解析也是一个需要连接到互联网的过程。 有些专门的PC,被称为DNS服务器,其作用就象是电话本,它知晓哪个IP地址被赋给了哪台PC。 由于这个过程本身的透明的,只要明白其背后的概念以及为何需要。

  • async_resolve

一旦域名解析成功或被某个错误中断,resolve_handler() 函数就会被调用。

软件执行流程

  1. 应用创建一个类型(bost::asio::ip::tcp::resolver::query)对象query,表示一个查询,这个查询被传递给async_resolve()方法以解析该域名。
  2. 当域名解析完成后,resolve_handler()被调用 访问I/O对象sock,用由迭代器it所提供的解析后地址创建一个连接,如果解析成功,则存有错误条件的对象ec被设为0,只有在这种情况下,才会相应地访问socket以创建连接。服务器地地址是通过类型为boost::asio::ip::tcp::resolver::iterator提供。
  3. 调用了 async_connect()方法之后,connect_handler()会被自动调用。在该句柄地内部,会访问ec对象以检查连接是否已建立。如果连接是有效的,则对相应的socket调用async_read_some()方法,启动读数据操作。为保存接收到的数据,要提供一个缓冲区作为第一个参数。在以下例子中,缓冲区类型是boost::array。
  4. 每当有一个或多个字节被接收并保存至缓冲区时,read_handler()函数就会被调用。准确的字节数通过std::size_t 类型的参数 bytes_transferred 给出。同样的规则,该句柄应该首先看看参数ec以检查有没有接收错误。如果是成功接收,则将数据写出至标准输出流。
    注意:read_handler() 在将数据写出至std::cout之后,会再次调用async_read_some()方法。这是必需的,因为无法保证仅在一次异步操作中就可以接收到整个网页。async_read_some() 和 read_handler() 的交替调用只有当连接被破坏时才中止,如当web服务器已经传送完整个网页时。这种情况下,在read_handler() 内部将报告一个错误,以防止进一步将数据输出至标准输出流,以及进一步对该socket调用async_read() 方法。

0 评论