boost::asio译文

 

Christopher Kohlhoff

Copyright © 2003-2012 Christopher M. Kohlhoff

以Boost1.0的软件授权进行发布(见附带的LICENSE_1_0.txt文件或从http://www.boost.org/LICENSE_1_0.txt)

Boost.Asio是用于网络和低层IO编程的跨平台C++库,为开发者提供了C++环境下稳定的异步模型.

综述

基本原理

应用程序与外界交互的方式有很多,可通过文件,网络,串口或控制台.例如在网络通信中,完成独立的IO操作需要很长时间.对应用程序开发者提出了一个挑战.

Boost.Asio 提供了管理需长时间运行操作的工具,但不必涉及到线程的并发模式和显示锁定.

Boost.Asio 库使用C++来实现,提供如网络编程等常用的操作系统接口. Boost.Asio实现了如下目标:

· 可移植性Portability.库支持一系列的常用系统操作,具有稳定的跨平台特性.

· 可扩展性Scalability.库可以帮助开发者构建数千并发连接的网络应用程序.库中实现的每个系统操作都使用了最具扩展性的机制.

· 效率Efficiency.库支持发散聚合IO(scatter-gather I/O)等技术,使应用程序尽量少的拷贝数据.

· 可以像BSD Sockets一样通过API函数建立模型概念. BSD Socket API应用广为人知,有很多相关的程序库.其他编程语言通常使用其简单的网络API接口.出于这些原因, Boost.Asio同样使用已存在的技术构建.

· 易于使用Ease of use.库提供了工具箱而不是框架来降低新手的入门门槛.可使用最少的时间投入来学习基本的规则和方针.而后,库的使用者只需要理解用到的特定函数即可.

· 基于更多的抽象.Basis for further abstraction.库提供了高层次的抽象允许其他库的开发者进行扩展.例如,实现常用的HTTP协议.

虽然Boost.Asio一开始定位于网络通信,但其异步IO的概念已经扩展到了如串口通信,文件描述符等其他操作系统的IO操作方面.

核心概念和功能

解析Boost.Asio

Boost.Asio 可用于如socket等IO对象的同步或异步操作.在使用Boost.Asio前了解Boost.Asio概念图, 及与应用程序的相互集成是很有帮助的.

第一个范例,看看处理socket连接的情况.首先从同步操作开始.

应用程序必须有一个io_service对象. io_service对象负责连接应用程序与操作系统的IO服务.

boost::asio::io_service io_service;

要执行IO操作应用程序需要一个像TCP Socket的IO对象:

boost::asio::ip::tcp::socket socket(io_service);

而后执行同步连接操作,发送如下事件:

1. 应用程序通过调用IO对象初始化连接操作:

socket.connect(server_endpoint);

2. IO对象向io_service 提出请求.

3. io_service 调用操作系统功能执行连接操作.

4. 操作系统向io_service 返回执行结果.

5. io_service将错误的操作结果翻译为boost::system::error_code类型. error_code可与特定值进行比较,或作为boolean值检测(false表示无错误).结果再传递给IO对象.

6. 如果操作失败,IO对象抛出boost::system::system_error类型的异常.开始操作的代码如下所示:

boost::system::error_code ec;

socket.connect(server_endpoint, ec);

而后error_code类型的变量ec被赋予操作的结果值,但不会抛出异常.

对于异步操作,事件顺序不同.

1. 应用程序调用IO对象进行连接操作:

socket.async_connect(server_endpoint, your_completion_handler);

your_completion_handler函数的签名为:

void your_completion_handler(const boost::system::error_code& ec);

执行的异步操作需要严格的函数签名.每种操作的合法形式可见参考文档.

2. IO对象请求io_service .

3. io_service 通知操作系统其需要开始一个异步连接.

时序过程.(在同步情况下等待包括连接操作时间.)

4. 操作系统指示连接操作完成, io_service从队列中获取操作结果.

5. 应用程序必须调用io_service::run()(或io_service相似的成员函数)以便于接收结果.调用io_service::run()会阻塞未完成的异步操作,因此可在启动第一个异步操作后调用这个函数.

6. 调用io_service::run()后,io_service返回一个操作结果,并将其翻译为error_code,传递到事件处理器中.

这是Boost.Asio的简单图形.更多特性可从文档中获取,如使用Boost.Asio执行其他类型的异步操作.

Proactor设计模式:无线程并发

Boost.Asio库同时支持同步和异步操作.异步支持基于Proactor设计模式.下面讨论这种方式与同步操作及Reactor方式相比的优缺点.

Proactor和Boost.Asio

现在讨论Proactor不依赖平台细节的特性.

Proactor 设计模式

— 异步操作:定义一个异步执行操作,如Socket异步读写.

— 异步操作处理器:执行异步操作并在操作完成后执行完成事件队列中的队列事件.从更高层次上说,服务端的stream_socket_service 就是一个异步操作处理器.

— 完成事件队列:缓冲完成事件,直到被异步事件信号分离器移出队列.

— 完成句柄:处理异步操作的结果.这是一个函数对象,通常使用boost::bind创建.

— 异步事件信号分离器:在完成事件队列中阻塞等待事件,受信后向调用者返回完成事件.

— Proactor :调用异步事件信号分离器将事件移出队列,并为这个事件分配一个完成句柄(如调用函数对象).这个功能封装在io_service类中.

— 初始化器: 执行特定程序代码启动异步操作.初始化器通过如basic_stream_socket等高层次接口与异步操作处理器交互,其返回stream_socket_service等类型的服务代理.

使用Reactor的实现

在很多平台上Boost.Asio按照Reactor来实现Proactor设计模式,如select,epoll或kqueue.相对于Proactor其实现方式如下:

— 异步操作处理器:Reactor使用select,epoll或kqueue机制实现.如果reactor指示运行操作的资源已经就位,处理器执行异步操作并在完成事件队列中插入相应的完成句柄.

— 完成事件队列:完成句柄的链表(如函数对象).

— 异步事件信号分离器:在事件或条件变量上等待,直到完成事件队列中的完成句柄可用.

实现Windows的重叠IO

在Windows NT,2000,和XP系统中, Boost.Asio使用重叠IO高效实现了Proactor设计模式:

— 异步操作处理器:由操作系统实现.操作调用如AcceptEx等重叠函数进行初始化.

— 完成事件队列:由操作系统实现,与IO完成端口相关联.每个io_service实例对应一个IO完成端口.

— 异步事件信号分离器:由Boost.Asio从队列中移除事件及其相关联的完成句柄.

优点

— 可移植性Portability:很多操作系统都提供原生的异步操作IO API(如Windows中的重叠IO),是开发者开发高性能网络应用程序的最佳选择.ASIO库可能实现了原生的异步IO.如果原生支持不可用,ASIO库可使用同步事件信号分离器实现典型的Reactor模式,如POSIX的select().

— 去除多线程并发的耦合

应用程序使用异步方式调用长时间运行的操作.但应用程序不必为增加并发而产生大量的线程.

每个连接一个线程的实现策略(仅需要同步的方式)--太多的线程会产生大量的CPU上下文切换,同步和数据移动,导致系统效率降低.异步操作通过最小化操作系统线程数量来可以避免线程上下文切换的代价--CPU是典型的有限资源,这时仅由激活的逻辑线程进行事件处理.

— 简单的应用程序同步.

异步操作完成句柄可以在已存在的单线程环境中进行设置,此时开发出来的应用程序逻辑清晰,不必涉及同步问题.

— 功能组合.

功能组合指实现高层次操作的功能,如按特定格式发送消息.每个功能的实现都是依赖于多次调用底层的读或写操作.

例如, 一个包含固定长度包头和变长包体的协议,包体长度在包头中指定.假设read_message操作由两个底层的读来实现,第一次接收包头,得到长度,第二次接收包体.

如果用异步方式来组合这些功能,可将这些异步调用连接到一起.前一个操作的完成句柄初始化下一个操作.封装链中的第一个操作的调用,使调用者不会意识到这个高层次的操作是按异步操作链的方式实现的.

按此方式可以很简单的向网路库中添加新的操作,开发出更高层次的抽象,如特定协议的功能和支持.

缺点

— 抽象复杂.

由于操作初始化和完成在时间和空间上是分离的,增加了使用异步机制开发程序的难度.而且控制流颠倒使应用程序很难调试.

— 内存占用.

在读和写过程中必须独占缓冲区空间,而后又变为不确定的,每个并发操作都需要一个独立的缓冲区.在Reactor模式中,换句话说,直到socket准备读或写时才需要缓冲区空间.

线程和Boost.Asio

线程安全

通常在每个并发操作中使用独立的对象是安全的,但并发操作中使用同一个对象就不安全了.然而如io_service 等类型可以保证并发操作中使用同一个对象也是安全的.

线程池

在多个线程中调用io_service::run()函数,就可以创建一个包含这些线程的线程池,其中的线程在异步操作完成后调用完成句柄.也可通过调用io_service::post()实现横跨整个线程池的任意计算任务来达到相同的目的.

注意所有加入io_service池的线程都是平等的,io_service可以按任意的方式向其分配工作.

内部线程

这个库的特定平台实现会创建几个内部线程来模拟异步操作.同时,这些线程对库用户是不可见的.这些线程特性:

  • 不会直接调用用户的代码
  • 阻塞所有信号

使用如下担保来实现:

  • 异步完成句柄只会由调用io_service::run()的线程来调用.

因此,由库的使用者来建立和管理投递通知的线程.

原因:

  • 在单线程中调用io_service::run(),用户代码避免了复杂的线程同步控制.例如,ASIO库用户可以使用单线程实现伸缩性良好的服务端(特定用户观点).
  • 线程启动后在其他应用程序代码执行前,ASIO库用户需要在线程中执行一些简短的初始化操作.例如在线程中调用微软的COM操作之前必须调用CoInitializeEx.
  • 将ASIO库接口与线程的创建和管理进行了解耦,确保可在不支持线程的平台中进行调用.

Strands:无显式锁定的线程

Strand被定义为严格按顺序调用(如无并发调用)事件句柄的机制.使用Strand可以在多线程程序中同步执行代码而无需显式地加锁(如互斥量等).

Strand可以按如下两种方式来隐式或显式的应用:

· 仅在一个线程中调用io_service::run()意味着所有的事件句柄都执行在一个隐式的Strand下,因为io_service保证所有的句柄都在run()中被调用.

· 链接中只有一个相关的异步操作链(如半双工的HTTP)是不可能并发执行句柄的.也是隐式Strand的情况.

· 显式Strand需要创建一个io_service::strand实例.所有的事件句柄函数对象都需要使用io_service::strand::wrap()进行包装,或使用io_service::strand进行投递或分发.

在组合异步操作中,如async_read() 或 async_read_until(),如果完成句柄使用一个Strand管理,则其他所有中间句柄都要由同一个Strand来管理.这可以确保线程安全的访问所有在调用者和组合的操作中共享的对象(例如socket中的async_read(),调用者可以调用close()来取消操作).这是通过在所有中间句柄中添加一个钩子函数实现的,在执行最终句柄前调用自定义的钩子函数:

struct my_handler

{

  void operator()() { ... }

};

 

template<class F>

void asio_handler_invoke(F f, my_handler*)

{

  // Do custom invocation here.

  // Default implementation calls f();

}

io_service::strand::wrap()函数生成一个新的定义了asio_handler_invoke的完成句柄,以便于Strand管理函数对象运行.

缓冲区

通常IO在连续的内存区域(缓冲区)内传输数据.这个缓冲区可以简单的认为是包含了一个指针地址和一些字节的东西.然而,为了高效的开发网络应用程序, Boost.Asio支持分散聚合操作,需要一个或多个缓冲区:

  • 一个分散读(scatter-read)接收数据并存入多缓冲区.
  • .一个聚合写(gather-write)传输多缓冲区中的数据.

因此需要一个抽象概念代表缓冲区集合.为此Boost.Asio定义了一个类(实际上是两个类)来代表单个缓冲区.可存储在向分散集合操作传递的容器中.

此外可将缓冲区看做是一个具有地址和大小的字节数组, Boost.Asio区别对待可修改内存(叫做mutable)和不可修改内存(后者在带有const声明的存储区域上创建).这两种类型可以定义为:

typedef std::pair<void*, std::size_t> mutable_buffer;
typedef std::pair<const void*, std::size_t> const_buffer;

这里mutable_buffer 可以转换为const_buffer ,但反之不成立.

然而,Boost.Asio没有使用上述的定义,而是定义了两个类: mutable_buffer 和 const_buffer.目的是提供了不透明的连续内存概念:

  • 类型转换上同std::pair.即mutable_buffer可以转换为const_buffer ,但反之不成立.
  • 可防止缓冲区溢出.对于一个缓冲区实例,用户只能创建另外一个缓冲区来代表同样的内存序列或子序列.为了更加安全,ASIOI库也提供了从数组(如boost::array 或 std::vector,或std::string)中自动计算缓冲区大小的机制.
  • 必须明确的调用buffer_cast函数进行类型转换.应用程序中通常不必如此,但在ASIO库的实现中将原始内存数据传递给底层的操作系统函数时必须如此.

最后将多个缓冲区存入一个容器中就可以传递给分散聚合操作了.(如read()write()).为了使用std::vector, std::list, std::vector 或 boost::array等容器而定义了MutableBufferSequence和ConstBufferSequence概念.

Streambuf与IoStream整合

类boost::asio::basic_streambuf从std::basic_streambuf继承,将输入输出流与一个或多个字符数组类型的对象相关联,其中的每个元素可以存储任意值.这些字符数组对象是内部的streambuf对象,但通过直接存取数组中的元素使其可用于IO操作,如在socket中发送或接收:

  • streambuf 的输入序列可以通过data()成员函数获取.函数的返回值满足ConstBufferSequence的要求.
  • streambuf 的输出序列可以通过prepare()成员函数获取.函数的返回值满足MutableBufferSequence的要求.
  • 调用commit()成员函数将数据从前端的输出序列传递到后端的输入序列.
  • 调用consume()成员函数从输入序列中移除数据.

streambuf 构造函数接收一个size_t的参数指定输入序列和输出序列大小的总和.对于任何操作,如果成功,增加内部数据量,超过这个大小限制会抛出std::length_error异常.

遍历缓冲区序列的字节

buffers_iterator<>类模板可用于像遍历连续字节序列一样遍历缓冲区序列(如MutableBufferSequence 或 ConstBufferSequence).并提供了buffers_begin() 和 buffers_end()帮助函数, 会自动推断buffers_iterator<>的模板参数.

例如,从socket中读取一行数据,存入std::string中:

boost::asio::streambuf sb;
...
std::size_t n = boost::asio::read_until(sock, sb, '\n');
boost::asio::streambuf::const_buffers_type bufs = sb.data();
std::string line(
    boost::asio::buffers_begin(bufs),
    boost::asio::buffers_begin(bufs) + n);

缓冲区调试

有些标准库实现,如VC++8.0或其后版本,提供了叫做迭代器调试的特性.这意味着运行期会验证迭代器是否合法.如果应用程序试图使用非法的迭代器,会抛出异常.例如:

std::vector<int> v(1)
std::vector<int>::iterator i = v.begin();
v.clear(); // invalidates iterators
*i = 0; // assertion!

Boost.Asio 利用这个特性实现缓冲区调试.对于下面的代码:

void dont_do_this()
{
 std::string msg = "Hello, world!";
 boost::asio::async_write(sock, boost::asio::buffer(msg), my_handler);
}

当调用异步读或写时需要确保此操作的缓冲区在调用完成句柄时可用.上例中,缓冲区是std::string变量msg.变量在栈中,异步完成前已经过期了.如果幸运的话程序崩溃,但更可能会出现随机错误.

当缓冲区调试启动后,Boost.Asio会存储一个字符串的迭代器,一直保存到异步操作完成,然后解引用来检查有效性.上例中在Boost.Asio调用完成句柄前就会看到一个断言错误.

当定义_GLIBCXX_DEBUG 选项时,会自动在VS8.0及其以后版本和GCC中启动这个特性.检查需要性能代价,因此缓冲区调试只在debug生成时启用.其他编译器可通过定义BOOST_ASIO_ENABLE_BUFFER_DEBUGGING选项来启动.也可显式的通过BOOST_ASIO_DISABLE_BUFFER_DEBUGGING选项停止.

流,短读短写

很多Boost.Asio中的IO对象都是基于流的.意味着:

  • 无消息边界.被传输的数据就是一个连续的字节序列.
  • 读写操作可能会传递少量不需要的字节.这被称为短读或短写.

提供基于流IO操作模型的对象需要模拟如下几个操作:

  • SyncReadStream, 调用成员函数read_some()执行同步读操作.
  • AsyncReadStream, 调用成员函数async_read_some()执行异步读操作.
  • SyncWriteStream, 调用成员函数write_some()执行同步写操作.
  • AsyncWriteStream, 调用成员函数async_write_some()执行异步写操作.

基于流的IO对象包括ip::tcp::socket, ssl::stream<>, posix::stream_descriptor, windows::stream_handle等等.

通常程序需要传递指定数量的字节数据.启动操作后就会发生短读或短写,直到所有数据传输完毕.Boost.Asio提供了通用函数来自动完成这些操作: read(), async_read(), write() 和 async_write().

为什么EOF是一个错误

  • 流终止会导致read, async_read, read_until or async_read_until 函数违反约定.如要读取N个字节,但由于遇到EOF而提前结束.
  • EOF错误可以区分流终止和成功读取0个字节.

 

Reactor风格操作

有时应用程序必须整合第三方的库来实现IO操作.为此,Boost.Asio提供一个可用于读和写操作的null_buffers类型.null_buffers直到IO对象准备好执行操作后才会返回.

例如,如下代码执行无阻塞读操作:

ip::tcp::socket socket(my_io_service);
...
socket.non_blocking(true);
...
socket.async_read_some(null_buffers(), read_handler);
...
void read_handler(boost::system::error_code ec)
{
  if (!ec)
  {
    std::vector<char> buf(socket.available());
    socket.read_some(buffer(buf));
  }
}

Socket在所有平台上都支持这种操作,是POSIX基于流的描述符合类.

基于行的传输操作

很多常用的网络协议都是基于行的,即这些协议元素被字符序列"\r\n"限定.例如HTTP,SMTP和FTP.为了便于实现基于行的协议,以及其他使用分隔符的协议,Boost.Asio包括了read_until() 和 async_read_until()函数.

如下代码展示在HTTP服务端使用async_read_until()接收客户端的第一行HTTP请求:

class http_connection
{
  ...
 
  void start()
  {
    boost::asio::async_read_until(socket_, data_, "\r\n",
        boost::bind(&http_connection::handle_request_line, this, _1));
  }
 
  void handle_request_line(boost::system::error_code ec)
  {
    if (!ec)
    {
      std::string method, uri, version;
      char sp1, sp2, cr, lf;
      std::istream is(&data_);
      is.unsetf(std::ios_base::skipws);
      is >> method >> sp1 >> uri >> sp2 >> version >> cr >> lf;
      ...
    }
  }
 
  ...
 
  boost::asio::ip::tcp::socket socket_;
  boost::asio::streambuf data_;
};

Streambuf数据成员用于存储在查找到分隔符前接收的数据.记录分隔符以后的数据也是很重要的.这些保留在streambuf中的冗余数据会被保留下来用于随后调用的read_until()或async_read_until()函数进行检查.

分隔符可以是单个字符,一个std::string或一个boost::regex. read_until() 和 async_read_until()函数也可接收一个用户定义函数作为参数,获取匹配条件.例如,在streambuf中读取数据,遇到一个空白字符后停止.:

typedef boost::asio::buffers_iterator<
    boost::asio::streambuf::const_buffers_type> iterator;
 
std::pair<iterator, bool>
match_whitespace(iterator begin, iterator end)
{
  iterator i = begin;
  while (i != end)
    if (std::isspace(*i++))
      return std::make_pair(i, true);
  return std::make_pair(i, false);
}
...
boost::asio::streambuf b;
boost::asio::read_until(s, b, match_whitespace);

从streambuf中读取数据,直到遇到匹配的字符:

class match_char
{
public:
  explicit match_char(char c) : c_(c) {}
 
  template <typename Iterator>
  std::pair<Iterator, bool> operator()(
      Iterator begin, Iterator end) const
  {
    Iterator i = begin;
    while (i != end)
      if (c_ == *i++)
        return std::make_pair(i, true);
    return std::make_pair(i, false);
  }
 
private:
  char c_;
};
 
namespace boost { namespace asio {
  template <> struct is_match_condition<match_char>
    : public boost::true_type {};
} } // namespace boost::asio
...
boost::asio::streambuf b;
boost::asio::read_until(s, b, match_char('a'));

is_match_condition<>类型会自动的对函数,及带有嵌套result_type类型定义的函数对象的返回值值进行评估是否为true.如上例所示,其他类型的特性必须显式指定.

自定义内存分配

很多异步操作需要分配对象,用来存储与操作有关的状态数据.例如,Win32的实现中需要将重叠子对象传递给Win32 API函数.

幸好程序包含一个易于识别的异步操作链.半双工协议(如HTTP服务)为每个客户端创建一个单操作链.全双工协议实现有两个并行的执行链.程序运用这个规则可以在链上的所有异步操作中重用内存.

假设复制一个用户定义的句柄对象h,如果句柄的实现需要分配内存,应该有如下代码:

void* pointer = asio_handler_allocate(size, &h);

同样要释放内存:

asio_handler_deallocate(pointer, size, &h);

这个函数实现了参数依赖的定位查找.asio空间中有这个函数的默认实现:

void* asio_handler_allocate(size_t, ...);
void asio_handler_deallocate(void*, size_t, ...);

实现了::operator new() 和 ::operator delete()功能.

函数实现保证了相关句柄调用前会发生内存重新分配,这样就可以实现同一个句柄上的异步操作重用内存.

在任意调用库函数的用户线程中都可调用自定义内存分配函数.实现保证库中的这些异步操作不会并发的对句柄进行内存分配函数调用.实现要加入适当的内存间隔,保证不同线程中调用的内存分配函数有正确的内存可见性.

句柄跟踪

为调试异步程序,Boost.Asio提供了句柄跟踪支持.当激活BOOST_ASIO_ENABLE_HANDLER_TRACKING定义,Boost.Asio向标准错误流中写入调试输出信息.输出信息记录异步操作及其句柄间的关系.

这个特性对调试很有帮助,需要知道异步操作是如何被链接在一起的,或什么异步操作被挂起了.如下是HTTP服务输出的调试信息,处理单个请求,而后按Ctrl+C退出:

@asio|1298160085.070638|0*1|signal_set@0x7fff50528f40.async_wait
@asio|1298160085.070888|0*2|socket@0x7fff50528f60.async_accept
@asio|1298160085.070913|0|resolver@0x7fff50528e28.cancel
@asio|1298160118.075438|>2|ec=asio.system:0
@asio|1298160118.075472|2*3|socket@0xb39048.async_receive
@asio|1298160118.075507|2*4|socket@0x7fff50528f60.async_accept
@asio|1298160118.075527|<2|
@asio|1298160118.075540|>3|ec=asio.system:0,bytes_transferred=122
@asio|1298160118.075731|3*5|socket@0xb39048.async_send
@asio|1298160118.075778|<3|
@asio|1298160118.075793|>5|ec=asio.system:0,bytes_transferred=156
@asio|1298160118.075831|5|socket@0xb39048.close
@asio|1298160118.075855|<5|
@asio|1298160122.827317|>1|ec=asio.system:0,signal_number=2
@asio|1298160122.827333|1|socket@0x7fff50528f60.close
@asio|1298160122.827359|<1|
@asio|1298160122.827370|>4|ec=asio.system:125
@asio|1298160122.827378|<4|
@asio|1298160122.827394|0|signal_set@0x7fff50528f40.cancel

每行的格式为:

<tag>|<timestamp>|<action>|<description>

<tag>一直是@asio,用于在程序输出中识别和提取句柄跟踪消息.

<timestamp>是从UTC时间1970年一月一日起至今的秒数.

<action>有如下几种形式:

>n

程序进入了第n个句柄.<description>描述句柄参数.

<n

程序退出第n个句柄.

!n

程序由于异常退出第n个句柄.

~n

第n个句柄没有执行就被销毁了.通常是由于io_service销毁时异步操作还没有完成.

n*m

第n个句柄创建了一个生成第m个完成句柄的新异步操作.<description>描述什么异步操作被启动.

n

第n个句柄执行其他操作.<description>显示调用了哪些函数.通常只记录close()和cancel()操作,因为其影响了异步操作挂起状态.

当<description>显示同步或异步操作,格式为<object-type>@<pointer>.<operation>. 并以逗号间隔来显示执行句柄的参数和值.

如上所示,每个句柄都由唯一的数字进行标识.句柄跟踪输出的句柄编号是0的,表示操作不是这些句柄执行的.

可视化

句柄跟踪输出可以使用包含在handlerviz.pl中的工具进行处理,创建可视化的界面.

网络

TCP,UDP和ICMP

Boost.Asio 对TCP,UDP和ICMP提供了完整的支持.

TCP客户端

使用resolver执行主机名称解析,查询主机和服务名称并转换为一个或多个端点:

ip::tcp::resolver resolver(my_io_service);
ip::tcp::resolver::query query("www.boost.org", "http");
ip::tcp::resolver::iterator iter = resolver.resolve(query);
ip::tcp::resolver::iterator end; // End marker.
while (iter != end)
{
  ip::tcp::endpoint endpoint = *iter++;
  std::cout << endpoint << std::endl;
}

上面列表中包含的端点可以是IPv4和IPv6端点,因此程序需要对每种情况进行尝试直到得到一个可用的端点.这使程序不依赖于特定的IP版本.

为了开发独立与协议的程序,TCP客户端可以使用connect()async_connect()函数建立连接.这个操作将尝试列表中的每个端点直到Socket连接成功为止.例如,简单的调用:

ip::tcp::socket socket(my_io_service);
boost::asio::connect(socket, resolver.resolve(query));

将同时尝试所有端点直到找到连接成功.同样异步操作的方式为:

boost::asio::async_connect(socket_, iter,
    boost::bind(&client::handle_connect, this,
      boost::asio::placeholders::error));
 
// ...
 
void handle_connect(const error_code& error)
{
  if (!error)
  {
    // Start read or write operations.
  }
  else
  {
    // Handle error.
  }
}

当特定端点可用,socket创建并连接.方式为:

ip::tcp::socket socket(my_io_service);
socket.connect(endpoint);

使用成员函数receive(),async_receive()send() 或 async_send()可将数据读于或写到TCP连接的Socket上.然而为了快速的读写操作,通常使用read(),async_read()write()async_write()函数进行操作.

TCP服务

程序使用接收器来接收到达的TCP连接:

ip::tcp::acceptor acceptor(my_io_service, my_endpoint);
...
ip::tcp::socket socket(my_io_service);
acceptor.accept(socket);

当成功接收一个socket连接(向如上例中的TCP客户端),就可以在其上读取或写入数据了.

UDP

UDP也使用来resolver解析主机名称:

ip::udp::resolver resolver(my_io_service);
ip::udp::resolver::query query("localhost", "daytime");
ip::udp::resolver::iterator iter = resolver.resolve(query);
...

UDP绑定到了本地端点.如下代码创建一个IPv4的UDP Socket,绑定到任意地址的12345端口上:

ip::udp::endpoint endpoint(ip::udp::v4(), 12345);
ip::udp::socket socket(my_io_service, endpoint);

使用成员函数receive_from(),async_receive_from()send_to() 或 async_send_to()可在无连接的UPD Socket上读出或写入数据.对于连接的UDP Socket,可使用receive(),async_receive()send() 或 async_send()成员函数.

ICMP

如同TCP和UPD一样,ICMP也是用resolver解析主机名称:

ip::icmp::resolver resolver(my_io_service);
ip::icmp::resolver::query query("localhost", "");
ip::icmp::resolver::iterator iter = resolver.resolve(query);
...

ICMP Socket可能绑定到本地端点.如下代码创建IPv6版本的ICMP Socket,并绑定到任意的地址:

ip::icmp::endpoint endpoint(ip::icmp::v6(), 0);
ip::icmp::socket socket(my_io_service, endpoint);

ICMP不必指定端口号.

使用成员函数receive_from(),async_receive_from()send_to() 或 async_send_to()在无连接的ICMP上读出或写入数据.

其他协议

其他Socket协议的支持(如蓝牙或IRCOMM)可按协议要求进行实现.

Socket IO流

Boost.Asio 中包含一个在Socket上实现的iostream类.将端点解析,协议无关等复杂特性的实现隐藏起来.要创建连接只需简单的写几行代码:

ip::tcp::iostream stream("www.boost.org", "http");
if (!stream)
{
  // Can't connect.
}

iostream 类可以与接收器一起创建简单的服务端.例如:

io_service ios;
 
ip::tcp::endpoint endpoint(tcp::v4(), 80);
ip::tcp::acceptor acceptor(ios, endpoint);
 
for (;;)
{
  ip::tcp::iostream stream;
  acceptor.accept(*stream.rdbuf());
  ...
}

可使用expires_at() 或 expires_from_now()函数设置超时期限.超时的Socket操作会将iostream设置为"bad"状态.

例如,一个简单的客户端程序:

ip::tcp::iostream stream;
stream.expires_from_now(boost::posix_time::seconds(60));
stream.connect("www.boost.org", "http");
stream << "GET /LICENSE_1_0.txt HTTP/1.0\r\n";
stream << "Host: www.boost.org\r\n";
stream << "Accept: */*\r\n";
stream << "Connection: close\r\n\r\n";
stream.flush();
std::cout << stream.rdbuf();

如果所有的Socket操作时间加起来超过60秒则失败.

如果发生错误,可使用iostream的error()成员函数获取最近系统操作的错误码:

if (!stream)
{
  std::cout << "Error: " << stream.error().message() << "\n";
}
注意

这个iostream模板只支持char,不支持wchar_t,不要用于这样的代码版本.

BSD Socket API和Boost.Asio

Boost.Asio 包含了实现BSD Socket API的低层次Socket接口的库.在其他语言中后者也作为最基本的网络API,如Java.底层次接口可用来开发高效率和高可伸缩性的应用程序.例如,程序员可以更好的控制系统调用次数,避免基础数据拷贝,最小化如线程等资源的使用等.

BSD Socket API包含了不安全和有错误倾向的方面.例如,使用整型数字代表socket缺乏安全性. Boost.Asio 中使用不同的Socket类型代表不用的协议,如TCP对应的是ip::tcp::socket,UDP对应的ip::udp::socket.

下表是BSD Socket API和Boost.Asio的对比:

BSD Socket API 元素

Boost.Asio中等价内容

Socket描述符-int (POSIX)或SOCKET (Windows)

TCP: ip::tcp::socketip::tcp::acceptor

UDP: ip::udp::socket

basic_socket,basic_stream_socketbasic_datagram_socketbasic_raw_socket

in_addr, in6_addr

ip::address,ip::address_v4ip::address_v6

sockaddr_in, sockaddr_in6

TCP: ip::tcp::endpoint

UDP: ip::udp::endpoint

ip::basic_endpoint

accept()

TCP: ip::tcp::acceptor::accept()

basic_socket_acceptor::accept()

bind()

TCP: ip::tcp::acceptor::bind()ip::tcp::socket::bind()

UDP: ip::udp::socket::bind()

basic_socket::bind()

close()

TCP: ip::tcp::acceptor::close()ip::tcp::socket::close()

UDP: ip::udp::socket::close()

basic_socket::close()

connect()

TCP: ip::tcp::socket::connect()

UDP: ip::udp::socket::connect()

basic_socket::connect()

getaddrinfo(), gethostbyaddr(), gethostbyname(), getnameinfo(), getservbyname(), getservbyport()

TCP: ip::tcp::resolver::resolve()ip::tcp::resolver::async_resolve()

UDP: ip::udp::resolver::resolve()ip::udp::resolver::async_resolve()

ip::basic_resolver::resolve(),ip::basic_resolver::async_resolve()

gethostname()

ip::host_name()

getpeername()

TCP: ip::tcp::socket::remote_endpoint()

UDP: ip::udp::socket::remote_endpoint()

basic_socket::remote_endpoint()

getsockname()

TCP: ip::tcp::acceptor::local_endpoint()ip::tcp::socket::local_endpoint()

UDP: ip::udp::socket::local_endpoint()

basic_socket::local_endpoint()

getsockopt()

TCP: ip::tcp::acceptor::get_option()ip::tcp::socket::get_option()

UDP: ip::udp::socket::get_option()

basic_socket::get_option()

inet_addr(), inet_aton(), inet_pton()

ip::address::from_string(),ip::address_v4::from_string()ip_address_v6::from_string()

inet_ntoa(), inet_ntop()

ip::address::to_string(),ip::address_v4::to_string()ip_address_v6::to_string()

ioctl()

TCP: ip::tcp::socket::io_control()

UDP: ip::udp::socket::io_control()

basic_socket::io_control()

listen()

TCP: ip::tcp::acceptor::listen()

basic_socket_acceptor::listen()

poll(), select(), pselect()

io_service::run(),io_service::run_one()io_service::poll()io_service::poll_one()

注意:包括异步操作.

readv(), recv(), read()

CP: ip::tcp::socket::read_some()ip::tcp::socket::async_read_some(),ip::tcp::socket::receive()ip::tcp::socket::async_receive()

UDP: ip::udp::socket::receive()ip::udp::socket::async_receive()

basic_stream_socket::read_some(),basic_stream_socket::async_read_some(),basic_stream_socket::receive()basic_stream_socket::async_receive(),basic_datagram_socket::receive()basic_datagram_socket::async_receive()

recvfrom()

UDP: ip::udp::socket::receive_from()ip::udp::socket::async_receive_from()

basic_datagram_socket::receive_from(),basic_datagram_socket::async_receive_from()

send(), write(), writev()

TCP: ip::tcp::socket::write_some()ip::tcp::socket::async_write_some(),ip::tcp::socket::send()ip::tcp::socket::async_send()

UDP: ip::udp::socket::send()ip::udp::socket::async_send()

basic_stream_socket::write_some(),basic_stream_socket::async_write_some(),basic_stream_socket::send()basic_stream_socket::async_send(),basic_datagram_socket::send()basic_datagram_socket::async_send()

sendto()

UDP: ip::udp::socket::send_to()ip::udp::socket::async_send_to()

basic_datagram_socket::send_to(),basic_datagram_socket::async_send_to()

setsockopt()

TCP: ip::tcp::acceptor::set_option()ip::tcp::socket::set_option()

UDP: ip::udp::socket::set_option()

basic_socket::set_option()

shutdown()

TCP: ip::tcp::socket::shutdown()

UDP: ip::udp::socket::shutdown()

basic_socket::shutdown()

sockatmark()

TCP: ip::tcp::socket::at_mark()

basic_socket::at_mark()

socket()

TCP: ip::tcp::acceptor::open()ip::tcp::socket::open()

UDP: ip::udp::socket::open()

basic_socket::open()

socketpair()

local::connect_pair()

注意:仅适合POSIX操作系统.

定时器

长时间运行的操作通常都会设置一个最终的完成期限.这个最终期限可以是使用绝对时间来表示,但通常使用相对时间.

一个简单的范例,在相对时间内执行等待异步操作:

io_service i;
...
deadline_timer t(i);
t.expires_from_now(boost::posix_time::seconds(5));
t.wait();

通常,程序会基于计时器来执行异步等待操作:

void handler(boost::system::error_code ec) { ... }
...
io_service i;
...
deadline_timer t(i);
t.expires_from_now(boost::posix_time::milliseconds(400));
t.async_wait(handler);
...
i.run();

定时器相关联的超时期限可以是相对时间:

boost::posix_time::time_duration time_until_expiry
  = t.expires_from_now();

或使用绝对时间:

deadline_timer t2(i);
t2.expires_at(t.expires_at() + boost::posix_time::seconds(30));

串口

Boost.Asio包含用灵活的方式创建和操作串口的类.例如,打开串口的代码:

serial_port port(my_io_service, name);

name是如Windows中的"COM1",及POSIX平台下的"/dev/ttyS0".

打开后,串口就可以向流一样使用了.既这个对象可以用于async_read(),write()async_write()read_until() 或async_read_until()函数.

串口实现中还包括配置串口波特率,流控制,奇偶校验,停止位和字符数量等可选类.

注意

串口可用于所有POSIX平台.Windows中串口通信需要在编译期将IO完成端口激活.程序可以测试BOOST_ASIO_HAS_SERIAL_PORTS宏来检查Windows系统是否支持串口操作.

信号处理

Boost.Asio通过signal_set类实现信号处理.程序可以向集合中加入一个或多个信号,而后执行asyn_wait()操作.当其中一个信号发生时执行特定的事件处理函数.同一个信号可注册在多个singal_set对象中,但这些信号只能用于Boost.Asio .

void handler(
    const boost::system::error_code& error,
    int signal_number)
{
  if (!error)
  {
    // A signal occurred.
  }
}
 
...
 
// Construct a signal set registered for process termination.
boost::asio::signal_set signals(io_service, SIGINT, SIGTERM);
 
// Start an asynchronous wait for one of the signals to occur.
signals.async_wait(handler);

信号处理也可在Windows中使用,与VC++运行时库映射到控制台的事件如Ctrl+C等价.

POSIX特有功能

UNIX领域的Socket

Boost.Asio 提供了UNIX领域的Socket基本支持(又叫做本地Socket).最简单的使用情况是有一对连接Socket.代码如下:

local::stream_protocol::socket socket1(my_io_service);
local::stream_protocol::socket socket2(my_io_service);
local::connect_pair(socket1, socket2);

将创建一对基于流的Socket.要实现基于数据包的Socket,使用:

local::datagram_protocol::socket socket1(my_io_service);
local::datagram_protocol::socket socket2(my_io_service);
local::connect_pair(socket1, socket2);

UNIX领域的Socket服务可以创建在一个绑定一个端点的接收器上,TCP服务也类似:

::unlink("/tmp/foobar"); // Remove previous binding.
local::stream_protocol::endpoint ep("/tmp/foobar");
local::stream_protocol::acceptor acceptor(my_io_service, ep);
local::stream_protocol::socket socket(my_io_service);
acceptor.accept(socket);

客户端连接到服务端代码:

local::stream_protocol::endpoint ep("/tmp/foobar");
local::stream_protocol::socket socket(my_io_service);
socket.connect(ep);

Boost.Asio不支持跨UNIX领域Socket传输文件描述符或证书,但可以使用native()函数调用Socket的底层描述符来实现.

注意

UNIX领域的Socket仅在支持的平台上编译的时候激活.可以测试BOOST_ASIO_HAS_LOCAL_SOCKETS宏检查是否支持.

基于流的文件描述符

Boost.Asio 包含在POSIX文件描述符上同步或异步读写的类,如管道,标准输入输出,和各种设备(但不是常规的文件).

例如,在标准输入输出上执行读写,创建如下对象:

posix::stream_descriptor in(my_io_service, ::dup(STDIN_FILENO));
posix::stream_descriptor out(my_io_service, ::dup(STDOUT_FILENO));

而后进行同步或异步读写流.即对象可用于read(),async_read()write()async_write()read_until() 或async_read_until()等函数.

注意

POSIX流描述符仅在支持平台上编译时激活.程序可以检查BOOST_ASIO_HAS_POSIX_STREAM_DESCRIPTOR宏查询是否支持.

Fork

Boost.Asio 支持程序使用fork()系统调用.程序可以在适当时间调用io_service.notify_fork(),Boost.Asio可重新创建一个内部的文件描述符(如使用"self-pipe trick"描述符技巧激活反应器(reactor)).通知用法如下:

io_service_.notify_fork(boost::asio::io_service::fork_prepare);
if (fork() == 0)
{
  io_service_.notify_fork(boost::asio::io_service::fork_child);
  ...
}
else
{
  io_service_.notify_fork(boost::asio::io_service::fork_parent);
  ...
}

用户定义服务可以重写io_service::service::fork_service()虚函数有意制造fork操作.

注意所有Boost.Asio的公共API函数可访问的文件描述符(如basic_socket<>, posix::stream_descriptor下的描述符)在fork期间不会更改.这需要程序按需要进行管理.

Windows特有功能

面向流的句柄

Boost.Asio 包含允许在Windows句柄上执行异步读或写操作的类,如命名管道.

例如,在命名管道上执行异步操作,创建如下对象:

HANDLE handle = ::CreateFile(...);
windows::stream_handle pipe(my_io_service, handle);

而后使用同步或异步方式读写流.即对象可用于read(),async_read()write()async_write()read_until() 或async_read_until()函数.

句柄相对的内核对象必须支持IO完成端口(命名管道支持,但匿名管道和控制台流不支持).

注意

Windows句柄流只能在编译期激活,而且必须只用IO完成端口作为后台处理(默认).可使用BOOST_ASIO_HAS_WINDOWS_STREAM_HANDLE宏检测.

随机存取句柄

Boost.Asio提供特定的类实现在规则文件相关句柄上执行异步读写操作.

例如,在文件上执行异步操作代码:

HANDLE handle = ::CreateFile(...);
windows::random_access_handle file(my_io_service, handle);

可以通过成员函数read_some_at(), async_read_some_at(), write_some_at() 或 async_write_some_at()来读出或写入数据.然而,和流上的等价函数(read_some()等)一样,这些函数只需要在单个操作中传递一个或多个字节.因此创建了read_at(),async_read_at()write_at() 和 async_write_at()函数,内部重复调用相应的*_some_at()函数直到所有数据传递完毕为止.

注意

Windows随机读取句柄只能在编译期激活,而且后台使用IO完成端口进行处理才可用.可使用BOOST_ASIO_HAS_WINDOWS_RANDOM_ACCESS_HANDLE宏来检测.

对象句柄

Boost.Asio提供Windows特有的类实现在内核对象句柄上进行异步等待的操作:

  • 修改通知
  • 控制台输入
  • 事件
  • 内存资源通知
  • 进程
  • 信号量
  • 线程
  • 等待定时器

例如,在事件上执行异步操作,创建如下对象:

HANDLE handle = ::CreateEvent(...);
windows::object_handle file(my_io_service, handle);

wait()和async_wait()成员函数用于等待内核对象受信.

注意

Windows对象句柄需要在编译期激活.可使用BOOST_ASIO_HAS_WINDOWS_OBJECT_HANDLE宏检查.

SSL

Boost.Asio 包括对SSL支持的类和类模板.这些类可以在通信时将已存在的流(如TCP Socket)进行加密.

在创建加密流前,应用程序必须构造SSL上下文对象.这个对象中通常设置了如验证模式,证书文件等SLL选项.例如,客户端初始化代码如下:

ssl::context ctx(ssl::context::sslv23);
ctx.set_verify_mode(ssl::verify_peer);
ctx.load_verify_file("ca.pem");

在TCP Socket下使用SSL:

ssl::stream<ip::tcp::socket> ssl_sock(my_io_service, ctx);

执行特定的Socket操作,如建立远程连接或接收连接,底层的Socket必须使用ssl::stream模板的lowest_layer()成员函数来获取:

ip::tcp::socket::lowest_layer_type& sock = ssl_sock.lowest_layer();
sock.connect(my_endpoint);

有时底层的流对象的生命期要比SSL流长,这时模板参数需要引用流类型:

ip::tcp::socket sock(my_io_service);
ssl::stream<ip::tcp::socket&> ssl_sock(sock, ctx);

SSL的加密连接握手需要在传输或接收数据前进行.可通过ssl::stream模板的handshake()或async_handshake()成员函数建立连接.

连接后,SSL流对象即可向同步或异步读写流一样的方式使用了.即对象可用于read(),async_read()write(),async_write()read_until() 或 async_read_until()函数.

证书验证

Boost.Asio 提供各种方法来配置SSL证书验证:

简单情况下证书验证规则为RFC2818(HTTPS下证书验证), Boost.Asio 提供一个可重用的证书验证回调函数对象:

下例演示用HTTPS的方式验证远程主机的证书:

using boost::asio::ip::tcp;
namespace ssl = boost::asio::ssl;
typedef ssl::stream<tcp::socket> ssl_socket;
 
// Create a context that uses the default paths for
// finding CA certificates.
ssl::context ctx(ssl::context::sslv23);
ctx.set_default_verify_paths();
 
// Open a socket and connect it to the remote host.
boost::asio::io_service io_service;
ssl_socket sock(io_service, ctx);
tcp::resolver resolver(io_service);
tcp::resolver::query query("host.name", "https");
boost::asio::connect(sock.lowest_layer(), resolver.resolve(query));
sock.lowest_layer().set_option(tcp::no_delay(true));
 
// Perform SSL handshake and verify the remote host's
// certificate.
sock.set_verify_mode(ssl::verify_peer);
sock.set_verify_callback(ssl::rfc2818_verification("host.name"));
sock.handshake(ssl_socket::client);
 
// ... read and write as normal ...

SSL和线程

SSL流对象在无锁定的情况下执行.因此,异步执行SSL都需要隐式或显式的应用strand.注意这意味着在单线程程序中不需同步控制(不必使用锁).

注意

OpenSSL需要Boost.Asio的SSL支持.当应用程序使用未由Boost.Asio包装的OpenSSL功能时,底层的OpenSSL类可通过调用ssl::context::native_handle() 或ssl::stream::native_handle()获得.

C++2011支持

可移动IO对象

C++支持移动对象后(通过右值引用),Boost.Asio可以对socket,串口,POSIX描述符和Windows句柄进行移动构造和赋值.

通过移动对象可实现如下代码:

tcp::socket make_socket(io_service& i)
{
  tcp::socket s(i);
  ...
  std::move(s);
}

或:

class connection : public enable_shared_from_this<connection>
{
private:
  tcp::socket socket_;
  ...
public:
  connection(tcp::socket&& s) : socket_(std::move(s)) {}
  ...
};
 
...
 
class server
{
private:
  tcp::acceptor acceptor_;
  tcp::socket socket_;
  ...
  void handle_accept(error_code ec)
  {
    if (!ec)
      std::make_shared<connection>(std::move(socket_))->go();
    acceptor_.async_accept(socket_, ...);
  }
  ...
};

同时:

std::vector<tcp::socket> sockets;
sockets.push_back(tcp::socket(...));

一字真言:当异步操作挂起时是可以继续移动对象的,但这不是一个好主意.特别是操作由如async_read()等函数触发时其引用了流对象.在操作期间移动对象将会导致相应操作访问已移动的对象.

移动支持在g++4.5及以后版本加入-std=c++0x 或 -std=gnu++0x编译选项即可自动支持.可通过BOOST_ASIO_DISABLE_MOVE定义宏禁用或BOOST_ASIO_HAS_MOVE宏激活.注意这些宏还会影响可移动句柄.

可移动句柄

优化后,用户定义的完成句柄可支持移动构造,Boost.Asio的实现会优先于拷贝构造函数使用移动构造函数.当前环境下,Boost.Asio也可能去除所有句柄的拷贝构造函数.然而,句柄类型还是需要拷贝构造函数的.

当激活移动支持,异步代码为:

template <typename Handler>
void async_XYZ(..., Handler handler);

实际上声明为:

template <typename Handler>
void async_XYZ(..., Handler&& handler);

句柄参数在async_XYZ函数体内部完美的传递,并发生了移动构造.这可确保函数的所有其他参数都会提前评估移动.尤其是在async_XYZ()的其他参数为句柄的成员时更重要.例如:

struct my_operation
{
  shared_ptr<tcp::socket> socket;
  shared_ptr<vector<char>> buffer;
  ...
  void operator(error_code ec, size_t length)
  {
    ...
    socket->async_read_some(boost::asio::buffer(*buffer), std::move(*this));
    ...
  }
};

移动支持在g++4.5及以后版本加入-std=c++0x 或 -std=gnu++0x编译选项即可自动支持.可通过BOOST_ASIO_DISABLE_MOVE定义宏禁用或BOOST_ASIO_HAS_MOVE宏激活.注意这些宏还会影响可移动IO对象.

可变参模板

如果编译器支持,Boost.Asio可以使用可变参模板实现basic_socket_streambuf::connect()basic_socket_iostream::connect()函数.

可变参模板支持在g++4.3及以后版本中的编译期添加-std=c++0x 或 -std=gnu++0x编译选项会自动激活.可使用BOOST_ASIO_DISABLE_VARIADIC_TEMPLATES宏禁用,使用BOOST_ASIO_HAS_VARIADIC_TEMPLATES宏激活.

数组容器

由于标准库提供了std::array<>,Boost.Asio:

G++4.3及以后版本中加上-std=c++0x 或 -std=gnu++0x编译选项即可自动激活std::array<>,同时VC++10也支持.使用BOOST_ASIO_DISABLE_STD_ARRAY宏禁用,或BOOST_ASIO_HAS_STD_ARRAY宏激活.

原语

Boost.Asio的实现相对于boost::detail::atomic_count优先使用std::atomic<>.

在g++4.5及以后版本中加入-std=c++0x 或 -std=gnu++0x编译选项即可激活标准的原语整数模板.可使用BOOST_ASIO_DISABLE_STD_ATOMIC宏禁用,或BOOST_ASIO_HAS_STD_ATOMIC宏启用.

共享指针

Boost.Asio 优先使用std::shared_ptr<>和std::weak_ptr<>.

在g++4.3及以后版本中编译期添加编译选项则会自动激活智能指针,MC++10也同样.可定义BOOST_ASIO_DISABLE_STD_SHARED_PTR宏禁用,用BOOST_ASIO_HAS_STD_SHARED_PTR宏激活.

Chrono

Boost.Asio基于std::chrono机制通过basic_waitable_timer类模板提供了基本的定时器功能.system_timer,steady_timer 和 high_resolution_timer分别使用system_clock, steady_clock 和 high_resolution_clock标准时钟.

g++4.6级以后版本添加-std=c++0x 或 -std=gnu++0x编译选项即可自动支持std::chrono.(注意,g++中使用monotonic_clock替代steady_clock.)使用BOOST_ASIO_DISABLE_STD_CHRONO宏禁用, BOOST_ASIO_HAS_STD_CHRONO宏激活.

如果chrono不可用,可使用Boost.Chrono库. basic_waitable_timer类仍可用.

使用Boost.Asio

支持平台

在如下平台和编译器下测试通过:

  • Win32 and Win64 using Visual C++ 7.1 and Visual C++ 8.0.
  • Win32 using MinGW.
  • Win32 using Cygwin. (__USE_W32_SOCKETS must be defined.)
  • Linux (2.4 or 2.6 kernels) using g++ 3.3 or later.
  • Solaris using g++ 3.3 or later.
  • Mac OS X 10.4 using g++ 3.3 or later.

也可用于如下平台:

  • AIX 5.3 using XL C/C++ v9.
  • HP-UX 11i v3 using patched aC++ A.06.14.
  • QNX Neutrino 6.3 using g++ 3.3 or later.
  • Solaris using Sun Studio 11 or later.
  • Tru64 v5.1 using Compaq C++ v7.1.
  • Win32 using Borland C++ 5.9.2

依赖

为使程序可连接到Boost.Asio如下库必须可用:

  • 提供boost::system::error_code 和 boost::system::system_error 的Boost.System库.
  • 如果使用带boost::regex参数重载的read_until()或async_read_until()函数,需要Boost.Regex库.
  • 如果使用了Boost.Asio的SSL支持需要OpenSSL库.

而且有些范例需要Boost.Thread, Boost.Date_Time 或 Boost.Serialization库的支持.

 

 

注意Note

在MSVC或Borland C++中可以在项目设置加入-DBOOST_DATE_TIME_NO_LIB 和 -DBOOST_REGEX_NO_LIB来禁止分别对库Boost.Date_Time 和 Boost.Regex的自动连接.否则需要生成这些库并进行连接.

 

生成Boost库

可以按需要生成Boost的子库,下例在Boost的根目录中运行命令行:

bjam --with-system --with-thread --with-date_time --with-regex --with-serialization stage

这里假设已经生成了bjam.更多信息见Boost.Build文档.

可选的独立编译

默认情况下,Boost.Asio仅是一个有头文件的库.然而,有些开发者更愿意分开编译Boost.Asio的源码.只需要在程序的源文件中加入#include<boost/asio/impl/src.hpp>,而后在项目/编译器设置中加入BOOST_ASIO_SEPARATE_COMPILATION编译选项并进行编译.或者使用BOOST_ASIO_DYN_LINK选项将Boost.Asio独立编译为静态库.

如果使用Boost.Asio的SSL支持,需要加入#include <boost/asio/ssl/impl/src.hpp>.

下表中的宏可用来控制Boost.Asio的行为.

 

宏Macro

描述Description

BOOST_ASIO_ENABLE_BUFFER_DEBUGGING

激活Boost.Asio的缓冲区调试支持,可帮助识别出在读写中引用的无效缓冲区(例如,向std::string写数据,但在操作完成前就已经被释放了).

在Microsoft Visual C++中,如果激活编译器的迭代调试支持这个宏会自动定义,否则需要定义BOOST_ASIO_DISABLE_BUFFER_DEBUGGING.

在G++中,如果启动标准库调试(定义了_GLIBCXX_DEBUG宏)这个宏会自动定义,否则需要定义BOOST_ASIO_DISABLE_BUFFER_DEBUGGING 宏.

BOOST_ASIO_DISABLE_BUFFER_DEBUGGING

显式的关闭Boost.Asio缓冲区调试支持.

BOOST_ASIO_DISABLE_DEV_POLL

显式关闭Solaris的/dev/poll支持,强制使用select-based实现.

BOOST_ASIO_DISABLE_EPOLL

显式关闭Linux的epoll支持,强制使用select-based实现.

BOOST_ASIO_DISABLE_EVENTFD

显式关闭Linux的eventfd支持,强制使用管道进行中断阻塞的epoll/select系统调用.

BOOST_ASIO_DISABLE_KQUEUE

Explicitly disables kqueue support on Mac OS X and BSD variants, forcing the use of a select-based implementation.

BOOST_ASIO_DISABLE_IOCP

Explicitly disables I/O completion ports support on Windows, forcing the use of a select-based implementation.

BOOST_ASIO_DISABLE_THREADS

Explicitly disables Boost.Asio's threading support, independent of whether or not Boost as a whole supports threads.

BOOST_ASIO_NO_WIN32_LEAN_AND_MEAN

By default, Boost.Asio will automatically define WIN32_LEAN_AND_MEAN when compiling for Windows, to minimise the number of Windows SDK header files and features that are included. The presence of BOOST_ASIO_NO_WIN32_LEAN_AND_MEAN prevents WIN32_LEAN_AND_MEAN from being defined.

BOOST_ASIO_NO_NOMINMAX

By default, Boost.Asio will automatically define NOMINMAX when compiling for Windows, to suppress the definition of the min() and max() macros. The presence of BOOST_ASIO_NO_NOMINMAX prevents NOMINMAX from being defined.

BOOST_ASIO_NO_DEFAULT_LINKED_LIBS

When compiling for Windows using Microsoft Visual C++ or Borland C++, Boost.Asio will automatically link in the necessary Windows SDK libraries for sockets support (i.e.ws2_32.lib and mswsock.lib, or ws2.lib when building for Windows CE). The BOOST_ASIO_NO_DEFAULT_LINKED_LIBS macro prevents these libraries from being linked.

BOOST_ASIO_SOCKET_STREAMBUF_MAX_ARITY

Determines the maximum number of arguments that may be passed to the basic_socket_streambuf class template's connect member function. Defaults to 5.

BOOST_ASIO_SOCKET_IOSTREAM_MAX_ARITY

Determines the maximum number of arguments that may be passed to the basic_socket_iostream class template's constructor and connect member function. Defaults to 5.

BOOST_ASIO_ENABLE_CANCELIO

Enables use of the CancelIo function on older versions of Windows. If not enabled, calls to cancel() on a socket object will always fail with asio::error::operation_not_supported when run on Windows XP, Windows Server 2003, and earlier versions of Windows. When running on Windows Vista, Windows Server 2008, and later, the CancelIoEx function is always used.

The CancelIo function has two issues that should be considered before enabling its use:

* It will only cancel asynchronous operations that were initiated in the current thread.

* It can appear to complete without error, but the request to cancel the unfinished operations may be silently ignored by the operating system. Whether it works or not seems to depend on the drivers that are installed.

For portable cancellation, consider using one of the following alternatives:

* Disable asio's I/O completion port backend by defining BOOST_ASIO_DISABLE_IOCP.

* Use the socket object's close() function to simultaneously cancel the outstanding operations and close the socket.

BOOST_ASIO_NO_TYPEID

Disables uses of the typeid operator in Boost.Asio. Defined automatically if BOOST_NO_TYPEID is defined.

BOOST_ASIO_HASH_MAP_BUCKETS

Determines the number of buckets in Boost.Asio's internal hash_map objects. The value should be a comma separated list of prime numbers, in ascending order. The hash_map implementation will automatically increase the number of buckets as the number of elements in the map increases.

Some examples:

* Defining BOOST_ASIO_HASH_MAP_BUCKETS to 1021 means that the hash_map objects will always contain 1021 buckets, irrespective of the number of elements in the map.

* Defining BOOST_ASIO_HASH_MAP_BUCKETS to 53,389,1543 means that the hash_map objects will initially contain 53 buckets. The number of buckets will be increased to 389 and then 1543 as elements are added to the map.

 

邮件列表

Boost.Asio 的邮件列表可从SourceForge.net查找.新闻组见Gmane.

Wiki

用户通常受益于Boost.Asio WIKI上的共享范例,提示和FAQ.,请见http://think-async.com/Asio/.

入门教程

基本技能

入门教程的第一小节介绍使用asio工具的基本概念.在开发复杂的网络程序前,范例程序阐述使用异步定时器的基本技巧.

Sockets简介

本节教程展示如何使用asio开发一个简单的客户端和服务端程序.程序使用TCP和UDP实现了基本的daytime协议.

前面三个程序使用TCP实现daytime协议.

后面三个程序使用UDP实现daytime协议.

本节最后的程序展示如何在一个程序中使用asio来组合TCP和UDP服务.

定时器1-使用同步定时器

本教程演示如何使用asio的定时器执行阻塞等待.

需要包含必要的头文件:

简单的包含"asio.hpp"头文件即可使用所有的asio类.

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

由于本例使用了定时器,需要包含相应的Boost.Date_Time头文件以便于操作定时器.

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

所有使用asio的程序都要创建一个io_service对象.这个类提供IO功能支持.在main函数中首先声明这个类的对象.

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

接着声明一个boost::asio::deadline_timer类型的对象.这些提供IO功能的核心asio类 (或是定时器功能)构造函数的第一个参数总是io_service类的实例.第二个参数设置过期时间为从现在起5秒钟以后.

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

这是在定时器上阻塞等待的简单范例.调用deadline_timer::wait()会在定时器到期(5秒钟)后才返回.

定时器总是有两个状态: "到期" 或 "未到期".如果deadline_timer::wait()函数在一个已过期的时间上执行,会立即返回.

  t.wait();

最后在时间到期后输出著名的"Hello, world!"消息.

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

定时器2--异步使用定时器

本范例修改了time1范例,通过在定时器上异步等待演示了如何使用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));

