在操作系统和网络编程中,IO(输入/输出)操作是一个非常重要的概念。
在处理IO的时候,阻塞和非阻塞都是同步IO。只有使用了特殊的API才是异步IO。 ——陈硕大神
网络IO层面
典型的一次IO的两个阶段是什么?
数据准备 和 数据读写
数据准备: 根据系统IO操作的就绪状态
· 阻塞
· 非阻塞
数据读写: 根据应用程序和内核的交互方式
· 同步
· 异步
同步IO
同步IO是一种阻塞式的IO模型。在这种模型中,当应用程序发起一个IO操作时(例如,读取文件、从网络套接字接收数据等),它会被阻塞,直到IO操作完成。在此期间,应用程序无法执行其他任务,只能等待IO操作的结果。
以C++的网络IO为例,如果你使用同步的recv
函数来从网络套接字接收数据,那么在调用recv
时,应用程序会进入阻塞状态,直到有足够的数据可读或者发生错误。在此期间,应用程序的其他部分(如用户界面、其他计算任务等)将无法运行。当有足够的数据时,需要应用程序花费自己的运行时间来将数据从系统层面转移到自己所需的地址中。
异步IO
异步IO是一种非阻塞式的IO模型。在这种模型中,当应用程序发起一个IO操作时,它不会立即阻塞,而是可以继续执行其他任务。当IO操作完成时,操作系统会通过某种机制(如回调函数、信号、事件等)通知应用程序。
在C++中,标准的网络编程库(如Boost.Asio、Poco等)提供了异步IO的支持。这些库允许你注册一个回调函数,当数据准备好或者IO操作完成时,该回调函数会被调用。**此时操作系统已经将数据放入发起异步IO的程序所提供的地址之中。**这样,你就可以在等待数据到达的同时,执行其他任务。
一个典型的网络IO接口调用,分为两个阶段,分别是“数据就绪”和“数据读写”,数据就绪阶段分为阻塞和非阻塞,表现得结果就是,阻塞当前线程或是直接返回。
同步表示A向B请求调用一个网络IO接口时(或者调用某个业务逻辑API接口时),数据的读写都是由请求方A自己来完成的(不管是阻塞还是非阻塞);异步表示A向B请求调用一个网络IO接口时(或者调用某个业务逻辑API接口时),向B传入请求的事件以及事件发生时通知的方式,A就可以处理其它逻辑了,当B监听到事件处理完成后,会用事先约定好的通知方式,通知A处理结
果。
业务层面的同步和异步
业务层面的一个逻辑处理,是同步还是异步?
同步: A操作等待B操作做完事情,得到返回值,继续处理。
异步: A操作告诉B操作它感兴趣的事件以及通知方式,A操作继续执行自己的业务逻辑了;等B监听到相应事件发生后,B会通知A,A开始相应的数据处理逻辑。
总结
阻塞、非阻塞、同步、异步描述的都是IO的状态,一个典型的网络IO包含两个阶段:数据准备(数据就绪)和数据读写。
以recv
函数,传入sktfd,buf,bufsize
,来举例:
在数据准备阶段
当IO(即sktfd)工作在阻塞模式下
当调用recv函数时,如果数据没有准备好,那么recv将会阻塞当前线程。
当IO工作在非阻塞模式下
当调用recv
函数时,在非阻塞模式下,recv
函数会立即返回,而不会等待数据到达。这意味着,recv
的返回值和 errno
的状态可以用来判断数据的接收情况。以下是如何通过 recv
的返回值和 errno
来判断数据接收情况的方法:
- 返回值:
- 如果
recv
返回大于 0 的值,那么该值表示实际接收到的字节数。 - 如果
recv
返回 0,这通常表示对端已经关闭了连接(即 TCP 的正常关闭)。 - 如果
recv
返回 -1,则表示有错误发生或没有数据可读(在非阻塞模式下)。
- 如果
- errno:
- 当
recv
返回 -1 时,可以通过检查errno
的值来获取更详细的错误信息。 - 在非阻塞模式下,如果
recv
返回 -1 并且errno
设置为EWOULDBLOCK
或EAGAIN
,则表示没有数据可读,这不是一个错误,只是告诉你现在无法读取数据。你应该稍后再次尝试读取。
- 当
当数据准备好后:
如果是同步IO:应用程序会花费自己的运行时间来将数据搬到自己的缓冲区,然后才能继续执行逻辑。
如果是异步IO:操作系统会根据我调用时传入的buf地址,将数据搬到我想使用的地方,然后通过一定机制如回调函数、参数等方法,通知我来继续处理。即不需要应用程序花费自己的时间搬数据。