网络IO接口调用分为两个阶段–数据准备和数据读写
1.数据准备 (操作系统方面)
ssize recv(int sockfd,void *buf,size_t len,int flags)\
//flags默认为0
当文件描述符为非阻塞时,recv的返回值 size:
-1&&errno == EAGAIN 如果只是-1,那么是系统发生错误,或者链接异常断开等。如果再继续errno==EAGAIN,则为文件描述符为非阻塞并且没有数据到来
0 网络对端关闭了链接
size>0 数据的字节
阻塞:调用IO方法的线程进入阻塞状态
非阻塞:不会改变线程状态,通过返回值判断
2.数据读写 (应用程序方面)
数据读写分同步和异步
PS:平时我们说的同步异步一般都是指网络IO方面的同步异步,和几个同事几件合作的同步异步是不一样的。
同步
自己定义一个buf,应用程序不断从操作系统的TCP接收缓冲区搬运数据,期间不可以进行其它操作(如果文件描述符为堵塞状态,则一直卡着)
异步
应用程序将sockfd,buf,以及通知方式(一般是回调函数或者信号sigio)告诉操作系统,让操作系统帮忙搬运,等操作系统完成后,操作系统再用应用程序提供的通知方式,来通知应用程序。在搬运期间,应用程序可以做自己的事情
linux提供的异步函数:
aio_read
aio_write
epoll_wait是同步方式
PS:陈硕大神的原话:在处理IO的时候,阻塞和非阻塞都是同步IO,只有使用了特殊的API才是异步IO。
作为面试者,如何阐述阻塞,非阻塞,同步,异步
PS:在将理论时,尽量也往实际上靠
一次网络IO接口调用包含两个阶段:数据准备和数据读写阶段。数据准备阶段,根据系统IO操作的就绪状态,即内核相应的sockfd对应的TCP接收缓冲区是否有数据可读,如果IO工作在阻塞模式下,那么调用例如recv函数时,recv会阻塞当前这个线程;如果IO工作在非阻塞模式下,调用recv会立刻返回,根据返回值来判断非阻塞的状态,如果单纯为-1则是链接异常中断等原因,如果同时errno等于ENGAIN,则表示当前IO工作于非阻塞模式,并且TCP接收缓冲区无数据。当数据就绪后,进入数据读写阶段。如果是同步,则应用程序调用函数去搬运操作系统的TCP接收缓冲区的数据,花费的是应用程序的时间。如果是异步,应用程序将sockfd,以及数据搬运的目的地buf,还有通知方式,一般是回调或者信号,告诉操作系统,让操作系统帮忙搬运,期间应用程序可以进行其它操作。等操作程序搬运完后,再通知应用程序。项目开发时,也会说到业务层面的并发的同步和异步。此时同步就比如说,A对B的业务感兴趣,A会等待B完成业务。异步则是,A告诉B我对你的业务感兴趣,并且把通知方式告诉你,等你完成业务后,再用我告诉你的通知方式来通知我。
五种IO模型
阻塞 blocking
非阻塞 non-blocking
IO复用 IO multiplexing
信号驱动 signal-driven
异步 asynchronous
说明:
-
IO复用,使得不用不断创建进程就可以处理多个连接,可以一个进程处理多个服务。
-
信号驱动,内核在第一个阶段是异步,在第二个阶段是同步;与非阻塞IO的区别在于它提供了消息通知机制,不需 要用户进程不断的轮询检查,减少了系统API的调用次数,提高了效率。
-
异步,第一和第二步(数据拷贝就是在第二步)都是异步,应用程序将任务交给操作系统去完成,完成后再通知应用程序。
典型的异步非阻塞状态,Node.js采用的网络IO模型。