接下来,替换范例1中的阻塞等待.调用deadline_timer::async_wait()函数执行异步等待.在这个函数中传递了上面定义的print回调函数.

  t.async_wait(&print);

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

Asio库保证回调函数在调用了io_service::run()的线程上执行.因此只有调用了io_service::run()函数,异步等待完成的回调函数才能被调用.

Io_service::run()将会一直在运行,因为其还有工作要做.本例中,这个工作是异步等待定时器,因此直到定时器过期而且回调函数执行完毕,才会退出.

在调用io_service::run()前为其指定工作是很重要的.例如,如果上例中忘记调用deadline_timer::async_wait(),io_service无事可做,则io_service::run()调用会立即返回.

  io.run();
  return 0;
}

定时器3-向处理器传递参数

本程序从Timer2程序修改而来,将timer改为每秒触发一次.用来阐述如何向处理函数传递其他参数.

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

为了让timer循环的过期,需要在回调函数中修改timer的过期时间,而后开始一个新的异步等待.当然必须让回调函数可以访问到timer对象.要实现这个效果需要向print函数传递两个参数:

  • 指向timer对象的指针.
  • Timer触发计数器,以便于实现触发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));

而后在timer上启动一个新的异步等待.boost::bind()函数用来将回调函数与参数相关联.deadline_timer::async_wait()函数希望的回调函数形式为void(const boost::system::error_code&).bind函数将回调函数print连同附加的参数转换为这种正确的函数签名.

