asio 基本解析

文章介绍了Unix/Linux系统中的I/O基础,包括内核的角色、进程如何通过系统调用进行I/O操作。接着讨论了文件I/O模型,并指出Asio库是如何简化I/O操作的,提供同步和异步操作接口。最后,通过例子分析了Asio库的基本工作原理,涉及I/O执行上下文和异步操作的完成处理。

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

1.基础知识

在uinix/Linux操作系统中,我们编写的应用程序,哪怕只有一个打印,都是需要使用I/O的。但是我们的应用程序运行后的一个实例,是一个进程,一般情况下只能够访问自己的存储空间,因为这个进程只能使用在“用户态”下的CPU,从而有些资源无法访问到。当我们的应用需要使用一些文件、硬件驱动程序、网络驱动程序,完成对应的文件硬件网络操作(在Linux下统一为I/O操作),就无法做到了。这个时候进程为了完成作业,就要把自己的使用参数传递给内核,内核使用“内核态”的CPU去完成所有的I/O操作,完成后将数据返还给进程。如图一所示,进程通过系统调用 的方式向内核传递请求。

请添加图片描述

图一

那么内核是什么呢?

内核是指管理和分配计算机资源(即CPU、RAM和设备)的核心层软件。
虽然在没有内核的情况下,计算机也能够运行程序,但有了内核会极大减少其他程序的编写和使用,令程序员的“功力”大进、游刃有余。这要归功于内核为管理计算机的有线资源所提供的的软件层。

引用自 《Linux_UNIX系统编程手册》

同时,这里也介绍一下内核的基本职责,正是内核的这些职责,才可以让我们比较单纯去写业务代码,把完成业务的操作留给内核去分配。

内核职责:

  • 进程调度
    即利用一组规则控制哪些进程获取对CPU的使用,以及每个进程使用多少时间。
  • 内存管理
    采用虚拟内存管理机制。
  • 提供文件系统
    内核在磁盘上提供有文件系统,允许对执行创建、获取、更新和删除等操作。
  • 创建和终止进程
    内核可将新程序载入内存,为其提供所需的资源(CPU、内存以及对文件的访问等)。这样的一个进程一旦执行完毕,内核要确保释放其占用资源,以供后续进程使用。
  • 对设备的访问
    计算机外接设备(鼠标、键盘、磁盘等)可实现计算机与外部世界的通信,这一通信机制包括输入、输出或者二者皆有,内核既为程序访问提供设备提供了简化版的标准接口,同时还要协调多个进程对每一个设备的访问。
  • 联网
  • 提供系统调用应用编程接口

可以看到上面的最后一条,内核提供了系统调用的应用编程接口,平时用到的c语言的标准库许多函数就是对部分应用编程接口的上层封装。

2.文件I/O模型

UNIX、Linux系统的I/O概念是通用的,也就是说,同一套系统调用(open() 、read()、write()、close()等)所执行的I/O操作,可适用于所有文件类型,包括设备文件在内。(应用程序发起的I/O请求,内核会将其转换为相应的文件系统操作,或者设备驱动程序操作,以此来执行针对目标文件或者设备的I/O操作)因此,采用这些系统调用的程序能够操作任何类型的文件,同样的,对socket的操作也在这个概念之内。

于是,对于我们用户使用(仅仅是使用层面)来说,asio 其实就是一组基于I/O的系统调用的封装,我们可以不用明白内核,不了解系统调用,就完全可以使用。使用asio提供的统一接口,让我们避免了自己直接去使用系统调用。看起来系统调用似乎很简单,但是当我们的程序,访问量比较大或者计算任务比较密集的时候,我们程序的系统调用逻辑往往会比较复杂,返回结果的处理也比较复杂,使用asio恰好可以简化这个过程,使我们专注于业务。

3.asio基本剖析

下面将从官方资料对asio进行基本剖析(图片中的英文会保留使用哦)

boost.asio可以在socket这样的I/O对象上执行同步和异步的操作,下面用图二来介绍一下,当我们想要在一个socket上执行一个connet()操作时会发生什么,先从验证一个同步操作开始

在这里插入图片描述

图二

首先,需要一个 I/O execution context,因为我们的系统调用,对内核(operating system)的操作就是由它来完成的。我们一般最常用的context就是

boost::asio::io_context io_context;

your Program要执行一个I/O 操作,那一定是需要一个 I/O object 的,例如 TCP socket

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

执行一个同步连接操作,会发生下面这些事情

  1. Your program 调用 I/O object对象来启动连接操作
   socket.connect(server_endpoint);
  1. I/O object 将请求传递给 I/O execution context
  2. I/O execution context 通过系统调用,让内核(operating system)去执行连接操作
  3. 内核(operating system)返回执行结果到 I/O execution context
  4. I/O execution context 将返回的任何错误结果翻译为 boost::system::error_code 类型的对象
  5. 如果操作失败, I/O object 抛出 boost::system::system_error 类型的异常,如果启动操作的代码被编写为
       boost::system::error_code ec;
       socket.connect(server_endpoint, ec);
  • error_code 类型变量 ec 就被设置为操作的结果,就不会有异常抛出。

如图三执行一个异步操作的时候,一些不同的事情将会生
在这里插入图片描述

图三
  1. Your program 通过调用 I/O object 来启动连接操作
      socket.async_connect(server_endpoint, your_completion_handler);
  • your_completion_handler是带有签名(签名指的是一个函数的信息,包括函数名、参数类型、所在类、名称空间及其他信息)的函数或函数对象。
      void your_completion_handler(const boost::system::error_code& ec);
  • 所需的确切签名取决于正在执行的异步操作。参考文档(boost的官方文档里面可以找到喔)指出了每个操作的相应表单
  1. I/O object 传递请求给 I/O execution context
  2. I/O execution context 给内核(operating system)的信号,告诉内核(operating system)它现在需要执行一个一部连接的操作

在这里插入图片描述

图四

接下来如图四

    1. 内核(Operating System)通过将结果放在队列中来指示连接操作已完成,该队列已准备好由 I/O execution context 选取。
    2. 当用io_context 作为 I/O execution context,your program 一定要调用 io_context::run() (或者和调用一个类似的io_context成员函数)来检索结果。当有未完成的异步操作时,调用io_context::run() 会阻塞,因此,在第一个异步操作开始之后,就尽可能直接调用。
    3. 在io_context::run()的调用内部,I/O execution context 把操作结果出列,翻译成 an error_code ,传给 your_completion_handler。

asio的一个基本解析大概就是这个样子。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值