目录
阻塞非阻塞,同步异步
不管是磁盘IO还是网络IO都包含两个阶段,以网络I/O为例,分为数据准备和数据读写。其中数据准备相当于服务器监听客户端有没有数据过来?数据读写就是监听到有数据过来了该如何去读取数据?阻塞/非阻塞,同步/非同步分别就是这两种状态下的不同方式的体现。
举recv接口函数的例子说明:
#include <sys/types.h> #include <sys/socket.h> ssize_t recv(int sockfd, void *buf, size_t len, int flags);
参数说明:
sockfd
:表示要接收数据的套接字(socket)描述符。buf
:指向接收数据的缓冲区的指针。len
:表示接收缓冲区的大小,即能够接收的最大字节数。flags
:用于指定接收操作的可选标志,例如设置非阻塞接收或处理特殊情况。返回值说明:
- 如果成功接收数据,返回接收到的字节数。
- 如果连接关闭(对于TCP套接字),返回0。
- 如果发生错误,返回-1,并设置全局变量
errno
表示具体的错误类型。
阶段一:阻塞&非阻塞
该函数默认是阻塞的,当我调用它的时候如果我的sockfd上没有数据到来,没有数据可读,该函数就不会返回,当前线程就一直阻塞等待sockfd上有数据到来。
如果说在recv之前将我们的sockfd设置为nblock,非阻塞的,如果此时sockfd上没有数据到来,recv函数会直接返回,不会造成当前线程状态的转变,会通过返回值判断状态
阶段二:同步&异步
recv中的这个buf是我们自己创建的一块内存,作用就是从操作系统的TCP接收缓冲区将发过来的数据搬过来,recv没有把TCP接收缓冲区中内存搬完之前不会向下执行。
异步就不可以用recv介绍了,因为recv是一个同步IO接口。
对于异步IO接口,调用时会向操作系统通知三个东西,一个是我们感兴趣的sockfd,一个是buf,一个是sigio信号,通知操作系统我们对该sockfd感兴趣,判断一下里面是否有数据,有数据放到buf中,放好了再通过sigio信号通知过来。比如linux中的aio_read和aio_write
在处理IO的时候,阻塞跟非阻塞都是同步IO。只有使用了特殊的API才是异步IO。
业务层面上的一个逻辑处理是同步还是异步?
同步相当于就是A操作等待B操作做完事情,得到返回值,继续处理。数据读写都是由请求A自己完成的。
异步就是a操作告诉b操作它感兴趣的事件以及通知方式,然后呢a操作继续执行自己的业务逻辑。当b监听到相应事件发生后,b会通知a开始a开始相应逻辑处理。
总结:什么是阻塞非阻塞、同步异步?
阻塞非阻塞,同步异步啊,它描述的都是IO的一些状态。一个典型的IO分为两个阶段,分别是数据就绪和数据读写,数据就绪就是判断远端的数据有没有来,是否准备好了,数据读写是指当这个数据准备好了,对其进行读操作还是写操作。
数据就绪分为阻塞和非阻塞两种状态,阻塞的表现形式就是当前线程会阻塞直到数据准备好,非阻塞的表现形式就是会立刻返回。
举例说明接收数据的网络编程接口函数recv函数,它默认是一个阻塞同步接口,使用时我传入一个需要接收数据的sockfd、一个存放接收数据的buffer以及它的大小,当这个sockfd工作在默认阻塞状态下,在调用过程中会使当前线程阻塞,等待操作结束;
当这个sockfd工作在非阻塞状态下调用recv函数会立刻返回,不会更改当前线程状态,可通过返回值进一步判断(大于0表示接收到了一部分数据,可以根据返回数据长度进一步处理和解析、返回值为0对于TCP套接字表示连接关闭对于UDP表示没有数据可接收、返回值为-1表示接收数据出错如果进一步判断errno==EAGAN说明是一个合理的非阻塞模式,需要再次轮询或者等待数据就绪)。
数据准备好了开始数据读写,分为同步和异步状态,同步表示A向B请求调用IO接口时,数据的读写都是由请求方A自己来完成的(不管是阻塞还是非阻塞)还以receive例子来说。应用层调用receive这个接口,相当于应用层自己把这个数据从这个内核的TCP接收缓冲区拷贝到我给receive传的buffer中。应用程序从TCP缓冲区当中拷贝数据到我们buffer中。应用程序是一直等待这个数据拷贝完成以后receive才返回的,才能继续向下执行,就是说他是一个顺序执行的过程。
如果是一个异步的接口,A向B请求一个网络IO时,A向B传入请求的时间及事件发生时通知的方式,A事件就可以去处理其他逻辑了,当B监听到事件处理完成后,会用事先约定好的通知方式(信号、回调函数),通知A处理结果。比如说我们调用时需要传一个socket fd,这个是对应的一个TCP缓冲区,从远端接收数据的,还有一个buffer就是我。我要最终如果有数据的话呢,我要把这个内核缓冲区的数据搬到应用程序的每一块儿缓冲区当中,还有一个就是通知方式。操作系统负责帮我监听这个socket fd上是否有数据可读,有的话你帮我把数据从内核的TCP缓冲区搬到我们传入的buffer里边,最后再通过咱俩事先约定好的通知方式来通知应用程序,应用程序就可以处理你这件事情了。