更多Boost.Bind信息见Boost.Bind documentation.

本例中,boost::bind()的boost::asio::placeholders::error参数是传递给成了函数的错误对象名称占位符.如果使用boost::bind()初始化异步操作,必须指定与句柄参数列表相一致的参数.在范例4中回调函数不需要这个参数会省略掉.

    t->async_wait(boost::bind(print,
          boost::asio::placeholders::error, t, count));
  }
}
 
int main()
{
  boost::asio::io_service io;

添加一个新的count变量,以便于在timer触发6次后结束程序运行.

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

如步骤4,在main函数中调用了deadline_timer::async_wait()时会向print函数传递附加参数.

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

最后,验证print句柄函数中的count变量,输出其值.

  std::cout << "Final count is " << count << "\n";
 
  return 0;
}

定时器4 -使用成员函数作为处理器

本例演示如何将类的成员函数作为回调处理函数.程序功能与Timer3完全一样.

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

上一个范例中使用print函数作为回调处理函数,本例中首先定义一个printer类.

class printer
{
public:

类的构造函数中有个io_service的引用,用来初始化timer_成员.关闭程序的计时器也声明为类的成员.

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

Boost::bind()函数也可用于处理类的成员函数.由于所有的非静态类成员函数都有一个隐式的this参数,我们需要将this绑定到函数上.与Timer3范例一样,使用boost::bind()转换回调处理函数,将其处理为void(constboost::system::error_code&)形式的签名.

注意这里boost::asio::placeholders::error占位符被忽略,成员函数print不在接收错误对象参数.

