Boost.Asio网络库 学习笔记

发布时间 2023-09-09 12:35:09作者: BryceAi

Asio网络库 学习笔记


Boost官网:https://www.boost.org/

Boost库 许可证:

//          Copyright Joe Coder 2004 - 2006.
// Distributed under the Boost Software License, Version 1.0.
//    (See accompanying file LICENSE_1_0.txt or copy at
//          https://www.boost.org/LICENSE_1_0.txt)

1. Asio简介

Asio是一个跨平台的C++网络库,它是Boost库的一部分,它是异步输入输出的核心。Asio提供了一组异步的I/O操作,包括TCP和UDP的socket、定时器、串口等。Asio的核心是一个事件循环,它使用epoll、kqueue、IOCP等系统调用来实现异步I/O操作。Asio的事件循环是单线程的,但是它提供了一些接口来实现多线程的异步操作。

这里引用文章Boost asio 官方教程简介的例子:异步任务的典型例子是网络应用。 如果数据被发送出去了,比如发送至 Internet,通常需要知道数据是否发送成功。 如果没有一个象 Boost.Asio 这样的库,就必须对函数的返回值进行求值。 但是,这样就要求待至所有数据发送完毕,并得到一个确认或是错误代码。 而使用 Boost.Asio,这个过程被分为两个单独的步骤:第一步是作为一个异步任务开始数据传输。 一旦传输完成,不论成功或是错误,应用程序都会在第二步中得到关于相应的结果通知。 主要的区别在于,应用程序无需阻塞至传输完成,而可以在这段时间里执行其它操作。

2. Asio的使用

2.1. Asio的安装

这里举例Unix/Linux 环境下的安装。

Asio是Boost库的一部分,所以要使用Asio,首先要安装Boost库。

  1. 下载Boost库

    Boost库的下载地址:https://boostorg.jfrog.io/artifactory/main/release/
    或者 https://sourceforge.net/projects/boost/files/

    首先我们创建一个目录,用来存放Boost库的源码和编译后的库文件。

    # 进入用户目录
    cd ~ 
    # 创建Boost目录
    mkdir Boost  
    # 进入Boost目录
    cd Boost  
    

    然后下载Boost库的源码,这里下载的是Boost 1.83.0版本的源码。

    wget https://boostorg.jfrog.io/artifactory/main/release/1.83.0/source/boost_1_83_0.tar.gz
    

    国内下载可能有点慢。建议用第二个网站。

  2. 解压Boost库

    tar -zxvf boost_1_83_0.tar.gz
    
  3. 编译Boost库

    cd boost_1_83_0
    ./bootstrap.sh
    ./b2
    

    编译完成后,会有如下提示:

    The Boost C++ Libraries were successfully built!
    
    The following directory should be added to compiler include paths:
    
        /home/username/boost/boost_1_83_0
    
    The following directory should be added to linker library paths:
    
        /home/username/boost/boost_1_83_0/stage/lib
    

    这里的username是你的用户名。

  4. 安装Boost库

    sudo ./b2 install
    

注意,由于Asio库使用了C++11的特性,所以编译时最好要加上-std=c++11选项,并且Asio使用了thread进行异步操作,所以编译时加上-pthread选项。

3. Asio工作原理

这里主要参考Asio官方文档

对于asio库,程序至少要包含一个io_context对象,这个对象代表程序链接到操作系统的I/O服务。io_context对象提供了一种将事件处理程序与操作系统的I/O服务进行交互的方法。boost::asio::io_contextboost::asio::thread_poolboost::asio::system_context

boost::asio::io_context io_context;

执行I/O操作都需要一个I/O对象,比如socket、定时器、串口等。

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

有两种类型的I/O操作:同步操作和异步操作。同步操作会阻塞当前线程,直到操作完成。异步操作会在操作完成后调用一个事件处理程序。

对于同步操作:

  1. 程序在开始阶段需要连接I/O对象。

    socket.connect(server_endpoint);
    
  2. I/O对象将请求转发到io_context对象。

  3. io_context对象调用操作系统来执行连接操作。

  4. 操作系统将操作结果返回到io_context对象。

  5. io_context对象将操作产生的任何错误转换为类型的对象。可以将与特定值进行比较,也可以作为布尔值进行测试(其中结果表示没有发生错误)。然后将结果转发回I/O对象。boost::system::error_codeerror_codefalse

  6. 如果操作失败,I/O对象将引发一个类型的异常。如果启动操作的代码改为:boost::system::system_error

    boost::system::error_code ec;
    socket.connect(server_endpoint, ec);
    

然后将变量设置为操作的结果,并且不会引发异常。error_codeec

对于异步操作:

  1. 您的程序通过调用I/O对象来启动连接操作:

    socket.async_connect(server_endpoint, your_completion_handler);
    

    其中是具有签名的函数或函数对象:your_completion_handler

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

    所需的确切签名取决于正在执行的异步操作。

  2. I/O对象将请求转发到io_context对象。

  3. io_context对象向操作系统发出信号,表示它应该启动异步连接。

    等待(在同步情况下,此等待将完全包含在连接操作的持续时间内。)
    (官方文档里给的词是 Time passes. 个人认为其含义是io_context对象等待系统响应。按我个人的理解:此时系统并不会立即响应请求,而是将请求放入队列,按照系统调度响应。此时io_context对象可以进行其他操作,也可以进行等待。)

  1. 操作系统通过将结果放在队列中来指示连接操作已经完成,该队列准备由io_context对象拾取。
  2. 当使用作io_context对象时,程序必须调用(或调用类似的成员函数之一)才能检索结果。在有未完成的异步操作时对块的调用,因此通常在启动第一个异步操作后立即调用它。io_contextio_context::run()io_contextio_context::run()
  3. 在对的调用中,I/O执行上下文将操作的结果排成队列,将其转换为,然后将其传递给您的完成处理程序。io_context::run()error_code

4. Asio的使用

4.1 简单使用

下面这个程序用来实现一个简单的UDP广播。

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

int main() {
    try {
        boost::asio::io_service io_service; // 创建io_service对象
        boost::asio::ip::udp::socket socket(io_service); // 创建socket对象

        // 设置广播地址和端口
        boost::asio::ip::udp::endpoint broadcast_endpoint(
            boost::asio::ip::address::from_string("255.255.255.255"), 50001);

        // 开启广播选项
        socket.open(boost::asio::ip::udp::v4()); // 打开套接字
        socket.set_option(boost::asio::socket_base::broadcast(true)); // 设置广播选项

        // 绑定套接字到本地端口
        boost::asio::ip::udp::endpoint local_endpoint(
            boost::asio::ip::address::from_string("0.0.0.0"), 0);
        socket.bind(local_endpoint); // 绑定套接字到本地端口

        std::string message = "Hello, UDP Broadcast!";
        socket.send_to(boost::asio::buffer(message), broadcast_endpoint);   // 发送广播消息

        std::cout << "Sent message: " << message << std::endl;
    } catch (std::exception& e) {
        std::cerr << "Exception: " << e.what() << std::endl;
    }

    return 0;
}