Boost::Asio库详解

本文详细介绍了Asio异步I/O服务的实现原理,包括io_service在线程调度、Proactor与Reactor模型的跨平台实现,以及如何高效地使用buffer和streambuf进行数据处理。此外,还探讨了Asio在不同场景下的应用,如网络读写、数据分段处理等。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

io_service

所有的异步操作:异步网络读写,异步时钟,都在io_service.run()时进行轮询。有趣的是,io_service在线程利用方面下了很大的功夫,你可以在主线程建立它的实例,但是在多个线程里面runio_service很擅长于将需要执行的回调函数分配到空闲线程当中。

io_service为了跨平台,不得不在Linux下也实现Proactor模型。再说一遍,Asio的实现是Proactor模型。在Linux普遍是Reactor的情况下模拟出Proactor,作者也是很辛苦啊。run里面同时需要调度来自用户的Handler和来自操作系统的IO请求。

这里有一个Service的概念。Service是从一类IO操作中抽象出来的一些共同的地方。在你调用 socket.async_read之类的操作之后,事实上它将真正跟操作系统打交道的操作托管给了 basic_stream_socket_service

io_service的调度有多种实现,在 epoll 实现里,作者很机智地把 "有空闲进程" 事件转化为一个水平触发(Level Trigger),这样就可以在等待中醒来,进行调度了。

buffer

库中提供了mutable_bufferconst_buffer两种单个缓冲,以及 mutable_buffers_1const_buffers_1两种缓冲序列,asio提供了一些操作:

  • buffer_cast<char*>(mb)单个缓冲转成指针

  • buffer_size(buf):取得缓冲区大小

  • buffer_copy(bufs, bufd):缓冲区(包括单个和序列)之间复制尽可能多的元素,它是安全的,不会出现溢出的情况

//`buffer` can wrap nearly everything
vector<char> underlying_buffer(50);
auto buf1 = buffer(underlying_buffer);

char underlying_string[] = "Hello";
auto buf2 = buffer(underlying_string);

buffer_copy(buf2, buf1); // returns 6

std::string s(buffer_begin(buf1), buffer_end(buf1));

streambuf

streambuf是Asio能灵活地异步调控数据的关键。它能自动增长和回收consumed space。在使用的时候有这些要点:

  • streambuf分为input sequence和output sequence两部分,这都是继承自std::streambuf的理念。

  • data()来获取输入序列(常缓冲),prepare(n)来获取输出序列(变缓冲)。

  • commit(n)来将从输出序列提交到输入缓冲,用 consume(n) 来将输入缓冲中的数据丢弃,代表已经处理完毕。

  • read数据的过程:先prepare好固定大小的缓冲区,然后buffer_copy进去一些数据,拷贝了多少数据就commit多少数据。然后再从prepare开始,拷贝到手头上的数据没有了为止。

  • streambuf不可拷贝,所以乖乖传引用或者指针吧。

这里有一些文档没有说明但是需要了解的细节:

  • 自动增长的功能是通过reserve(n)函数管理的,这个函数在overflowprepare处会调用,用于保证输出缓冲区里有至少n字节的空间。

  • 缓冲区的大小是没有限制的,max_size一般是size_t能表示的最大数字,相当于内存的极限。

  • std::istream(sb).ignore(n)sb.gbump(n)sb.consume(n)有相同的效果,但是ignoreconsume有防下溢机制。

async_read_xxxx

值得注意的是async_read_until(socket, streambuf, condition, handler)函数,给我们处理数据分段带来了极大的方便。它内建了4种条件决定read到什么时候停止 :

  • 出现了某个字符

  • 出现了某条字串

  • 符合了某条 Regular Expression

  • 符合了某条谓词的条件

注意:这里的"出现"指的是streambuf的input sequence中出现,也就是如果原本streambuf中的内容已经符合条件,则async_read_until将立即呼叫回调。
推论:某些库的until是不包含结束符的,比如readLine没有换行符。但是asio是包含的。

using boost::system::error_code;