    timer_.async_wait(boost::bind(&printer::print, this));
  }

在类的析构函数输出最终的计数器值.

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

成员函数print与上例的print函数非常类似.由于计数器和timer都做为了类的成员,因此不必在进行参数传递了.

  void print()
  {
    if (count_ < 5)
    {
      std::cout << count_ << "\n";
      ++count_;
 
      timer_.expires_at(timer_.expires_at() + boost::posix_time::seconds(1));
      timer_.async_wait(boost::bind(&printer::print, this));
    }
  }
 
private:
  boost::asio::deadline_timer timer_;
  int count_;
};

Main函数与上例也很相似,现在声明一个printer对象并运行io_service.

int main()
{
  boost::asio::io_service io;
  printer p(io);
  io.run();
 
  return 0;
}

同步TCP daytime客户端

本例展示如何使用asio实现一个TCP客户端.

需要包含如下头文件.

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

程序的目的是要访问daytime服务器,因此需要用户指定一个服务器.

using boost::asio::ip::tcp;
 
int main(int argc, char* argv[])
{
  try
  {
    if (argc != 2)
    {
      std::cerr << "Usage: client <host>" << std::endl;
      return 1;
    }

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

    boost::asio::io_service io_service;

我们需要将程序参数中指定的服务器名称转换为TCP端点,因此需要定义一个ip::tcp::resolver对象.

    tcp::resolver resolver(io_service);

分析器使用一个查询对象并获取一个端点列表.使用服务器的名称(参数argv[1])以及服务名称(本例中是daytime)作为参数来构造查询对象.

    tcp::resolver::query query(argv[1], "daytime");

返回的端点列表为ip::tcp::resolver::iterator类型的迭代器.

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

现在创建并连接Socket.上述的端点列表可能包括IPv4或IPv6类型的端点,需要尝试每种可能获取可用的端点.这样客户端就独立于IP版本号了. boost::asio::connect()自动完成这些功能.

    tcp::socket socket(io_service);
    boost::asio::connect(socket, endpoint_iterator);

连接已经打开.现在就可以从服务端读取数据了.

使用boost::array 来接收数据. boost::asio::buffer()函数自动管理数组大小以防止缓冲区溢出.除了boost::array ,还可以使用char[]或std::vector.

    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.
      else if (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;
  }

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()
{
  using namespace 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协议来侦听13端口.

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

这是个可迭代的服务端,即每次只能处理一个连接.对每个客户端链接创建一个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), ignored_error);
    }
  }

