精华内容
下载资源
问答
  • NIO,同步非阻塞IO,简单理解:一个请求一个线程 AIO,异步非阻塞IO,简单理解:一个有效请求一个线程 IO:阻塞IO BIO:同步阻塞IO。服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器需要启动一个...

     

    • BIO,同步阻塞式IO,简单理解:一个连接一个线程
    • NIO,同步非阻塞IO,简单理解:一个请求一个线程
    • AIO,异步非阻塞IO,简单理解:一个有效请求一个线程

    IO:阻塞IO

    BIO:同步阻塞IO。服务器实现模式为一个连接一个线程,即客户端有连接请求时服务器需要启动一个线程进行处理,如果这个链接不做任何事情会造成不必要的线程开销,当然可以通过线程池机制改善。

    NIO:同步非阻塞IO,服务器实现模式为一个请求一个线程,即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有IO请求时才启动一个线程进行处理。用户进程也需要时不时的询问IO操作是否就绪,这需要用户进行不停的去询问。NIO的包括三个核心概念:缓冲区(Buffer)、通道(Channel)、选择器(Selector)。

    AIO:Asynchronous IO,异步非阻塞AIO。最大的特性时具有异步能力,这种能力对socket与文件I/O都起作用。AIO其实是一种在读写操作结束之前允许进行其他操作的I/O处理。
     

    应用场景

    BIO:适用于连接数目比较小且固定的架构,这种方式对服务器资源要求比较高,并发局限于应用中,JDK1.4之前的唯一选择, 但是程序简单直观容易理解。

    NIO:适用于连接数目多且连接比较短(轻操作)的架构,比如聊推荐服务器,并发局限于应用中,编程比较复杂,JDK1.4开始支持。

    AIO:适用于连接数目多且连接比较长(重操作)的架构,比如相册服务器,充分调用OS参与并发操作,编程比较复杂,JDK1.7开始支持。

    IO NIO
    面向流 面向缓冲区
    阻塞

    非阻塞

    选择器

    BIO

    在JDK1.4之前,用Java编写网络请求,都是建立一个ServerSocket,然后,客户端建立Socket时就会询问是否有线程可以处理,如果没有,要么等待,要么被拒绝。即:一个连接,要求Server对应一个处理线程。

    NIO

    在Java里的由来,在JDK1.4及以后版本中提供了一套API来专门操作非阻塞I/O,我们可以在java.nio包及其子包中找到相关的类和接口。由于这套API是JDK新提供的I/O API,因此,也叫New I/O,这就是包名nio的由来。这套API由三个主要的部分组成:缓冲区(Buffers)、通道(Channels)和非阻塞I/O的核心类组成。在理解NIO的时候,需要区分,说的是New I/O还是非阻塞IO,New I/O是Java的包,NIO是非阻塞IO概念。这里讲的是后面一种。

    NIO本身是基于事件驱动思想来完成的,其主要想解决的是BIO的大并发问题: 在使用同步I/O的网络应用中,如果要同时处理多个客户端请求,或是在客户端要同时和多个服务器进行通讯,就必须使用多线程来处理。也就是说,将每一个客户端请求分配给一个线程来单独处理。这样做虽然可以达到我们的要求,但同时又会带来另外一个问题。由于每创建一个线程,就要为这个线程分配一定的内存空间(也叫工作存储器),而且操作系统本身也对线程的总数有一定的限制。如果客户端的请求过多,服务端程序可能会因为不堪重负而拒绝客户端的请求,甚至服务器可能会因此而瘫痪。

    NIO基于Reactor,当socket有流可读或可写入socket时,操作系统会相应的通知引用程序进行处理,应用再将流读取到缓冲区或写入操作系统。 
    也就是说,这个时候,已经不是一个连接就要对应一个处理线程了,而是有效的请求,对应一个线程,当连接没有数据时,是没有工作线程来处理的。

    AIO

    与NIO不同,当进行读写操作时,只须直接调用API的read或write方法即可。这两种方法均为异步的,对于读操作而言,当有流可读取时,操作系统会将可读的流传入read方法的缓冲区,并通知应用程序;对于写操作而言,当操作系统将write方法传递的流写入完毕时,操作系统主动通知应用程序。 
    即可以理解为,read/write方法都是异步的,完成后会主动调用回调函数。 
    在JDK1.7中,这部分内容被称作NIO.2,主要在java.nio.channels包下增加了下面四个异步通道:

    • AsynchronousSocketChannel
    • AsynchronousServerSocketChannel
    • AsynchronousFileChannel
    • AsynchronousDatagramChannel

    其中的read/write方法,会返回一个带回调函数的对象,当执行完读取/写入操作后,直接调用回调函数。

    按照《Unix网络编程》的划分,IO模型可以分为:阻塞IO、非阻塞IO、IO复用、信号驱动IO和异步IO,按照POSIX标准来划分只分为两类:同步IO和异步IO。如何区分呢?首先一个IO操作其实分成了两个步骤:发起IO请求和实际的IO操作,同步IO和异步IO的区别就在于第二个步骤是否阻塞,如果实际的IO读写阻塞请求进程,那么就是同步IO,因此阻塞IO、非阻塞IO、IO复用、信号驱动IO都是同步IO,如果不阻塞,而是操作系统帮你做完IO操作再将结果返回给你,那么就是异步IO。阻塞IO和非阻塞IO的区别在于第一步,发起IO请求是否会被阻塞,如果阻塞直到完成那么就是传统的阻塞IO,如果不阻塞,那么就是非阻塞IO。

     

    展开全文
  • (2)同步非阻塞IO(Non-blocking IO):默认创建的socket都是阻塞的,非阻塞IO要求socket被设置为NONBLOCK。 注意这里所说的NIO并非Java的NIO(New IO)库。 (3)IO多路复用(IO Multiplexing):即经典的...

    常用4IO模型

    (1)同步阻塞IO(Blocking IO):即传统的IO模型。

    (2)同步非阻塞IO(Non-blocking IO):默认创建的socket都是阻塞的,非阻塞IO要求socket被设置为NONBLOCK。

    注意这里所说的NIO并非Java的NIO(New IO)库。

    (3)IO多路复用(IO Multiplexing):即经典的Reactor设计模式,有时也称为异步阻塞IO,Java中的Selector和Linux中的epoll都是这种模型。

    (4)异步IO(Asynchronous IO):即经典的Proactor设计模式,也称为异步非阻塞IO。

    I/0 操作基本概念

    1数据准备,将数据加载到内核缓存(数据加载到操作系统)

    2将内核缓存中的数据加载到用户缓存(从操作系统复制到应用中)

    3同步和异步的概念描述的是用户线程与内核的交互方式

    4阻塞和非阻塞的概念描述的是用户线程调用内核IO操作的方式

    同步阻塞IO

    同步阻塞IO模型是最简单的IO模型,用户线程在内核进行IO操作时被阻塞。

     https://images2018.cnblogs.com/blog/874126/201808/874126-20180815215329688-621626362.png

    用户线程通过系统调用read发起IO读操作,由用户空间转到内核空间。内核在等待数据的时候将用户线程挂起造成阻塞,内核等到数据包到达后,然后将接收的数据拷贝到用户空间,完成read操作。

    同步非阻塞IO

    同步非阻塞IO是在同步阻塞IO的基础上,将socket设置为NIO(NONBLOCK)。

    https://images2018.cnblogs.com/blog/874126/201808/874126-20180815215551173-501412222.png

    于socket是非阻塞的方式,因此用户线程发起IO请求时立即返回。返回的是可能是一个code码代表数据正在处理中,此时线程一直循环轮询发送IO请求,直到数据在内核准备完毕才真正获取数据。此处非阻塞的意思是:线程轮询一直请求内核占用内核时间片而不被挂起的状态。效率上节省了取消用户线程挂起的资源消耗,但也伴随着大量CPU运算。对用户来讲可能比较快的接收到请求的数据。

    IO多路复用(异步阻塞)

    IO多路复用模型是建立在内核提供的多路分离函数select基础之上的,

    使用select函数可以避免同步非阻塞IO模型中轮询等待的问题。

    https://images2018.cnblogs.com/blog/874126/201808/874126-20180815215732938-781387747.png

    用户首先将需要进行IO操作的socket添加到select中,然后阻塞等待select系统调用返回。

    当数据到达时,socket被激活,select函数返回。用户线程正式发起read请求,读取数据并继续执行。

    从流程上来看,使用select函数进行IO请求和同步阻塞模型没有太大的区别,

    甚至还多了添加监视socket,以及调用select函数的额外操作,效率更差。

    但是,使用select以后最大的优势是用户可以在一个线程内同时处理多个socket的IO请求。

    用户可以注册多个socket,然后不断地调用select读取被激活的socket,即可达到在同一个线程内同时处理多个IO请求的目的。

    而在同步阻塞模型中,必须通过多线程的方式才能达到这个目的。

    然而,使用select函数的优点并不仅限于此。

    虽然上述方式允许单线程内处理多个IO请求,但是每个IO请求的过程还是阻塞的(在select函数上阻塞),平均时间甚至比同步阻塞IO模型还要长。

    如果用户线程只注册自己感兴趣的socket或者IO请求,然后去做自己的事情,等到数据到来时再进行处理,则可以提高CPU的利用率。

    异步IO(非阻塞)

    “真正”的异步IO需要操作系统更强的支持。

    在IO多路复用模型中,事件循环将文件句柄的状态事件通知给用户线程,由用户线程自行读取数据、处理数据。

    而在异步IO模型中,当用户线程收到通知时,数据已经被内核读取完毕,并放在了用户线程指定的缓冲区内,内核在IO完成后通知用户线程直接使用即可。

    https://images2018.cnblogs.com/blog/874126/201808/874126-20180815220557814-526945852.png

    相比于IO多路复用模型,异步IO并不十分常用,不少高性能并发服务程序使用IO多路复用模型+多线程任务处理的架构基本可以满足需求。

    况且目前操作系统对异步IO的支持并非特别完善,更多的是采用IO多路复用模型模拟异步IO的方式。

     

    展开全文
  • (2)同步非阻塞IO(Non-blocking IO):默认创建的socket都是阻塞的,非阻塞IO要求socket被设置为NONBLOCK。注意这里所说的NIO并非Java的NIO(New IO)库。(3)IO多路复用(IO ...

    转载自原文链接

    服务器端编程经常需要构造高性能的IO模型,常见的IO模型有四种:

    (1)同步阻塞IO(Blocking IO):即传统的IO模型。

    (2)同步非阻塞IO(Non-blocking IO):默认创建的socket都是阻塞的,非阻塞IO要求socket被设置为NONBLOCK。注意这里所说的NIO并非Java的NIO(New IO)库。

    (3)IO多路复用(IO Multiplexing):即经典的Reactor设计模式,有时也称为异步阻塞IO,Java中的Selector和Linux中的epoll都是这种模型。

    (4)异步IO(Asynchronous IO):即经典的Proactor设计模式,也称为异步非阻塞IO。

    同步和异步的概念描述的是用户线程与内核的交互方式:同步是指用户线程发起IO请求后需要等待或者轮询内核IO操作完成后才能继续执行;而异步是指用户线程发起IO请求后仍继续执行,当内核IO操作完成后会通知用户线程,或者调用用户线程注册的回调函数。

    阻塞和非阻塞的概念描述的是用户线程调用内核IO操作的方式:阻塞是指IO操作需要彻底完成后才返回到用户空间;而非阻塞是指IO操作被调用后立即返回给用户一个状态值,无需等到IO操作彻底完成。

    另外,Richard Stevens 在《Unix 网络编程》卷1中提到的基于信号驱动的IO(Signal Driven IO)模型,由于该模型并不常用,本文不作涉及。接下来,我们详细分析四种常见的IO模型的实现原理。为了方便描述,我们统一使用IO的读操作作为示例。

    一、同步阻塞IO

    同步阻塞IO模型是最简单的IO模型,用户线程在内核进行IO操作时被阻塞。

    图1 同步阻塞IO

    如图1所示,用户线程通过系统调用read发起IO读操作,由用户空间转到内核空间。内核等到数据包到达后,然后将接收的数据拷贝到用户空间,完成read操作。

    用户线程使用同步阻塞IO模型的伪代码描述为:

    {
    
    	read(socket, buffer);
    	
    	process(buffer);
    
    }
    
    

    即用户需要等待read将socket中的数据读取到buffer后,才继续处理接收的数据。整个IO请求的过程中,用户线程是被阻塞的,这导致用户在发起IO请求时,不能做任何事情,对CPU的资源利用率不够。

    二、同步非阻塞IO

    同步非阻塞IO是在同步阻塞IO的基础上,将socket设置为NONBLOCK。这样做用户线程可以在发起IO请求后可以立即返回。

    图2 同步非阻塞IO

    如图2所示,由于socket是非阻塞的方式,因此用户线程发起IO请求时立即返回。但并未读取到任何数据,用户线程需要不断地发起IO请求,直到数据到达后,才真正读取到数据,继续执行。

    用户线程使用同步非阻塞IO模型的伪代码描述为:

    {
    
    	while(read(socket, buffer) != SUCCESS);
    	
    	process(buffer);
    
    }
    

    即用户需要不断地调用read,尝试读取socket中的数据,直到读取成功后,才继续处理接收的数据。整个IO请求的过程中,虽然用户线程每次发起IO请求后可以立即返回,但是为了等到数据,仍需要不断地轮询、重复请求,消耗了大量的CPU的资源。一般很少直接使用这种模型,而是在其他IO模型中使用非阻塞IO这一特性。

    三、IO多路复用

    IO多路复用模型是建立在内核提供的多路分离函数select基础之上的,使用select函数可以避免同步非阻塞IO模型中轮询等待的问题。

    图3 多路分离函数select

    如图3所示,用户首先将需要进行IO操作的socket添加到select中,然后阻塞等待select系统调用返回。当数据到达时,socket被激活,select函数返回。用户线程正式发起read请求,读取数据并继续执行。

    从流程上来看,使用select函数进行IO请求和同步阻塞模型没有太大的区别,甚至还多了添加监视socket,以及调用select函数的额外操作,效率更差。但是,使用select以后最大的优势是用户可以在一个线程内同时处理多个socket的IO请求。用户可以注册多个socket,然后不断地调用select读取被激活的socket,即可达到在同一个线程内同时处理多个IO请求的目的。而在同步阻塞模型中,必须通过多线程的方式才能达到这个目的。

    用户线程使用select函数的伪代码描述为:

    {
    
    	select(socket);
    	
    	while(1) {
    	
    		sockets = select();
    		
    		for(socket in sockets) {
    		
    			if(can_read(socket)) {
    			
    				read(socket, buffer);
    				
    				process(buffer);
    			
    			}
    		
    		}
    	
    	}
    
    }
    

    其中while循环前将socket添加到select监视中,然后在while内一直调用select获取被激活的socket,一旦socket可读,便调用read函数将socket中的数据读取出来。

    然而,使用select函数的优点并不仅限于此。虽然上述方式允许单线程内处理多个IO请求,但是每个IO请求的过程还是阻塞的(在select函数上阻塞),平均时间甚至比同步阻塞IO模型还要长。如果用户线程只注册自己感兴趣的socket或者IO请求,然后去做自己的事情,等到数据到来时再进行处理,则可以提高CPU的利用率。

    IO多路复用模型使用了Reactor设计模式实现了这一机制。

    图4 Reactor设计模式

    如图4所示,EventHandler抽象类表示IO事件处理器,它拥有IO文件句柄Handle(通过get_handle获取),以及对Handle的操作handle_event(读/写等)。继承于EventHandler的子类可以对事件处理器的行为进行定制。Reactor类用于管理EventHandler(注册、删除等),并使用handle_events实现事件循环,不断调用同步事件多路分离器(一般是内核)的多路分离函数select,只要某个文件句柄被激活(可读/写等),select就返回(阻塞),handle_events就会调用与文件句柄关联的事件处理器的handle_event进行相关操作。

    图5 IO多路复用

    如图5所示,通过Reactor的方式,可以将用户线程轮询IO操作状态的工作统一交给handle_events事件循环进行处理。用户线程注册事件处理器之后可以继续执行做其他的工作(异步),而Reactor线程负责调用内核的select函数检查socket状态。当有socket被激活时,则通知相应的用户线程(或执行用户线程的回调函数),执行handle_event进行数据读取、处理的工作。由于select函数是阻塞的,因此多路IO复用模型也被称为异步阻塞IO模型。注意,这里的所说的阻塞是指select函数执行时线程被阻塞,而不是指socket。一般在使用IO多路复用模型时,socket都是设置为NONBLOCK的,不过这并不会产生影响,因为用户发起IO请求时,数据已经到达了,用户线程一定不会被阻塞。

    用户线程使用IO多路复用模型的伪代码描述为:

    void UserEventHandler::handle_event() {
    
    	if(can_read(socket)) {
    	
    		read(socket, buffer);
    		
    		process(buffer);
    	
    	}
    
    }
    
    {
    
    	Reactor.register(new UserEventHandler(socket));
    
    }
    

    用户需要重写EventHandler的handle_event函数进行读取数据、处理数据的工作,用户线程只需要将自己的EventHandler注册到Reactor即可。Reactor中handle_events事件循环的伪代码大致如下。

    Reactor::handle_events() {
    
    	while(1) {
    	
    		sockets = select();
    		
    		for(socket in sockets) {
    		
    			get_event_handler(socket).handle_event();
    		
    		}
    	
    	}
    
    }
    

    事件循环不断地调用select获取被激活的socket,然后根据获取socket对应的EventHandler,执行器handle_event函数即可。

    IO多路复用是最常使用的IO模型,但是其异步程度还不够“彻底”,因为它使用了会阻塞线程的select系统调用。因此IO多路复用只能称为异步阻塞IO,而非真正的异步IO。

    四、异步IO

    “真正”的异步IO需要操作系统更强的支持。在IO多路复用模型中,事件循环将文件句柄的状态事件通知给用户线程,由用户线程自行读取数据、处理数据。而在异步IO模型中,当用户线程收到通知时,数据已经被内核读取完毕,并放在了用户线程指定的缓冲区内,内核在IO完成后通知用户线程直接使用即可。

    异步IO模型使用了Proactor设计模式实现了这一机制。

    图6 Proactor设计模式

    如图6,Proactor模式和Reactor模式在结构上比较相似,不过在用户(Client)使用方式上差别较大。Reactor模式中,用户线程通过向Reactor对象注册感兴趣的事件监听,然后事件触发时调用事件处理函数。而Proactor模式中,用户线程将AsynchronousOperation(读/写等)、Proactor以及操作完成时的CompletionHandler注册到AsynchronousOperationProcessor。AsynchronousOperationProcessor使用Facade模式提供了一组异步操作API(读/写等)供用户使用,当用户线程调用异步API后,便继续执行自己的任务。AsynchronousOperationProcessor 会开启独立的内核线程执行异步操作,实现真正的异步。当异步IO操作完成时,AsynchronousOperationProcessor将用户线程与AsynchronousOperation一起注册的Proactor和CompletionHandler取出,然后将CompletionHandler与IO操作的结果数据一起转发给Proactor,Proactor负责回调每一个异步操作的事件完成处理函数handle_event。虽然Proactor模式中每个异步操作都可以绑定一个Proactor对象,但是一般在操作系统中,Proactor被实现为Singleton模式,以便于集中化分发操作完成事件。

    图7 异步IO

    如图7所示,异步IO模型中,用户线程直接使用内核提供的异步IO API发起read请求,且发起后立即返回,继续执行用户线程代码。不过此时用户线程已经将调用的AsynchronousOperation和CompletionHandler注册到内核,然后操作系统开启独立的内核线程去处理IO操作。当read请求的数据到达时,由内核负责读取socket中的数据,并写入用户指定的缓冲区中。最后内核将read的数据和用户线程注册的CompletionHandler分发给内部Proactor,Proactor将IO完成的信息通知给用户线程(一般通过调用用户线程注册的完成事件处理函数),完成异步IO。

    用户线程使用异步IO模型的伪代码描述为:

    void UserCompletionHandler::handle_event(buffer) {
    
    	process(buffer);
    
    }
    
    {
    
    	aio_read(socket, new UserCompletionHandler);
    
    }
    

    用户需要重写CompletionHandler的handle_event函数进行处理数据的工作,参数buffer表示Proactor已经准备好的数据,用户线程直接调用内核提供的异步IO API,并将重写的CompletionHandler注册即可。

    相比于IO多路复用模型,异步IO并不十分常用,不少高性能并发服务程序使用IO多路复用模型+多线程任务处理的架构基本可以满足需求。况且目前操作系统对异步IO的支持并非特别完善,更多的是采用IO多路复用模型模拟异步IO的方式(IO事件触发时不直接通知用户线程,而是将数据读写完毕后放到用户指定的缓冲区中)。Java7之后已经支持了异步IO,感兴趣的读者可以尝试使用。

    本文从基本概念、工作流程和代码示例三个层次简要描述了常见的四种高性能IO模型的结构和原理,理清了同步、异步、阻塞、非阻塞这些容易混淆的概念。通过对高性能IO模型的理解,可以在服务端程序的开发中选择更符合实际业务特点的IO模型,提高服务质量。希望本文对你有所帮助。

    展开全文
  • 服务器端编程经常需要构造高性能的 IO...(2) 同步非阻塞 IO(Non-blocking IO):默认创建的 socket 都是阻塞的,非阻塞 IO 要求 socket 被设置为 NONBLOCK。注意这里所说的 NIO 并非 Java 的 NIO(New IO)库。 (3)

    服务器端编程经常需要构造高性能的 IO 模型,常见的 IO 模型有四种:

    (1) 同步阻塞 IO(Blocking IO):即传统的 IO 模型。
    (2) 同步非阻塞 IO(Non-blocking IO):默认创建的 socket 都是阻塞的,非阻塞 IO 要求 socket 被设置为 NONBLOCK。注意这里所说的 NIO 并非 Java 的 NIO(New IO)库。
    (3) IO 多路复用(IO Multiplexing):即经典的 Reactor 设计模式,有时也称为异步阻塞 IO,Java 中的 Selector 和 Linux 中的 epoll 都是这种模型。
    (4) 异步 IO(Asynchronous IO):即经典的 Proactor 设计模式,也称为异步非阻塞 IO。

    同步(synchronous)和异步(asynchronous)的概念描述的是用户线程与内核的交互方式:同步是指用户线程发起 IO 请求后需要等待或者轮询内核 IO 操作完成后才能继续执行;而异步是指用户线程发起 IO 请求后仍继续执行,当内核 IO 操作完成后会通知用户线程,或者调用用户线程注册的回调函数。

    阻塞(blocking)和非阻塞(non-blocking)的概念描述的是用户线程调用内核 IO 的操作方式:阻塞是指 IO 操作需要彻底完成后才返回到用户空间;而非阻塞是指 IO 操作被调用后立即返回给用户一个状态值,无需等到 IO 操作彻底完成。

    再说一下 IO 发生时涉及的对象和步骤。对于一个 network IO(这里我们以 read 举例),它会涉及到两个系统对象,一个是调用这个 IO 的 process(or thread),另一个就是系统内核(kernel)。当一个 read 操作发生时,它会经历两个阶段:

    (1) 等待数据准备(Waiting for the data to be ready)
    (2) 将数据从内核拷贝到进程中(Copying the data from the kernel to the process)

    记住这两点很重要,因为这些 IO 模型的区别就是在两个阶段上各有不同的情况。

    1. 同步阻塞 IO

    在 linux 中,默认情况下所有的 socket 都是 blocking,一个典型的读操作流程大概是这样:


    当用户进程调用了 recvfrom 这个系统调用,kernel 就开始了 IO 的第一个阶段:准备数据。对于 network io 来说,很多时候数据在一开始还没有到达(比如,还没有收到一个完整的 UDP 包),这个时候 kernel 就要等待足够的数据到来。而在用户进程这边,整个进程会被阻塞。当 kernel 一直等到数据准备好了,它就会将数据从 kernel 中拷贝到用户内存,然后 kernel 返回结果,用户进程才解除 block 的状态,重新运行起来。

    所以,blocking IO 的特点就是在 IO 执行的两个阶段(等待数据和拷贝数据两个阶段)都被 block 了。

    几乎所有的程序员第一次接触到的网络编程都是从 listen()、send()、recv() 等接口开始的,这些接口都是阻塞型的。使用这些接口可以很方便的构建服务器 / 客户机的模型。下面是一个简单地"一问一答"的服务器。


    我们注意到,大部分的 socket 接口都是阻塞型的。所谓阻塞型接口是指系统调用(一般是 IO 接口)不返回调用结果并让当前线程一直阻塞,只有当该系统调用获得结果或者超时出错时才返回。

    实际上,除非特别指定,几乎所有的 IO 接口 ( 包括 socket 接口 ) 都是阻塞型的。这给网络编程带来了一个很大的问题,如在调用 send() 的同时,线程将被阻塞,在此期间,线程将无法执行任何运算或响应任何的网络请求。

    一个简单的改进方案是在服务器端使用多线程(或多进程)。多线程(或多进程)的目的是让每个连接都拥有独立的线程(或进程),这样任何一个连接的阻塞都不会影响其他的连接。具体使用多进程还是多线程,并没有一个特定的模式。传统意义上,进程的开销要远远大于线程,所以如果需要同时为较多的客户机提供服务,则不推荐使用多进程;如果单个服务执行体需要消耗较多的CPU资源,譬如需要进行大规模或长时间的数据运算或文件访问,则进程较为安全。通常,使用 pthread_create() 创建新线程,fork() 创建新进程。

    我们假设对上述的服务器 / 客户机模型,提出更高的要求,即让服务器同时为多个客户机提供一问一答的服务。于是有了如下的模型。


    在上述的线程 / 时间图例中,主线程持续等待客户端的连接请求,如果有连接,则创建新线程,并在新线程中提供为前例同样的问答服务。

    很多初学者可能不明白为何一个 socket 可以 accept 多次。实际上 socket 的设计者可能特意为多客户机的情况留下了伏笔,让 accept() 能够返回一个新的 socket。下面是 accept 接口的原型:

    1
    intaccept(ints, struct sockaddr *addr, socklen_t *addrlen);

    输入参数 s 是从 socket(),bind() 和 listen() 中沿用下来的 socket 句柄值。执行完 bind() 和 listen() 后,操作系统已经开始在指定的端口处监听所有的连接请求,如果有请求,则将该连接请求加入请求队列。调用 accept() 接口正是从 socket s 的请求队列抽取第一个连接信息,创建一个与 s 同类的新的 socket 返回句柄。新的 socket 句柄即是后续 read() 和 recv() 的输入参数。如果请求队列当前没有请求,则 accept() 将进入阻塞状态直到有请求进入队列。

    上述多线程的服务器模型似乎完美的解决了为多个客户机提供问答服务的要求,但其实并不尽然。如果要同时响应成百上千路的连接请求,则无论多线程还是多进程都会严重占据系统资源,降低系统对外界响应效率,而线程与进程本身也更容易进入假死状态。

    很多程序员可能会考虑使用"线程池"或"连接池"。"线程池"旨在减少创建和销毁线程的频率,其维持一定合理数量的线程,并让空闲的线程重新承担新的执行任务。"连接池"维持连接的缓存池,尽量重用已有的连接、减少创建和关闭连接的频率。这两种技术都可以很好的降低系统开销,都被广泛应用很多大型系统,如 websphere、tomcat 和各种数据库等。但是,"线程池"和"连接池"技术也只是在一定程度上缓解了频繁调用 IO 接口带来的资源占用。而且,所谓"池"始终有其上限,当请求大大超过上限时,"池"构成的系统对外界的响应并不比没有池的时候效果好多少。所以使用"池"必须考虑其面临的响应规模,并根据响应规模调整"池"的大小。

    对应上例中的所面临的可能同时出现的上千甚至上万次的客户端请求,"线程池"或"连接池"或许可以缓解部分压力,但是不能解决所有问题。总之,多线程模型可以方便高效的解决小规模的服务请求,但面对大规模的服务请求,多线程模型也会遇到瓶颈,可以用非阻塞接口来尝试解决这个问题。

    2. 同步非阻塞 IO

    Linux 下,可以通过设置 socket 使其变为 non-blocking。当对一个 non-blocking socket 执行读操作时,流程是这个样子:


    从图中可以看出,当用户进程发出 read 操作时,如果 kernel 中的数据还没有准备好,那么它并不会 block 用户进程,而是立刻返回一个 error。从用户进程角度讲 ,它发起一个 read 操作后,并不需要等待,而是马上就得到了一个结果。用户进程判断结果是一个 error 时,它就知道数据还没有准备好,于是它可以再次发送 read 操作。一旦 kernel 中的数据准备好了,并且又再次收到了用户进程的 system call,那么它马上就将数据拷贝到了用户内存,然后返回。

    所以,在非阻塞式 IO 中,用户进程其实是需要不断的主动询问 kernel 数据准备好了没有。

    非阻塞的接口相比于阻塞型接口的显著差异在于,在被调用之后立即返回。使用如下的函数可以将某句柄 fd 设为非阻塞状态。

    1
    fcntl( fd, F_SETFL, O_NONBLOCK );

    下面将给出只用一个线程,但能够同时从多个连接中检测数据是否送达,并且接受数据的模型。


    在非阻塞状态下,recv() 接口在被调用后立即返回,返回值代表了不同的含义。如在本例中,

    * recv() 返回值大于 0,表示接受数据完毕,返回值即是接受到的字节数;
    * recv() 返回 0,表示连接已经正常断开;
    * recv() 返回 -1,且 errno 等于 EAGAIN,表示 recv 操作还没执行完成;
    * recv() 返回 -1,且 errno 不等于 EAGAIN,表示 recv 操作遇到系统错误 errno。

    可以看到服务器线程可以通过循环调用 recv() 接口,可以在单个线程内实现对所有连接的数据接收工作。但是上述模型绝不被推荐。因为,循环调用 recv() 将大幅度推高 CPU 占用率;此外,在这个方案中 recv() 更多的是起到检测"操作是否完成"的作用,实际操作系统提供了更为高效的检测"操作是否完成"作用的接口,例如 select() 多路复用模式,可以一次检测多个连接是否活跃。

    3. IO 多路复用

    同步阻塞IO在等待数据就绪上花去太多时间,而传统的同步非阻塞IO虽然不会阻塞进程,但是结合轮询来判断数据是否就绪仍然会耗费大量的CPU时间。

    多路IO复用提供了对大量文件描述符进行就绪检查的高性能方案。

    select

    select诞生于4.2BSD,在几乎所有平台上都支持,其良好的跨平台支持是它的主要的也是为数不多的优点之一。

    select的缺点(1)单个进程能够监视的文件描述符的数量存在最大限制(2)select需要复制大量的句柄数据结构,产生巨大的开销 (3)select返回的是含有整个句柄的列表,应用程序需要遍历整个列表才能发现哪些句柄发生了事件(4)select的触发方式是水平触发,应用程序如果没有完成对一个已经就绪的文件描述符进行IO操作,那么之后每次select调用还是会将这些文件描述符通知进程。相对应方式的是边缘触发。

    poll

    poll 诞生于UNIX System V Release 3,那时AT&T已经停止了UNIX的源代码授权,所以显然也不会直接使用BSD的select,所以AT&T自己实现了一个和select没有多大差别的poll。

    poll和select是名字不同的孪生兄弟,除了没有监视文件数量的限制,select后面3条缺点同样适用于poll。

    面对select和poll的缺陷,不同的OS做出了不同的解决方案,可谓百花齐放。不过他们至少完成了下面两点超越,一是内核长期维护一个事件关注列表,我们只需要修改这个列表,而不需要将句柄数据结构复制到内核中;二是直接返回事件列表,而不是所有句柄列表。

    /dev/poll

    Sun在Solaris中提出了新的实现方案,它使用了虚拟的/dev/poll设备,开发者可以将要监视的文件描述符加入这个设备,然后通过ioctl()来等待事件通知。

    /dev/epoll

    名为/dev/epoll的设备以补丁的方式出现在Linux2.4中,它提供了类似/dev/poll的功能,并且在一定程度上使用mmap提高了性能。

    kqueue

    FreeBSD实现了kqueue,可以支持水平触发和边缘触发,性能和下面要提到的epoll非常接近。

    epoll

    epoll诞生于Linux 2.6内核,被公认为是Linux2.6下性能最好的多路IO复用方法。

    ?


    IO multiplexing 这个词可能有点陌生,但是如果我说 select / epoll,大概就都能明白了。有些地方也称这种 IO 方式为事件驱动 IO(event driven IO)。我们都知道,select / epoll 的好处就在于单个 process 就可以同时处理多个网络连接的 IO。它的基本原理就是 select / epoll 这个 function 会不断的轮询所负责的所有 socket,当某个 socket 有数据到达了,就通知用户进程。它的流程如图:


    当用户进程调用了 select,那么整个进程会被 block,而同时,kernel 会"监视"所有 select 负责的 socket,当任何一个 socket 中的数据准备好了,select 就会返回。这个时候用户进程再调用 read 操作,将数据从 kernel 拷贝到用户进程。

    这个图和 blocking IO 的图其实并没有太大的不同,事实上还更差一些。因为这里需要使用两个系统调用(select 和 recvfrom),而 blocking IO 只调用了一个系统调用(recvfrom)。但是,用 select 的优势在于它可以同时处理多个 connection。(多说一句:所以,如果处理的连接数不是很高的话,使用 select / epoll 的 web server 不一定比使用 multi-threading + blocking IO 的 web server 性能更好,可能延迟还更大。select / epoll 的优势并不是对于单个连接能处理得更快,而是在于能处理更多的连接。)

    在多路复用模型中,对于每一个 socket,一般都设置成为 non-blocking,但是,如上图所示,整个用户的 process 其实是一直被 block 的。只不过 process 是被 select 这个函数 block,而不是被 socket IO 给 block。因此 select() 与非阻塞 IO 类似。

    用户线程使用 select 函数的伪代码描述为:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    {
        select(socket);
        while(1) {
            sockets = select();
            for(socket in sockets) {
                if(can_read(socket)) {
                    read(socket, buffer);
                    process(buffer);
                }
            }
        }
    }

    其中 while 循环前将 socket 添加到 select 监视中,然后在 while 内一直调用 select 获取被激活的 socket,一旦 socket 可读,便调用 read 函数将 socket 中的数据读取出来。

    IO 多路复用模型使用了 Reactor 设计模式实现了这一机制。


    如图所示,EventHandler 抽象类表示 IO 事件处理器,它拥有 IO 文件句柄 Handle(通过 get_handle 获取),以及对 Handle 的操作 handle_event(读/写等)。继承于 EventHandler 的子类可以对事件处理器的行为进行定制。Reactor 类用于管理 EventHandler(注册、删除等),并使用 handle_events 实现事件循环,不断调用同步事件多路分离器(一般是内核)的多路分离函数 select,只要某个文件句柄被激活(可读/写等),select 就返回(阻塞),handle_events 就会调用与文件句柄关联的事件处理器的 handle_event 进行相关操作。


    如图所示,通过 Reactor的方式,可以将用户线程轮询 IO 操作状态的工作统一交给 handle_events 事件循环进行处理。用户线程注册事件处理器之后可以继续执行做其他的工作(异步),而 Reactor 线程负责调用内核的 select 函数检查 socket 状态。当有 socket 被激活时,则通知相应的用户线程(或执行用户线程的回调函数),执行 handle_event 进行数据读取、处理的工作。由于 select 函数是阻塞的,因此多路 IO 复用模型也被称为异步阻塞 IO 模型。注意,这里的所说的阻塞是指 select 函数执行时线程被阻塞,而不是指 socket。一般在使用 IO 多路复用模型时,socket 都是设置为 NONBLOCK 的,不过这并不会产生影响,因为用户发起 IO 请求时,数据已经到达了,用户线程一定不会被阻塞。

    用户线程使用 IO 多路复用模型的伪代码描述为:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    voidUserEventHandler::handle_event() {
        if(can_read(socket)) {
            read(socket, buffer);
            process(buffer);
        }
    }
    {
        Reactor.register(newUserEventHandler(socket));
    }

    用户需要重写 EventHandler 的 handle_event 函数进行读取数据、处理数据的工作,用户线程只需要将自己的 EventHandler 注册到 Reactor 即可。Reactor 中 handle_events 事件循环的伪代码大致如下。

    1
    2
    3
    4
    5
    6
    7
    8
    Reactor::handle_events() {
        while(1) {
            sockets = select();
            for(socket in sockets) {
                get_event_handler(socket).handle_event();
            }
        }
    }

    事件循环不断地调用 select 获取被激活的 socket,然后根据获取 socket 对应的 EventHandler,执行器 handle_event 函数即可。

    大部分 Unix/Linux 都支持 select 函数,该函数用于探测多个文件句柄的状态变化。下面给出 select 接口的原型:

    1
    2
    3
    4
    5
    6
    7
    8
    /**
     * nfds:select()函数监视的描述符数的最大值,一般取监视的描述符数的最大值+1,其上限设置在sys/types.h中有定义
     * readfds:select()函数监视的可读描述符集合
     * wtitefds:select()函数监视的可写描述符集合
     * errnofds:select()函数监视的异常描述符集合
     * timeout:select()函数监视超时结束时间,取NULL表示永久等待
     */
    intselect(intnfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)

    这里,fd_set 类型可以简单的理解为按 bit 位标记句柄的队列,例如要在某 fd_set 中标记一个值为16的句柄,则该 fd_set 的第16个 bit 位被标记为1。具体的置位、验证可使用 FD_SET、FD_ISSET 等宏实现。在 select() 函数中,readfds、writefds 和 exceptfds 同时作为输入参数和输出参数。如果输入的 readfds 标记了16号句柄,则 select() 将检测16号句柄是否可读。在 select() 返回后,可以通过检查 readfds 有否标记16号句柄,来判断该"可读"事件是否发生。另外,用户可以设置 timeout 时间。

    返回值:返回总的位数这些位对应已准备好的描述符,否则返回-1。相关宏操作:

    1
    2
    3
    4
    FD_ZERO(intfd, fd_set* fds) // 清空 fdset 与所有描述符的关系
    FD_SET(intfd, fd_set* fds) // 建立描述符 fd 与 fdset 的关系
    FD_ISSET(intfd, fd_set* fds) // 撤销描述符 fd 与 fdset 的关系
    FD_CLR(intfd, fd_set* fds) // 检查与 fdset 联系的描述符 fd 是否可以读写,返回非零表示可以读写

    下面将重新模拟上例中从多个客户端接收数据的模型。


    上述模型只是描述了使用 select() 接口同时从多个客户端接收数据的过程;由于 select() 接口可以同时对多个句柄进行读状态、写状态和错误状态的探测,所以可以很容易构建为多个客户端提供独立问答服务的服务器系统。如下图。


    这里需要指出的是,客户端的一个 connect() 操作,将在服务器端激发一个"可读事件",所以 select() 也能探测来自客户端的 connect() 行为。

    上述模型中,最关键的地方是如何动态维护 select() 的三个参数 readfds、writefds 和 exceptfds。作为输入参数,readfds 应该标记所有的需要探测的"可读事件"的句柄,其中永远包括那个探测 connect() 的那个"母"句柄;同时,writefds 和 exceptfds 应该标记所有需要探测的"可写事件"和"错误事件"的句柄 ( 使用 FD_SET() 标记 )。

    作为输出参数,readfds、writefds 和 exceptfds 中的保存了 select() 捕捉到的所有事件的句柄值。程序员需要检查的所有的标记位 ( 使用 FD_ISSET() 检查 ),以确定到底哪些句柄发生了事件。

    上述模型主要模拟的是"一问一答"的服务流程,所以如果 select() 发现某句柄捕捉到了"可读事件",服务器程序应及时做 recv() 操作,并根据接收到的数据准备好待发送数据,并将对应的句柄值加入 writefds,准备下一次的"可写事件"的 select() 探测。同样,如果 select() 发现某句柄捕捉到"可写事件",则程序应及时做 send() 操作,并准备好下一次的"可读事件"探测准备。下图描述的是上述模型中的一个执行周期。


    这种模型的特征在于每一个执行周期都会探测一次或一组事件,一个特定的事件会触发某个特定的响应。我们可以将这种模型归类为"事件驱动模型"。

    相比其他模型,使用 select() 的事件驱动模型只用单线程(进程)执行,占用资源少,不消耗太多 CPU,同时能够为多客户端提供服务。如果试图建立一个简单的事件驱动的服务器程序,这个模型有一定的参考价值。

    但这个模型依旧有着很多问题。首先 select() 接口并不是实现"事件驱动"的最好选择。因为当需要探测的句柄值较大时,select() 接口本身需要消耗大量时间去轮询各个句柄。很多操作系统提供了更为高效的接口,如 linux 提供了 epoll,BSD 提供了 kqueue,Solaris 提供了 /dev/poll … 如果需要实现更高效的服务器程序,类似 epoll 这样的接口更被推荐。遗憾的是不同的操作系统特供的 epoll 接口有很大差异,所以使用类似于 epoll 的接口实现具有较好跨平台能力的服务器会比较困难。

    其次,该模型将事件探测和事件响应夹杂在一起,一旦事件响应的执行体庞大,则对整个模型是灾难性的。如下例,庞大的执行体1的将直接导致响应事件2的执行体迟迟得不到执行,并在很大程度上降低了事件探测的及时性。


    幸运的是,有很多高效的事件驱动库可以屏蔽上述的困难,常见的事件驱动库有 libevent 库,还有作为 libevent 替代者的 libev 库。这些库会根据操作系统的特点选择最合适的事件探测接口,并且加入了信号(signal)等技术以支持异步响应,这使得这些库成为构建事件驱动模型的不二选择。

    4. 异步IO

    Linux 下的 asynchronous IO 其实用得不多,从内核2.6版本才开始引入。先看一下它的流程:


    用户进程发起 read 操作之后,立刻就可以开始去做其它的事。而另一方面,从 kernel 的角度,当它受到一个 asynchronous read 之后,首先它会立刻返回,所以不会对用户进程产生任何 block。然后,kernel 会等待数据准备完成,然后将数据拷贝到用户内存,当这一切都完成之后,kernel 会给用户进程发送一个 signal,告诉它 read 操作完成了。

    "真正"的异步 IO 需要操作系统更强的支持。在 IO 多路复用模型中,事件循环将文件句柄的状态事件通知给用户线程,由用户线程自行读取数据、处理数据。而在异步 IO 模型中,当用户线程收到通知时,数据已经被内核读取完毕,并放在了用户线程指定的缓冲区内,内核在 IO 完成后通知用户线程直接使用即可。

    异步 IO 模型使用了 Proactor 设计模式实现了这一机制。


    如图,Proactor 模式和 Reactor 模式在结构上比较相似,不过在用户(Client)使用方式上差别较大。Reactor 模式中,用户线程通过向 Reactor 对象注册感兴趣的事件监听,然后事件触发时调用事件处理函数。而 Proactor 模式中,用户线程将 AsynchronousOperation(读/写等)、Proactor 以及操作完成时的 CompletionHandler 注册到 AsynchronousOperationProcessor。AsynchronousOperationProcessor 使用 Facade 模式提供了一组异步操作 API(读/写等)供用户使用,当用户线程调用异步 API 后,便继续执行自己的任务。AsynchronousOperationProcessor 会开启独立的内核线程执行异步操作,实现真正的异步。当异步IO操作完成时,AsynchronousOperationProcessor 将用户线程与 AsynchronousOperation 一起注册的 Proactor 和 CompletionHandler 取出,然后将 CompletionHandler 与 IO 操作的结果数据一起转发给 Proactor,Proactor 负责回调每一个异步操作的事件完成处理函数 handle_event。虽然 Proactor 模式中每个异步操作都可以绑定一个 Proactor 对象,但是一般在操作系统中,Proactor 被实现为 Singleton 模式,以便于集中化分发操作完成事件。


    如图所示,异步 IO 模型中,用户线程直接使用内核提供的异步 IO API 发起 read 请求,且发起后立即返回,继续执行用户线程代码。不过此时用户线程已经将调用的 AsynchronousOperation 和 CompletionHandler 注册到内核,然后操作系统开启独立的内核线程去处理 IO 操作。当 read 请求的数据到达时,由内核负责读取 socket 中的数据,并写入用户指定的缓冲区中。最后内核将 read 的数据和用户线程注册的 CompletionHandler 分发给内部 Proactor,Proactor 将 IO 完成的信息通知给用户线程(一般通过调用用户线程注册的完成事件处理函数),完成异步 IO。

    用户线程使用异步 IO 模型的伪代码描述为:

    1
    2
    3
    4
    5
    6
    voidUserCompletionHandler::handle_event(buffer) {
        process(buffer);
    }
    {
        aio_read(socket,newUserCompletionHandler);
    }

    用户需要重写 CompletionHandler 的 handle_event 函数进行处理数据的工作,参数 buffer 表示 Proactor 已经准备好的数据,用户线程直接调用内核提供的异步 IO API,并将重写的 CompletionHandler 注册即可。

    到目前为止,已经将四个 IO 模型都介绍完了。现在回过头来回答最初的那几个问题:blocking 和 non-blocking 的区别在哪,synchronous IO 和 asynchronous IO 的区别在哪。

    blocking 与 non-blocking。调用 blocking IO 会一直 block 住对应的进程直到操作完成,而 non-blocking IO 在 kernel 还在准备数据的情况下会立刻返回。

    在说明 synchronous IO 和 asynchronous IO 的区别之前,需要先给出两者的定义。Stevens 给出的定义(其实是 POSIX 的定义)是这样子的:

    * A synchronous I/O operation causes the requesting process to be blocked until that I/O operation completes; 
    * An asynchronous I/O operation does not cause the requesting process to be blocked;

    两者的区别就在于 synchronous IO 做"IO operation"的时候会将 process 阻塞。按照这个定义,之前所述的 blocking IO,non-blocking IO,IO multiplexing 都属于 synchronous IO。有人可能会说,non-blocking IO 并没有被block啊。这里有个非常"狡猾"的地方,定义中所指的"IO operation"是指真实的IO操作,就是例子中的 recvfrom 这个系统调用。non-blocking IO 在执行 recvfrom 这个系统调用的时候,如果 kernel 的数据没有准备好,这时候不会 block 进程。但是当 kernel 中数据准备好的时候,recvfrom 会将数据从 kernel 拷贝到用户内存中,这个时候进程是被 block 了,在这段时间内进程是被 block 的。而 asynchronous IO 则不一样,当进程发起 IO 操作之后,就直接返回再也不理睬了,直到 kernel 发送一个信号,告诉进程说 IO 完成。在这整个过程中,进程完全没有被 block。

    各个 IO Model 的比较如图所示:


    经过上面的介绍,会发现 non-blocking IO 和 asynchronous IO 的区别还是很明显的。在 non-blocking IO 中,虽然进程大部分时间都不会被 block,但是它仍然要求进程去主动的 check,并且当数据准备完成以后,也需要进程主动的再次调用 recvfrom 来将数据拷贝到用户内存。而 asynchronous IO 则完全不同。它就像是用户进程将整个 IO 操作交给了他人(kernel)完成,然后他人做完后发信号通知。在此期间,用户进程不需要去检查 IO 操作的状态,也不需要主动的去拷贝数据。

    展开全文
  • 老张爱喝茶,废话不说,煮开水。(网上众说纷纭,不过我还是认为这个更加准确) ...(同步非阻塞) 老张还是觉得自己有点傻,于是变高端了,买了把会响笛的那种水壶。水开之后,能大声发出嘀~~~~
  • 百科解释:https://baike.baidu.com/item/%E5%BC%82%E6%AD%A5IO/6018433?fr=aladdin 先看2,再看你,会理解的更好! 1. 2.阻塞和非阻塞 ...# 非阻塞调用是指在不能立即得到结果之前,该调用不...
  • 本文图片均来自网络 一、同步IO---Blocking IO 在Blocking IO模型中,用户空间的应用程序执行一个系统调用(recvform),这会导致应用程序阻塞,直到数据准备好,并且将...二、同步非阻塞IO---NonBlocking IO 在Non...
  • 学习的时候遇到这个问题,在网络上也找不到说的比较准确的答案,想知道Java中的NIO是同步非阻塞IO还是多路复用IO?  </p>
  • 同步非阻塞IO是指在进行IO读写时,可以立即返回,不用阻塞等待read、write完全结束。 java实现同步非阻塞IO需要使用Java NIO技术。 先来看server端: 使用iava nio的ServerSocketChannel,设置 serverChannel....
  • 熟练掌握 BIO,NIO,AIO 的基本概念以及一些常见问题是你准备面试的过程中不可或缺的一部分,另外这些知识点也是你学习 Netty 的基础。 BIO,NIO,AIO 总结 1. BIO (Blocking I/O) ...2.2 NIO的特性/NIO与IO...
  • NIO同步非阻塞IO

    2018-12-27 21:32:36
    1.首先介绍一下BIO(同步阻塞IO) BIO-JDK1.0 - 同步阻塞式IO-BlockingIO 在执行ACCEPT CONNECT READ WRITE 四中操作时都会产生阻塞 Accept:客户端未连接 Connect:连接超时(connection reset);连接拒绝...
  • java同步非阻塞IO

    2018-07-02 01:30:21
    异步IO编程在javascript中得到了广泛的应用,之前也写过一篇博文...从另外一个角度看待的话,底层操作系统对于非阻塞IO的系统调用是一种多路复用机制,js对其进行了比较厚的封装,转换成了异步IO。但是,也可以进...
  • NIO----同步非阻塞IO

    2021-04-20 19:25:17
    NIO概述分类NIO:同步非阻塞IO缺点BIO:同步阻塞式IO缺点NIO三大组件BufferChannelselector 概述 用于数据传输 同步:一个对象或一段业务逻辑同一时间只允许一个线程使用 异步:一个对象或一段业务逻辑同一时间...
  • Java NIO 同步非阻塞IO

    2018-03-18 09:24:53
    Java NIO ...阻塞与非阻塞IO 选择器(Selectors) 总结 IO/NIO/AIO 区别 NIO AIO BIO、NIO、AIO适用场景 Java NIO Java NIO(New IO)是从Java 1.4版本开始引入的一个新的IO API,...
  • 2019独角兽企业重金招聘Python...是比较浪费CPU的方式,一般很少直接使用这种模型,而是在其他IO模型中使用非阻塞IO这一特性 转载于:https://my.oschina.net/huangguangsheng/blog/2231195
  • NIO(同步非阻塞IO

    2019-09-20 22:15:12
    1、区别 NIO相当于只是建立了一个连接两端的通道,而实际传输数据的是缓冲区。 NIO特点: a、面向缓冲区,用缓冲区传输数据。...普通IO是面向字节流的,建立了管道就是为了让数据在管道中流动传输,而且只能是...
  •  在比较这两个模式之前,我们首先的搞明白几个概念,什么是阻塞和非阻塞,什么是同步和异步 , 同步和异步 是针对应用程序和内核的交互而言的 ,同步指的是用户进程触发 IO 操作并等待或者轮询的去
  • Socket通信(BIO/NIO/AIO)编程 BIO: 传统阻塞IO NIO: 同步非阻塞式IO AIO: 异步非阻塞IO,(非阻塞采用的是注册通知的模式。)为什么会选择Netty?因为它简单!使用Netty不必编写复杂的逻辑代码去实现通信,再也...
  • Linux中的多路复用技术---epoll的详解 epoll完整例子
  • 对于IO来说,我们听得比较多的是: BIO:阻塞IONIO:非阻塞IO...同步阻塞IO同步非阻塞IO异步阻塞IO异步非阻塞IO 那么什么是阻塞IO、非阻塞IO、同步IO、异步IO呢? 一个IO操作其实分成了两个步骤:发起IO请求(阻塞)
  • IO的两个重要步骤:发起IO请求,和实际的IO操作。 在unix网络编程的定义里异步和非异步概念的区别就是实际的IO操作是否阻塞...NIO:同步非阻塞IO 参考如下 同步需要主动去询问结果 http://weixiaolu.iteye.com/b...
  • 网络IO模型:同步IO和异步IO,阻塞IO和非阻塞IO
  • 非阻塞IO:IO操作执行之后会立即返回,不会使进程或者线程进入休眠状态,如果没有完成,会返回返回错误; 同步同步想表达的是这样一种情形,呃,只能写个各个博客惯用的例子描述:我想找lucy,打电话找不到,我就...
  • 1、BIO(阻塞IO)、NIO(同步非阻塞IO)、AIO(异步非阻塞IO)区别 2、java NIO原理及实例 1、BIO(阻塞IO)、NIO(同步非阻塞IO)、AIO(异步非阻塞IO)区别 一:事件分离器 在IO读写时,把 IO请求 与 读写操作 ...
  • 如何理解分5种IO模型、阻塞IO和非阻塞IO同步IO、信号驱动IO和异步IO前言一、IO的概念二、5种IO模型阻塞IO模型(blocking IO)非阻塞IO模型(nonblocking IO)IO复用模型(IO multiplexing)信号驱动IO模型异步IO模型...
  • 同步IO、异步IO、阻塞IO、非阻塞IO, POSIX(可移植操作系统接口)把同步IO操作定义为导致进程阻塞直到IO完成的操作,反之则是异步IO,按POSIX的描述似乎把同步和阻塞划等号,异步和非阻塞划等号,但是为什么有的人说...
  • 同步IO、异步IO、阻塞IO、非阻塞IO,这几个词常见于各种各样的与网络相关的文章之中,往往不同上下文中它们的意思是不一样的,以致于我在很长一段时间对此感到困惑,所以想写一篇文章整理一下。 POSIX(可移植操作...
  • 5种IO模型、阻塞IO和非阻塞IO同步IO和异步IO

    万次阅读 多人点赞 2016-10-28 20:01:41
    5种IO模型、阻塞IO和非阻塞IO同步IO和异步IO 看了一些文章,发现有很多不同的理解,可能是因为大家入切的角度、环境不一样。所以,我们先说明基本的IO操作及环境。本文是在《UNIX网络编程 卷1:套接字联网API》...
  • 很多时候我们常常看到同步与异步,阻塞与非阻塞的出现。有的地方直接将同步与阻塞画上了等号。异步与非阻塞画上了等号。事实上这是不对的。同步不等于阻塞,而异步也不等于非阻塞。下面就来仔细的看看同步与异步、...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 5,555
精华内容 2,222
关键字:

同步非阻塞io