// this piece of code shows how to read Chunked Tranfer-Encoding
streambuf sbuf;
std::string content;

void chunked_handler(const error_code& ec, std::size_t sz)
{
    std::istream is(sbuf);
    std::size_t chunk_size;

    is >> std::hex >> chunk_size;
    std::assert(is.get() == '\r' && is.get() == '\n');

    async_read(socket, sbuf,
        [chunk_size] (const error_code& ec, std::size_t) {
            return chunk_size + 2 - sbuf.size();}, 
        [chunk_size] (const error_code& ec, std::size_t) {
            std::istream is(sbuf);
            boost::scoped_array<char> cbuf(new char[chunk_size]);

            is.read(cbuf, chunk_size);
            content += std::string(cbuf, chunk_size);

            std::assert(is.get() == '\r' && is.get() == '\n');

            async_read_until(socket, sbuf,
                std::string("\r\n"), chunked_handler); });
}

async_read_until(socket, sbuf,
    std::string("\r\n"), chunked_handler);

Networking

基本上都是ip::tcp里的东西了。acceptor相当于Java里的 ServerSocket,没有任何数据传输的功能,只有作为服务器监听端口接收连接的功能。socket就是一般意义上的socket了,没有什么特别的。iostream 是一个很聪明的设计,你可以用 operator >>operator << 来进行数据传输了,不过是同步阻塞的。这是使用上的一些要点:

  • socket::read_some是非阻塞的,socket::async_read_some会立即回调。read some的意义是,有多少读多少,没有就不读直接返回。write_some同理,如果网络不畅导致内核缓冲区满的话,返回0都是有可能的。

  • async_***的话很多时候要灵活运用 std::bind了。

  • query 中的服务参数service可以是端口或者服务名,定义在/etc/services中。

  • socket::connect 仅对一个endpoint进行连接,connect可对迭代器所指示的一系列endpoint进行连接,直到有其中一个成功连接为止。

using boost::system::error_code;

// server: accept
tcp::ip::socket sock(service);
tcp::ip::acceptor acc(service);
tcp::ip::endpoint ep(tcp::ip::v4(), 8080);

acc.async_accpet(sock, eq, [sock] (const error_code&) {
    // new connection handling
});

//client: resolve + connect
tcp::ip::resolver resolver(service);
tcp::ip::resolver::query qurey("www.example.com", "http" /*"80"*/);