最后处理异常.

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

异步TCP daytime服务端

main函数

int main()
{
  try
  {

首先创建一个服务对象接收客户端的连接. io_service对象提供IO服务,如socket就使用这个IO服务对象.

    boost::asio::io_service io_service;
    tcp_server server(io_service);

运行io_service对象使其执行异步操作.

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

tcp_server 类

class tcp_server
{
public:

构造函数初始化接收器监听TCP的13端口.

  tcp_server(boost::asio::io_service& io_service)
    : acceptor_(io_service, tcp::endpoint(tcp::v4(), 13))
  {
    start_accept();
  }
 
private:

函数start_accept()创建一个Socket并初始化为异步接收操作,等待新的连接.

  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));
  }

由start_accept()函数初始化的异步接收操作完成后会调用handle_accept()函数.其响应客户端的请求并再次调用start_accept()函数初始化下一次接收操作.

  void handle_accept(tcp_connection::pointer new_connection,
      const boost::system::error_code& error)
  {
    if (!error)
    {
      new_connection->start();
    }
 
    start_accept();
  }

Tcp连接类

使用shared_ptr和enable_shared_from_this保证tcp_connetion在有相应操作期间保持激活状态.

class tcp_connection
  : public boost::enable_shared_from_this<tcp_connection>
{
public:
  typedef boost::shared_ptr<tcp_connection> pointer;
 
  static pointer create(boost::asio::io_service& io_service)
  {
    return pointer(new tcp_connection(io_service));
  }
 
  tcp::socket& socket()
  {
    return socket_;
  }

在函数start()中调用boost::asio::async_write()向客户端写数据.注意使用的是boost::asio::async_write()而不是ip::tcp::socket::async_write_some(),这样可以确保发送全部的数据.

  void start()
  {

发送的数据存储在类成员message_中,在异步操作完成前必须保证数据的有效性.

    message_ = make_daytime_string();

初始化异步操作后,使用boost::bind()传递的参数必须与事件处理函数参数列表相匹配.本例中, boost::asio::placeholders::error 和 boost::asio::placeholders::bytes_transferred参数占位符都被去掉了,因为他们没有在handle_write()中使用.

    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));

再次处理客户端连接请求需要由handle_write()负责.

  }
 
private:
  tcp_connection(boost::asio::io_service& io_service)
    : socket_(io_service)
  {
  }
 
  void handle_write(const boost::system::error_code& /*error*/,
      size_t /*bytes_transferred*/)
  {
  }
 
  tcp::socket socket_;
  std::string message_;
};

移除无用的事件处理函数的参数

你可能发现error和bytes_transferred参数在函数handle_write()中是无用的.如果参数不需要可将其移除:

  void handle_write()
  {
  }

boost::asio::async_write()的调用调整为如下形式:

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

DDaytime4-同步UDP daytime客户端

本范例程序展示如何使用asio来实现UPD客户端程序.

#include/span> <iostream>

#include <boost/array.hpp>

#include <boost/asio.hpp>

 

using boost::asio::ip::udp;;

启动程序的方式和TCP daytime客户端相同.

int/span> main(int argc, char* argv[])

{

  try

  {

    if (argc != 2)

    {

      std::cerr << "Usage: client <host>" << std::endl;

      return 1;

    }

 

    boost::asio::io_service io_service;;

ip::udp::resolver传递主机和服务名称获取正确的远程端点.查询通过ip::udp::v4()参数限制使其返回一个IPv4协议的端点.

    udp::resolver resolver(io_service);

    udp::resolver::query query(udp::v4(), argv[1], "daytime"));

ip::udp::resolver::resolve()函数保证如果查询成功的话会返回至少有一个端点的列表.因此可以安全的直接对返回值进行解引用.

   udp::endpoint receiver_endpoint = *resolver.resolve(query));