resolver.async_resolve(query, [] (
        const error_code& ec,
        tcp::ip::resolver::iterator i) {
    tcp::ip::socket sock(service);

    tcp::ip::async_connect(i, [sock] (
            const error_code& ec,
            tcp::ip::resolver::iterator i) {
        // new connection handling
    });
});
<think>我们正在讨论Boost.Asio中的io_service(在较新版本中称为io_context)的核心概念和作用。根据用户的问题,我们需要解释io_service是什么以及它在异步I/O编程中的作用。同时,用户提供的引用材料[1]和[2]虽然不完全匹配,但可以辅助理解。 首先,io_service是Boost.Asio的核心,它负责提供I/O功能的事件循环和任务调度。其主要作用包括: 1. 事件循环:处理异步I/O事件(如套接字读写完成、定时器到期等)。 2. 任务调度:允许用户投递(post)自定义任务到事件循环中执行。 接下来,结合引用材料: - 引用[1]提到:Boost.Asio执行“工作”(work)并通知应用程序。这体现了io_service的事件处理机制。 - 引用[2]提到:post和dispatch的区别,说明任务调度时的执行策略(立即执行或排队执行)。 因此,回答将围绕这些点展开,并强调io_service在异步编程中的核心地位。 最后,生成相关问题以扩展用户对io_service的理解。</think>### Boost.Asio 的 `io_service` 概念与作用详解 `io_service`(在新版 Boost.Asio 中重命名为 `io_context`)是 **异步 I/O 操作的核心调度器**,本质上是一个**事件循环引擎**。它负责管理所有异步操作的任务队列、事件分发和回调执行,是 Boost.Asio 异步编程的基石。 --- ### 一、核心作用 #### 1. **I/O 事件调度中心** - 管理所有异步操作(套接字读写、定时器、信号等)的事件队列 - 通过 `run()` 方法启动事件循环,阻塞线程直到所有任务完成: ```cpp boost::asio::io_service io; io.run(); // 启动事件循环(阻塞) ``` - 当有 I/O 事件就绪时,自动调用对应的回调函数(处理程序) #### 2. **任务队列管理器** - 支持通过 `post()` 或 `dispatch()` 添加自定义任务: ```cpp io.post([](){ std::cout << "任务被执行" << std::endl; }); // 添加任务到队列 ``` - `post()` 与 `dispatch()` 的区别[^2]: - `post()`:任务**总是加入队列**,等待下次事件循环执行 - `dispatch()`:若在 `io_service` 关联线程中调用,则**立即执行**;否则加入队列 #### 3. **资源生命周期控制器** - 管理所有异步操作的资源(如套接字、定时器) - 通过 `work` 对象防止事件循环提前退出: ```cpp boost::asio::io_service::work work(io); // 保持事件循环活跃 io.run(); // 即使无任务也持续运行 ``` --- ### 二、工作流程解析 1. **投递任务** - 用户通过 `async_*` 函数(如 `async_read`)或 `post()` 提交任务 2. **事件循环启动** - 调用 `run()` 启动线程的事件循环 3. **事件监听与分发** - 操作系统通知 I/O 事件就绪 → `io_service` 将回调加入执行队列 4. **回调执行** - 在 `run()` 线程中按顺序执行回调函数 - 若使用 `io_service::strand` 可保证线程安全 > 关键特性:**非阻塞异步模型**。当调用 `async_read(socket, buffer, handler)` 时,函数立即返回,实际读写操作由操作系统在后台执行。操作完成后,`io_service` 将 `handler` 加入队列,由 `run()` 线程调用[^1]。 --- ### 三、典型应用场景 ```cpp // 创建 io_service 和 TCP 接收器 boost::asio::io_service io; boost::asio::ip::tcp::acceptor acceptor(io, endpoint); // 异步接受连接 acceptor.async_accept([](boost::system::error_code ec, tcp::socket socket) { if (!ec) { // 新连接处理逻辑 } }); // 启动事件循环(通常用多线程处理) std::thread t([&io](){ io.run(); }); t.join(); ``` 1. **高并发服务器** - 单线程 `io_service` 可处理数千并发连接(通过异步 I/O) 2. **定时任务调度** ```cpp boost::asio::deadline_timer timer(io, boost::posix_time::seconds(1)); timer.async_wait([](...){ /* 定时任务 */ }); ``` 3. **跨线程任务同步** - 通过 `post()` 实现线程间安全的任务传递 --- ### 四、性能优化要点 1. **多线程运行** - 单个 `io_service` 可由多线程同时调用 `run()`: ```cpp std::vector<std::thread> threads; for (int i = 0; i < 4; ++i) { threads.emplace_back([&io](){ io.run(); }); } ``` - 线程数建议与 CPU 核心数匹配 2. **避免回调阻塞** - 回调函数必须**非阻塞**,否则会拖慢整个事件循环 - 耗时任务应投递到专用线程池 3. **使用 `strand` 同步** - 当多线程访问共享资源时: ```cpp boost::asio::io_service::strand strand(io); strand.post([](){ /* 线程安全操作 */ }); ``` --- ### 五、与操作系统的交互 `io_service` 底层封装了操作系统的 I/O 多路复用机制: - Linux: `epoll` - Windows: `IOCP` - macOS: `kqueue` 其通过**就绪事件通知机制**实现高效 I/O 调度,避免轮询造成的 CPU 浪费[^1]。 > 总结:**`io_service` = 异步任务队列 + 事件分发器 + 资源管理器**,是 Boost.Asio 实现高性能网络编程的核心组件。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值