由于UDP是基于数据报的,没有使用基于流的Socket.创建一个ip::udp::socket并使用远程端点进行初始连接.

   udp::socket socket(io_service);

   socket.open(udp::v4());

   boost::array<char, 1> send_buf  = {{ 0 }};

   socket.send_to(boost::asio::buffer(send_buf), receiver_endpoint));

现在需要准备好接收服务端发送过来的数据.客户端接收服务端响应的端点要通过ip::udp::socket::receive_from()指定.

   boost::array<char, 128> recv_buf;

   udp::endpoint sender_endpoint;

   size_t len = socket.receive_from(

       boost::asio::buffer(recv_buf), sender_endpoint);

   std::cout.write(recv_buf.data(), len);

 }}

 

 catch (std/span>::exception& e)

 {

   std::cerr << e.what() << std::endl;

 }

 return 0;

}

Daytime5- daytime同步UDP服务端

本范例程序展示如何使用asio实现同步UDP服务程序.

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

创建ip::udp::socket对象接收UDP 13端口上的请求.

    udp::socket socket(io_service, udp::endpoint(udp::v4(), 13));

等待客户端请求连接.remote_endpoint对象将传入ip::udp::socket::receive_from()函数.

    for (;;)
    {
      boost::array<char, 1> recv_buf;
      udp::endpoint remote_endpoint;
      boost::system::error_code error;
      socket.receive_from(boost::asio::buffer(recv_buf),
          remote_endpoint, 0, error);
 
      if (error && error != boost::asio::error::message_size)
        throw boost::system::system_error(error);

接着实现向客户端发送数据.

      std::string message = make_daytime_string();

向remote_endpoint发送响应.

      boost::system::error_code ignored_error;
      socket.send_to(boost::asio::buffer(message),
          remote_endpoint, 0, ignored_error);
    }
  }

最后处理异常.

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

Daytime6- daytime异步UDP服务端

main()函数

int main()
{
  try
  {

创建服务对象接收客户端请求,并运行io_service对象.

    boost::asio::io_service io_service;
    udp_server server(io_service);
    io_service.run();
  }
  catch (std::exception& e)
  {
    std::cerr << e.what() << std::endl;
  }
 
  return 0;
}

udp_server类

class udp_server
{
public:

构造函数初始化socket并侦听UDP的13端口.

  udp_server(boost::asio::io_service& io_service)
    : socket_(io_service, udp::endpoint(udp::v4(), 13))
  {
    start_receive();
  }
 
private:
  void start_receive()
  {

ip::udp::socket::async_receive_from()函数使应用程序在后台侦听新的请求.当接收到请求,io_service对象使用两个参数来调用handle_receive函数:一个描述操作成功失败的boost::system::error_code类型参数,和一个size_t类型的bytes_transferred 指定接收到的字节数.

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

handle_receive()函数处理客户端请求.

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

error参数包含异步操作的结果.由于这里只提供了一个字节的recv_buffer_来存放客户端请求,如果客户端发送的数据过长则io_service会返回错误信息.如果发生错误则忽略.

    if (!error || error == boost::asio::error::message_size)
    {

生成发送给客户端的数据.

      boost::shared_ptr<std::string> message(
          new std::string(make_daytime_string()));

调用ip::udp::socket::async_send_to()函数将数据发送到客户端.

      socket_.async_send_to(boost::asio::buffer(*message), remote_endpoint_,
          boost::bind(&udp_server::handle_send, this, message,
            boost::asio::placeholders::error,
            boost::asio::placeholders::bytes_transferred));

当初始化异步操作时,如果使用boost::bind(),必须指定与回调函数相匹配的参数列表.本程序中,两个参数占位符(boost::asio::placeholders::error 和 boost::asio::placeholders::bytes_transferred)应该被移除.

开始侦听下一个客户端请求.

      start_receive();

对客户请求的更多处理由handle_send()负责.

    }
  }

handle_send()函数在服务请求结束后调用.

  void handle_send(boost::shared_ptr<std::string> /*message*/,
      const boost::system::error_code& /*error*/,
      std::size_t /*bytes_transferred*/)
  {
  }
 
  udp::socket socket_;
  udp::endpoint remote_endpoint_;
  boost::array<char, 1> recv_buffer_;
};

Daytime7-TCP/UDP异步服务

本范例程序展示如何将已经写过的两个异步服务整合到一个服务程序中.

main()函数

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

创建一个接收TCP客户端连接的服务对象.

    tcp_server server1(io_service);

创建一个接收UDP客户端请求的服务对象.

    udp_server server2(io_service);

已经创建了两个基于io_service对象上的工作对象.

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

tcp_connection 和 tcp_server 类

如下两个类取自Daytime3范例.

class tcp_connection
  : public boost::enable_shared_from_this<tcp_connection>
{
public:
  typedef boost::shared_ptr<tcp_connection> pointer;
 
  static pointer create(boost::asio::io_service& io_service)
  {
    return pointer(new tcp_connection(io_service));
  }
 
  tcp::socket& socket()
  {
    return socket_;
  }
 
  void start()
  {
    message_ = make_daytime_string();
 
    boost::asio::async_write(socket_, boost::asio::buffer(message_),
        boost::bind(&tcp_connection::handle_write, shared_from_this()));
  }
 
private:
  tcp_connection(boost::asio::io_service& io_service)
    : socket_(io_service)
  {
  }
 
  void handle_write()
  {
  }
 
  tcp::socket socket_;
  std::string message_;
};
 
class tcp_server
{
public:
  tcp_server(boost::asio::io_service& io_service)
    : acceptor_(io_service, tcp::endpoint(tcp::v4(), 13))
  {
    start_accept();
  }
 
private:
  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));
  }
 
  void handle_accept(tcp_connection::pointer new_connection,
      const boost::system::error_code& error)
  {
    if (!error)
    {
      new_connection->start();
    }
 
    start_accept();
  }
 
  tcp::acceptor acceptor_;
};

udp_server 类

同样,下面的类取自上一个范例.

class udp_server
{
public:
  udp_server(boost::asio::io_service& io_service)
    : socket_(io_service, udp::endpoint(udp::v4(), 13))
  {
    start_receive();
  }
 
private:
  void start_receive()
  {
    socket_.async_receive_from(
        boost::asio::buffer(recv_buffer_), remote_endpoint_,
        boost::bind(&udp_server::handle_receive, this,
          boost::asio::placeholders::error));
  }
 
  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();
    }
  }
 
  void handle_send(boost::shared_ptr<std::string> /*message*/)
  {
  }
 
  udp::socket socket_;
  udp::endpoint remote_endpoint_;
  boost::array<char, 1> recv_buffer_;
};

范例

分配内存

本范例展示如何为异步操作分配必需的内存.

缓冲区

本范例展示如何为Socket读写操作创建有引用计数的缓冲区.

聊天程序

本范例实现了聊天程序的服务端和客户端.这个程序使用具有6字节的消息头和可变长消息体的自定义协议.

下面针对POSIX的聊天客户端演示如何使用posix::stream_descriptor类执行控制台输入输出.

Echo

展示如何使用同步和异步操作实现一系列简单的客户端和服务端.

fock

针对POSIX 的范例程序演示了如何使用Boost.Asio结合fork()系统调用.第一个范例阐述了如何启动守护进程:

第二个范例阐述了如何在完成句柄中启动进程.

HTTP客户端

范例程序简单的实现了HTTP1.0客户端.程序演示了如何使用read_until和async_read_until函数.

HTTP服务

范例阐述了在单线程中使用asio实现HTTP1.0服务.演示了如何执行清除命令结束未完成的异步操作.

HTTP服务2

使用io_service-per-CPU 设计的HTTP服务.

HTTP服务3

使用单个io_service并在线程池上调用io_service::run()的HTTP服务.

HTTP服务4

使用无栈协同技术实现单线程HTTP服务.

ICMP

展示如何在ICMP上使用原生的Socket ping远程主机.

调用

展示如何自定义处理函数调用.将完成句柄加入到一个优先级队列而不是直接调用.

iostream

展示如何使用ip::tcp::iostream.

广播

展示使用广播向订阅组传输包.

序列化

展示如何在asio中使用Boost.Serialization序列号和反序列化结构体,从而实现在Socket上传输数据对象.

服务

展示如何向asio的io_service整合自定义功能(本例中是添加了日志功能),以及在basic_stream_socket<>上使用自定义服务.

SOCKS4协议

范例客户端程序实现了通过代理通信的SOCKS4协议.

SSL

客户端和服务端范例程序演示了在异步操作中使用ssl::stream<>模板.

超时设定

下面的一系列范例展示了如何在指定时间之后取消长时间运行的异步操作.

定时器

范例演示了如何定制deadline_time实现不同类型的计时器.

Porthopper

本例演示了混合同步和异步操作,及如何在Boost.Asio中使用Boost.Lambda.

无阻塞

展示如何整合实现IO操作的第三方库来实现Reactor风格的操作.

UNIX领域的Socket

展示如何使用UNIX领域的Socket.

Windows

展示如何在Boost.Asio中使用Windows特有的TransmitFile函数.

posted @ 2015-04-12 19:27  廖先生  阅读(854)  评论(0编辑  收藏  举报