精华内容
下载资源
问答
  • 多路复用器

    2020-09-27 23:35:35
    多路复用器基本知识多进程/多线程连接处理模型多路复用连接处理模型工作原理selectpollepollLT模式ET模式 基本知识 多进程/多线程连接处理模型 在该模型下,一个用户连接请求会由一个内核进程处理,而一个内核进程...

    基本知识

    多进程/多线程连接处理模型

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-68Y9WyqG-1601220469495)(C:\Users\bbxyl\Desktop\nginx\image-20200927225215149.png)]
    在该模型下,一个用户连接请求会由一个内核进程处理,而一个内核进程会创建一个应用程序进程,即app进程来处理该连接请求。应用程序进程在调用IO时,采用的是BIO(阻塞IO)通讯方式,即应用程序进程在未获取到IO响应之前是处于阻塞态的。

    优点

    • 内核进程不存在对app进程的竞争,一个内核进程对应一个app进程

    缺点

    • 若请求很多,需要创建很多的app进程
    • 一个系统的进程数量是有上限的,所以该模型不能处理高并发的情况
    • app进程中使用的用户连接请求数据是复制于内核进程的,没有使用零拷贝,效率低,消耗系统资源

    多路复用连接处理模型

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-djg0iWCu-1601220469498)(C:\Users\bbxyl\Desktop\nginx\image-20200927225714285.png)]
    在该模型下,只有一个app进程来处理内核进程事务,且app进程一次只能处理一个内核进程事务。故这种模型对于内核进程来说,存在对app进程的竞争。

    在该模型下,需要通过多路复用器来获取各个内核进程的状态信息,将已经就绪的内核进程交由app进程执行

    多路复用器通过算法分析获取内核进程的状态,常见的算法有三种:selectpollepoll

    在该模型下,app进程采用的是NIO通讯方式,即该app进程不会阻塞。

    当一个IO结果返回时,app进程会暂停当前事务,将IO结果返回给对应的内核进程。然后再继续执行暂停的线程。

    工作原理

    select

    select多路复用器是采用轮询的方式,一直在轮询所有的相关内核进程,查看它们的进程状态。若已经就绪,则马上将该内核进程放入到就绪队列(该就绪队列底层由数组实现)。否则,继续查看下一个内核进程状态。在处理内核进程事务之前,app进程首先会从内核空间中将用户连接请求相关数据复制到用户空间。
    该多路复用器的缺陷有以下几点:

    • 对所有内核进程采用轮询方式效率会很低。因为对于大多数情况下,内核进程都不属于就绪状态,只有少部分才会是就绪态。所以这种轮询结果大多数都是无意义的
    • 由于就绪队列底层由数组实现,所以其所能处理的内核进程数量是有限制的,即其能够处理的最大并发连接数量是有限制的
    • 从内核空间到用户空间的复制,系统开销大。

    poll

    poll多路复用器的工作原理与select几乎相同,不同的是,由于其就绪队列由链表实现,所以,其对于要处理的内核进程数量理论上是没有限制的,即其能够处理的最大并发连接数量是没有限制的(当然,要受限于当前系统中进程可以打开的最大文件描述符数ulimit)。

    epoll

    epoll多路复用器是对select与poll的增强与改进。其不再采用轮询方式了,而是采用回调方式实现对内核进程状态的获取:一旦内核进程就绪,其就会回调epoll多路复用器,进入到多路复用器的就绪队列(由链表实现)。所以epoll多路复用模型也称为epoll事件驱动模型
    另外,应用程序所使用的数据,也不再从内核空间复制到用户空间了,而是使用mmap零拷贝机制,大大降低了系统开销。

    问:当内核进程就绪信息通知了epoll多路复用器后,多路复用器就会马上对其进行处理,将其马上存放到就绪队列吗?

    答:不是的。根据处理方式的不同,可以分为两种处理模式:LT模式ET模式

    LT模式

    LT,Level Triggered,水平触发模式即只要内核进程的就绪通知由于某种原因暂时没有被epoll处理,则该内核进程就会定时将其就绪信息通知epoll。直到epoll将其写入到就绪队列,或由于某种原因该内核进程又不再就绪而不再通知。其支持两种通讯方式:BIO与NIO。

    ET模式

    ET,Edge Triggered,边缘触发模式其仅支持NIO的通讯方式。当内核进程的就绪信息仅会通知一次epoll,无论epoll是否处理该通知。明显该方式的效率要高于LT模式,但其有可能会出现就绪通知被忽视的情况,即连接请求丢失的情况。

    展开全文
  • NIO多路复用器Selector

    2018-08-07 22:29:54
    selector简称多路复用器,它是JAVA NIO编程的基础简单来讲,selector会不断轮询注册在其上的channel,如果某个channel上面发生了读或者写事件,这个channel就会处于就绪状态,会被selector轮询出来,然后通过...

          selector简称多路复用器,它是JAVA NIO编程的基础简单来讲,selector会不断轮询注册在其上的channel,如果某个channel上面发生了读或者写事件,这个channel就会处于就绪状态,会被selector轮询出来,然后通过selectorKey可以获取就绪channel的集合,进行后续的io操作。

          一个多路复用器selector可以同时轮询多个channel,由于jdk使用epoll()替代传统的selector实现,所以他并没有最大连接句柄限制,也就意味着只需要一个负责轮询selector的线程就可以接入成千上万的channel.

    展开全文
  • Nginx多路复用器select、 poll 、 epoll

    千次阅读 2019-06-23 11:50:09
    若要理解select、poll与epoll多路复用器的工作原理,就需要首先了解什么是多路复用器。而要了解什么是多路复用器,就需要先了解什么是“多进程/多线程连接处理模型”。 多进程/多线程连接处理模型 在该模型下,...

    一、简介

    若要理解select、poll与epoll多路复用器的工作原理,就需要首先了解什么是多路复用器。而要了解什么是多路复用器,就需要先了解什么是“多进程/多线程连接处理模型”。

    多进程/多线程连接处理模型

    在该模型下,一个用户连接请求会由一个内核进程处理,而一个内核进程会创建一个应用程序进程,即app进程来处理该连接请求。应用程序进程在调用IO时,采用的是BIO(blocking I/O,同步并阻塞)通讯方式,即应用程序进程在未获取到IO响应之前是处于阻塞态的。

    优点:

    1、内核进程不存在app进程的竞争。

    缺点:

    1、若请求太多,则需要创建很多的app进程

    2、一个系统的进程数量是有上限的,所以该模型不能处理高并发情况

    3、app进程中使用的用户连接请求数据是复制到内核进程,没有零拷贝,效率低,消耗系统资源

     

    二、多路复用连接处理模型

    在该模型下,只有一个app进程来处理内核进程事务,且app进程一次只能处理一个内核进程事务。故这种模型对于内核进程来说,存在对app进程的竞争。

     

    三、多路复用器工作原理

    1、select

    select多路复用器是采用轮询的方式,一直在轮询所有的相关内核进程,查看它们的进程状态。若已经就绪,则马上将该内核进程放入到就绪队列,该就绪队列底层由数组实现。否则,继续查看下一个内核进程状态。在处理内核进程事务之前,app进程首先会从内核空间中将用户连接请求相关数据复制到用户空间。

    优点:

    1、app调用IO是非阻塞

    缺点:

    1、轮询,占用CPU资源以及大多数内核进程处于非就绪状态,有点做无用功。

    2、底层数组实现,不适合高并发。

    3、复制,用户数据复制到内核空间,没有零拷贝,效率低,消耗系统资源

    2、poll

    poll多路复用器的工作原理与select几乎相同,不同的是,由于其就绪队列由链表实现,所以,其对于要处理的内核进程数量理论上是没有限制的,即其能够处理的最大并发连接数量是没有限制的(当然,要受限于当前系统中进程可以打开的最大文件描述符数ulimit,后面会讲到)。

    优点:

    1、链表实现,可以处理高并发。查看系统连接数限制数ulimit -n

    2、app调用IO是非阻塞

    缺点:

    1、轮询,占用CPU资源以及大多数内核进程处于非就绪状态,有点做无用功。

    2、复制,用户数据复制到内核空间,没有零拷贝,效率低,消耗系统资源

    3、epoll

    epoll多路复用是对select与poll的增强与改进。其不再采用轮询方式了,而是采用回调方式实现对内核进程状态的获取:一旦内核进程就绪,其就会回调epoll多路复用器,进入到多路复用器的就绪队列(由链表实现)。所以epoll多路复用模型也称为epoll事件驱动模型。
    另外,应用程序所使用的数据,也不再从内核空间复制到用户空间了,而是使用mmap零拷贝机制,大大降低了系统开销。
    当内核进程就绪信息通知了epoll多路复用器后,并不是马上对其进行处理,将其立即存放到就绪队列,而是根据不同的处理模型,有不同的处理方式。两种模型:LT模型与ET模型。

    •  LT,Level Trigered,水平触发模型,若没有将其存放到就绪队列,其会一直重试,直到加入到就绪队列,或取消了本次请求。效率低些但请求不丢失。
    •  ET,Edge Trigered,边缘触发模型,若没有将其存放到就绪队列,则直接取消本次请求。所以其会存在请求丢失的情况,但其效率很高。效率高些,但有请求丢失。

    优点:

    1、采用回调方式,节省CPU资源

    2、链表实现,可以处理高并发

    3、mmap零拷贝机制,效率高

    展开全文
  • Java NIO之多路复用器(Selector)

    千次阅读 2019-03-15 20:35:51
    多路复用器Selector是Java NIO编程的基础,熟练地掌握Selector对于掌握NIO编程至关重要。多路复用器提供选择已经就绪的任务的能力。简单来讲,Selector会不断地轮询注册在其上的Channel,如果某个Channel上面有新的...

    多路复用器Selector是Java NIO编程的基础,熟练地掌握Selector对于掌握NIO编程至关重要。多路复用器提供选择已经就绪的任务的能力。简单来讲,Selector会不断地轮询注册在其上的Channel,如果某个Channel上面有新的TCP连接接入、读和写事件,这个Channel就处于就绪状态,会被Selector轮询出来,然后通过SelectionKey可以获取就绪Channel的集合,进行后续的I/O操作。

      一个多路复用器Selector可以同时轮询多个Channel,由于JDK使用了epoll代替传统的select实现,所以它并没有最大连接句柄1024/2048的限制。这也就意味着只需要一个线程负责Selector的轮询,就可以介入成千上万的客户端。

    Selector(选择器)是Java NIO中能够检测一到多个NIO通道,并能够知晓通道是否为诸如读写事件做好准备的组件。这样,一个单独的线程可以管理多个channel,从而管理多个网络连接。

    一、为什么使用Selector?

    仅用单个线程来处理多个Channels的好处是,只需要更少的线程来处理通道。事实上,可以只用一个线程处理所有的通道。对于操作系统来说,线程之间上下文切换的开销很大,而且每个线程都要占用系统的一些资源(如内存)。因此,使用的线程越少越好。

    但是,需要记住,现代的操作系统和CPU在多任务方面表现的越来越好,所以多线程的开销随着时间的推移,变得越来越小了。实际上,如果一个CPU有多个内核,不使用多任务可能是在浪费CPU能力。不管怎么说,关于那种设计的讨论应该放在另一篇不同的文章中。在这里,只要知道使用Selector能够处理多个通道就足够了。

    下面是单线程使用一个Selector处理3个channel的示例图:

     

    二、Selector简介

      选择器提供选择执行已经就绪的任务的能力,从底层来看,Selector提供了询问通道是否已经准备好执行每个I/O操作的能力。Selector 允许单线程处理多个Channel。仅用单个线程来处理多个Channels的好处上面已经解释了。

    在开始之前,需要回顾一下Selector、SelectableChannel和SelectionKey:

    选择器(Selector)

    Selector选择器类管理着一个被注册的通道集合的信息和它们的就绪状态。通道是和选择器一起被注册的,并且使用选择器来更新通道的就绪状态。当这么做的时候,可以选择将被激发的线程挂起,直到有就绪的的通道。

    可选择通道(SelectableChannel)

    SelectableChannel这个抽象类提供了实现通道的可选择性所需要的公共方法。它是所有支持就绪检查的通道类的父类。因为FileChannel类没有继承SelectableChannel因此是不是可选通道,而所有socket通道都是可选择的,包括从管道(Pipe)对象的中获得的通道。SelectableChannel可以被注册到Selector对象上,同时可以指定对那个选择器而言,那种操作是感兴趣的。一个通道可以被注册到多个选择器上,但对每个选择器而言只能被注册一次。

    选择键(SelectionKey)

    选择键封装了特定的通道特定的选择器的注册关系。选择键对象被SelectableChannel.register()返回并提供一个表示这种注册关系的标记。选择键包含了两个比特集(以整数的形式进行编码),指示了该注册关系所关心的通道操作,以及通道已经准备好的操作。

     

     

    三、Selector的使用

    3.1、Selector的创建

    通过调用Selector.open()方法创建一个Selector,通过Selector的静态方法open()创建,如下:

    Selector selector = Selector.open();

    3.2、将channel注册到Selector中

    为了将Channel和Selector配合使用,必须将channel注册到selector上。通过SelectableChannel.register()方法来实现,如下:

    channel.configureBlocking(false);
    SelectionKey key = channel.register(selector, Selectionkey.OP_READ);

    与Selector一起使用时,Channel必须处于非阻塞模式下这意味着不能将FileChannel与Selector一起使用,因为FileChannel不能切换到非阻塞模式。而套接字通道都可以

    注意register()方法的第二个参数。这是一个“interest集合”,意思是在通过Selector监听Channel时对什么事件感兴趣。可以监听四种不同类型的事件:

    1. Connect
    2. Accept
    3. Read
    4. Write

    通道触发了一个事件意思是该事件已经就绪。所以,某个channel成功连接到另一个服务器称为“连接就绪”。一个server socket channel准备好接收新进入的连接称为“接收就绪”。一个有数据可读的通道可以说是“读就绪”。等待写数据的通道可以说是“写就绪”。

    这四种事件用SelectionKey的四个常量来表示:

    1. SelectionKey.OP_CONNECT
    2. SelectionKey.OP_ACCEPT
    3. SelectionKey.OP_READ
    4. SelectionKey.OP_WRITE

    如果你对不止一种事件感兴趣,那么可以用“位或”操作符将常量连接起来,如下:

    int interestSet = SelectionKey.OP_READ | SelectionKey.OP_WRITE;

    在下面还会继续提到interest集合。

    SelectionKey

    在上一小节中,当向Selector注册Channel时,register()方法会返回一个SelectionKey对象。这个对象包含了一些你感兴趣的属性:

    • interest集合
    • ready集合
    • Channel
    • Selector
    • 附加的对象(可选)

    下面我会描述这些属性。

    interest集合

    就像向Selector注册通道一节中所描述的,interest集合是你所选择的感兴趣的事件集合。可以通过SelectionKey读写interest集合,像这样:

    int interestSet = selectionKey.interestOps();
    
    boolean isInterestedInAccept  = (interestSet & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT;
    boolean isInterestedInConnect = interestSet & SelectionKey.OP_CONNECT;
    boolean isInterestedInRead    = interestSet & SelectionKey.OP_READ;
    boolean isInterestedInWrite   = interestSet & SelectionKey.OP_WRITE;

    可以看到,用“位与”操作interest 集合和给定的SelectionKey常量,可以确定某个确定的事件是否在interest 集合中。

    ready集合

    ready 集合是通道已经准备就绪的操作的集合。在一次选择(Selection)之后,你会首先访问这个ready set。Selection将在下一小节进行解释。可以这样访问ready集合:

    int readySet = selectionKey.readyOps();

    可以用像检测interest集合那样的方法,来检测channel中什么事件或操作已经就绪。但是,也可以使用以下四个方法,它们都会返回一个布尔类型:

    selectionKey.isAcceptable();//等价于selectionKey.readyOps()&SelectionKey.OP_ACCEPT
    selectionKey.isConnectable();
    selectionKey.isReadable();
    selectionKey.isWritable();

    需要注意的是,通过相关的选择键的readyOps()方法返回的就绪状态指示只是一个提示,底层的通道在任何时候都会不断改变,而其他线程也可能在通道上执行操作并影响到它的就绪状态。另外,我们不能直接修改read集合。

    取出SelectionKey所关联的Selector和Channel

    从SelectionKey访问Channel和Selector很简单。如下:

    Channel  channel  = selectionKey.channel();
    Selector selector = selectionKey.selector();

    关于取消SelectionKey对象

    我们可以通过SelectionKey对象的cancel()方法来取消特定的注册关系。该方法调用之后,该SelectionKey对象将会被”拷贝”至已取消键的集合中,该键此时已经失效,但是该注册关系并不会立刻终结。在下一次select()时,已取消键的集合中的元素会被清除,相应的注册关系也真正终结。

    为SelectionKey绑定附加对象

    可以将一个对象或者更多信息附着到SelectionKey上,这样就能方便的识别某个给定的通道。例如,可以附加 与通道一起使用的Buffer,或是包含聚集数据的某个对象。使用方法如下:

    selectionKey.attach(theObject);
    Object attachedObj = selectionKey.attachment();

    还可以在用register()方法向Selector注册Channel的时候附加对象。如:

    SelectionKey key = channel.register(selector, SelectionKey.OP_READ, theObject);

    需要注意的是如果附加的对象不再使用,一定要人为清除,因为垃圾回收器不会回收该对象,若不清除的话会成内存泄漏。

    一个单独的通道可被注册到多个选择器中,有些时候我们需要通过isRegistered()方法来检查一个通道是否已经被注册到任何一个选择器上。 通常来说,我们并不会这么做。

    3.3、通过Selector选择通道

    我们可以通过 Selector.select()方法获取对某件事件准备好了的 Channel, 即如果我们在注册 Channel 时, 对其的可写事件感兴趣, 那么当 select()返回时, 我们就可以获取 Channel 了.

    注意, select()方法返回的值表示有多少个 Channel 可操作.

    我们知道选择器维护注册过的通道的集合,并且这种注册关系都被封装在SelectionKey当中。接下来我们简单的了解一下Selector维护的三种类型SelectionKey集合:

    已注册的键的集合(Registered key set)

    所有与选择器关联的通道所生成的键的集合称为已经注册的键的集合。并不是所有注册过的键都仍然有效。这个集合通过keys()方法返回,并且可能是空的。这个已注册的键的集合不是可以直接修改的;试图这么做的话将引发java.lang.UnsupportedOperationException。

    已选择的键的集合(Selected key set)

    已注册的键的集合的子集。这个集合的每个成员都是相关的通道被选择器(在前一个选择操作中)判断为已经准备好的,并且包含于键的interest集合中的操作。这个集合通过selectedKeys()方法返回(并有可能是空的)。 
    不要将已选择的键的集合与ready集合弄混了。这是一个键的集合,每个键都关联一个已经准备好至少一种操作的通道。每个键都有一个内嵌的ready集合,指示了所关联的通道已经准备好的操作。键可以直接从这个集合中移除,但不能添加。试图向已选择的键的集合中添加元素将抛出java.lang.UnsupportedOperationException。

    已取消的键的集合(Cancelled key set)

    已注册的键的集合的子集,这个集合包含了cancel()方法被调用过的键(这个键已经被无效化),但它们还没有被注销。这个集合是选择器对象的私有成员,因而无法直接访问。

    在刚初始化的Selector对象中,这三个集合都是空的。通过Selector的select()方法可以选择已经准备就绪的通道(这些通道包含你感兴趣的的事件)。比如你对读就绪的通道感兴趣,那么select()方法就会返回读事件已经就绪的那些通道。下面是Selector几个重载的select()方法: 

    • select():阻塞到至少有一个通道在你注册的事件上就绪了。 
    • select(long timeout):和select()一样,但最长阻塞事件为timeout毫秒。 
    • selectNow():非阻塞,只要有通道就绪就立刻返回。

    select()方法返回的int值表示有多少通道已经就绪。亦即,自上次调用select()方法后有多少通道变成就绪状态。如果调用select()方法,因为有一个通道变成就绪状态,返回了1,若再次调用select()方法,如果另一个通道就绪了,它会再次返回1。如果对第一个就绪的channel没有做任何操作,现在就有两个就绪的通道,但在每次select()方法调用之间,只有一个通道就绪了。

    selectedKeys()

    一旦调用了select()方法,并且返回值表明有一个或更多个通道就绪了,然后可以通过调用selector的selectedKeys()方法,访问“已选择键集(selected key set)”中的就绪通道。如下所示:

    Set selectedKeys = selector.selectedKeys();

    当像Selector注册Channel时,Channel.register()方法会返回一个SelectionKey 对象。这个对象代表了注册到该Selector的通道。可以通过SelectionKey的selectedKeySet()方法访问这些对象。

    可以遍历这个已选择的键集合来访问就绪的通道。如下:

    Set selectedKeys = selector.selectedKeys();
    Iterator keyIterator = selectedKeys.iterator();
    while(keyIterator.hasNext()) {
        SelectionKey key = keyIterator.next();
        if(key.isAcceptable()) {
            // a connection was accepted by a ServerSocketChannel.
        } else if (key.isConnectable()) {
            // a connection was established with a remote server.
        } else if (key.isReadable()) {
            // a channel is ready for reading
        } else if (key.isWritable()) {
            // a channel is ready for writing
        }
        keyIterator.remove();
    }

    这个循环遍历已选择键集中的每个键,并检测各个键所对应的通道的就绪事件。

    注意, 在每次迭代时, 我们都调用 "keyIterator.remove()" 将这个 key 从迭代器中删除, 因为 select() 方法仅仅是简单地将就绪的 IO 操作放到 selectedKeys 集合中, 因此如果我们从 selectedKeys 获取到一个 key, 但是没有将它删除, 那么下一次 select 时, 这个 key 所对应的 IO 事件还在 selectedKeys 中.
    例如此时我们收到 OP_ACCEPT 通知, 然后我们进行相关处理, 但是并没有将这个 Key 从 SelectedKeys 中删除, 那么下一次 select() 返回时 我们还可以在 SelectedKeys 中获取到 OP_ACCEPT 的 key.
    注意, 我们可以动态更改 SekectedKeys 中的 key 的 interest set. 例如在 OP_ACCEPT 中, 我们可以将 interest set 更新为 OP_READ, 这样 Selector 就会将这个 Channel 的 读 IO 就绪事件包含进来了。

    SelectionKey.channel()方法返回的通道需要转型成你要处理的类型,如ServerSocketChannel或SocketChannel等。

    关于Selector执行选择的过程

    我们知道调用select()方法进行选择通道,现在我们再来深入一下选择的过程,也就是select()执行过程。当select()被调用时将执行以下几步:

    1. 首先检查已取消键集合,也就是通过cancle()取消的键。如果该集合不为空,则清空该集合里的键,同时该集合中每个取消的键也将从已注册键集合和已选择键集合中移除。(一个键被取消时,并不会立刻从集合中移除,而是将该键“拷贝”至已取消键集合中,这种取消策略就是我们常提到的“延迟取消”。)
    2. 再次检查已注册键集合(准确说是该集合中每个键的interest集合)。系统底层会依次询问每个已经注册的通道是否准备好选择器所感兴趣的某种操作,一旦发现某个通道已经就绪了,则会首先判断该通道是否已经存在在已选择键集合当中,如果已经存在,则更新该通道在已注册键集合中对应的键的ready集合,如果不存在,则首先清空该通道的对应的键的ready集合,然后重设ready集合,最后将该键存至已注册键集合中。这里需要明白,当更新ready集合时,在上次select()中已经就绪的操作不会被删除,也就是ready集合中的元素是累积的,比如在第一次的selector对某个通道的read和write操作感兴趣,在第一次执行select()时,该通道的read操作就绪,此时该通道对应的键中的ready集合存有read元素,在第二次执行select()时,该通道的write操作也就绪了,此时该通道对应的ready集合中将同时有read和write元素。

    深入已注册键集合的管理

    到现在我们已经知道一个通道的的键是如何被添加到已选择键集合中的,下面我们来继续了解对已选择键集合的管理 。首先要记住:选择器不会主动删除被添加到已选择键集合中的键,而且被添加到已选择键集合中的键的ready集合只能被设置,而不能被清理。如果我们希望清空已选择键集合中某个键的ready集合该怎么办?我们知道一个键在新加入已选择键集合之前会首先置空该键的ready集合,这样的话我们可以人为的将某个键从已注册键集合中移除最终实现置空某个键的ready集合。被移除的键如果在下一次的select()中再次就绪,它将会重新被添加到已选择的键的集合中。这就是为什么要在每次迭代的末尾调用keyIterator.remove()

    停止选择

    选择器执行选择的过程,系统底层会依次询问每个通道是否已经就绪,这个过程可能会造成调用线程进入阻塞状态,那么我们有以下三种方式可以唤醒在select()方法中阻塞的线程。

    1. 通过调用Selector对象的wakeup()方法让处在阻塞状态的select()方法立刻返回 
      该方法使得选择器上的第一个还没有返回的选择操作立即返回。如果当前没有进行中的选择操作,那么下一次对select()方法的一次调用将立即返回。

    2. 通过close()方法关闭Selector** 
      该方法使得任何一个在选择操作中阻塞的线程都被唤醒(类似wakeup()),同时使得注册到该Selector的所有Channel被注销,所有的键将被取消,但是Channel本身并不会关闭。

    3. 调用interrupt() 
      调用该方法会使睡眠的线程抛出InterruptException异常,捕获该异常并在调用wakeup()

    上面有些人看到“系统底层会依次询问每个通道”时可能在想如果已选择键非常多是,会不会耗时较长?答案是肯定的。但是我想说的是通常你可以选择忽略该过程,至于为什么,后面再说。

    Selector 的基本使用流程

    1. 通过 Selector.open() 打开一个 Selector.

    2. 将 Channel 注册到 Selector 中, 并设置需要监听的事件(interest set)

    3. 不断重复:

      • 调用 select() 方法

      • 调用 selector.selectedKeys() 获取 selected keys

      • 迭代每个 selected key:

        • *从 selected key 中获取 对应的 Channel 和附加信息(如果有的话)

        • *判断是哪些 IO 事件已经就绪了, 然后处理它们. 如果是 OP_ACCEPT 事件, 则调用 "SocketChannel clientChannel = ((ServerSocketChannel) key.channel()).accept()" 获取 SocketChannel, 并将它设置为 非阻塞的, 然后将这个 Channel 注册到 Selector 中.

        • *根据需要更改 selected key 的监听事件.

        • *将已经处理过的 key 从 selected keys 集合中删除.

    示例:NIO编程实现:利用线程池,主线程接收请求,发现有就绪事件交给子线程进行IO操作 (与之前未用线程池比较,客户端基本没有改变,主要改变了服务端)

    public class NIOServerPool {
        /**
         * 利用线程池实现NIO服务端
         * 主线程用于接收请求,交给子线程进行IO操作
         * @param args
         */
        private ServerSocketChannel server;
        private Selector selector;
        ExecutorService executor = Executors.newCachedThreadPool();
        public static void main(String[] args) throws IOException {
            int port = 1234;
            NIOServerPool server = new NIOServerPool();
            server.bind(port);
            server.start();
        }
        //绑定端口
        public void bind(int port) throws IOException {
            server = ServerSocketChannel.open();
            selector = Selector.open();
            server.bind(new InetSocketAddress(port));
            server.configureBlocking(false);
            server.register(selector,SelectionKey.OP_ACCEPT);
            System.out.println("服务端启动");
        }
        //选择器轮询并安排子线程进行操作
        public void start() throws IOException {
            while(selector.select()>0){
                Iterator<SelectionKey> itr = selector.selectedKeys().iterator();
                while(itr.hasNext()){
                    SelectionKey key = itr.next();
                    itr.remove();
                    if(key.isValid() && key.isAcceptable()){
                        ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
                        SocketChannel sc = ssc.accept();
                        System.out.println("客户端已上线");
                        ThreadTask task = new ThreadTask(sc);
                        executor.submit(task);
                    }
                }
            }
            server.close();
            selector.close();
            executor.shutdown();
    
        }
        class ThreadTask implements Runnable{
            //子类选择器
            Selector selector = Selector.open();
            //缓冲区
            ByteBuffer byteBuffer ;
            ThreadTask(SocketChannel channel) throws IOException {
                channel.configureBlocking(false);
                channel.register(selector,SelectionKey.OP_READ);
                byteBuffer = ByteBuffer.allocate(1024);
                SocketAddress address = channel.getRemoteAddress();
                System.out.println("线程"+Thread.currentThread().getName()+"处理"+address+"的用户请求");
            }
            @Override
            public void run() {
                try {
                    while(selector.select()>0){
                        Iterator<SelectionKey> itr = selector.selectedKeys().iterator();
                        while(itr.hasNext()){
                            SelectionKey key = itr.next();
                            itr.remove();
                            if(key.isValid() && key.isReadable()){
                                SocketChannel socket = (SocketChannel) key.channel();
                                SocketAddress address = socket.getRemoteAddress();
                                StringBuffer sBuffer = new StringBuffer();
                                while(socket.read(byteBuffer)>0){
                                    byteBuffer.flip();
                                    int count = byteBuffer.remaining();
                                    sBuffer.append(new String(byteBuffer.array()),0,count);
                                    byteBuffer.clear();
                                }
                                String msg = sBuffer.toString();
                                if("".equals(msg)){
                                    continue;
                                }
                                if(msg.equals("exit")){
                                    socket.close();
                                    key.cancel();
                                    System.out.println(address+"下线");
                                    return;
                                }
                                System.out.println("客户端"+address+"发送消息");
                                byteBuffer.clear();
                                byteBuffer.put("server ack/n".getBytes());
                                byteBuffer.flip();
                                socket.write(byteBuffer);
                                byteBuffer.clear();
                            }
                        }
    
                    }
                }catch (IOException e){
                    e.printStackTrace();
                }
            }
        }
    }
    
    
    public class NIOClient {
        private static final int DEFAULT_PORT = 1234;
        public static void main(String[] args) throws IOException {
            //创建SocketChannel实例
            SocketChannel socketChannel = SocketChannel.open();
            //创建选择器实例
            Selector selector = Selector.open();
            //将SocketChannel设为非阻塞
            socketChannel.configureBlocking(false);
            //将connect设为非阻塞,则无论connect是否发生都会立即返回
            if(!socketChannel.connect(new InetSocketAddress("127.0.0.1",DEFAULT_PORT))){
                //服务端还未连接上,对connect事件进行监听,将socketChannel注册到选择器上
                socketChannel.register(selector,SelectionKey.OP_CONNECT);
            }
            int num;
            while((num = selector.select(1000))>0){
                System.out.println("select返回"+num);
                //获取注册到选择器上的事件
                Set<SelectionKey> selectionKeys = selector.selectedKeys();
                Iterator<SelectionKey> iterator = selectionKeys.iterator();
                while(iterator.hasNext()){
                    SelectionKey key = iterator.next();
                    iterator.remove();
                    //可连接事件完成
                    if(key.isValid() && key.isConnectable()){
                        SocketChannel sc = (SocketChannel) key.channel();
                        key.cancel();
                        //若连接未完成,结束该用户连接
                        if(!sc.finishConnect()){
                            System.exit(1);
                        }
                        //连接完成,创建发送数据的缓冲区
                        ByteBuffer buffer = ByteBuffer.allocate(1024);
                        buffer.put("hello".getBytes());
                        buffer.flip();
                        //给服务端发消息
                        socketChannel.write(buffer);
                        System.out.println("给服务端发消息完成");
                    }
                }
            }
            //关闭
            selector.close();
            socketChannel.close();
            System.out.println("客户端关闭");
        }
    }
    
    
    

    转自https://www.cnblogs.com/duanxz/p/6782783.html

    展开全文
  • 数字电路基础知识——组合逻辑电路(数据选择器MUX、多路复用器) 数据选择器(MUX) 二选一数据选择器 用 2输入mux,实现与,或,非,异或 三、 四选一选择器 四、用选择器设计组合逻辑电路 五、例:用Verilog描述...
  • NIO解读之多路复用器Selector

    千次阅读 2015-03-20 16:26:01
    Selector是JDK的NIO中最重要的类之一,当我们通过Selector.open()方法打开一个多路复用器的时候实际上执行的open方法为 public static Selector open() throws IOException {  return SelectorProvider.provider()....
  • 看大图:https://www.processon.com/view/link/5f26da28f346fb5cdcb377a3
  • NIO存在的问题 NIO的优势:可以通过一个或几个线程来解决N个IO连接的处理 ...多路复用器 还是沿用上一版当中的 Java 代码 package com.bjmashibing.system.io; import java.io.IOException; import java.net.InetSocke
  • NIO 的优劣 优势:相比 BIO 来说,NIO 可以通过1个或几个线程,来解决 N 个 IO ...下图:左侧是 NIO,右侧是多路复用器多路复用器的实现 常问的几个概念 (我们先只关注IO,不关注IO之后的处理) 同步:application
  • 集中器与多路复用器

    千次阅读 2005-08-12 23:45:00
    代码转换,组合报文,进行差错控制等)高多路复用器终端独占信道无(透明)字符输入线路容量总和≤输出线路容量无有成对单一低 
  • 多路复用技术

    千次阅读 2019-01-18 15:52:30
    多路复用:数据通信系统或计算机网络系统中,传输媒体的带宽或容量往往会大于传输单一信号的需求,为了有效地利用通信线路,希望一个信道同时传输多路信号,这就是所谓的多路复用技术(Multiplexing)。 在计算机网络...
  • 5、深入剖析Java NIO之Selector(多路复用器

    万次阅读 多人点赞 2018-12-19 11:39:22
    关于多路复用的基本原理,在大白话分析BIO,NIO,AIO中简单的介绍了关于多路复用技术的理解。这章节,我们深入理解分析多路复用技术。以及JDK的部分源码作为参考。 2、多路复用快速认知 为了快速理解多路复用...
  • udp多路复用

    千次阅读 2019-05-15 17:41:38
    一个 UDP 多路复用,被用来处理共享同一个 UDP 端口的多个并发的 UDT 连接。 UDP 多路复用,根据包头的目标 Socket ID, 将接收到的不同的 UDT 包分发给相应 的 UDT Socket。换言之,多个 UDT Socket 绑定到不同的...
  • BIO NIO 多路复用器 NIO:操作系统角度:N表示非阻塞;JDK角度:N表示New IO(要掌握nonblocking,channel,buffer,selector),内存、文件系统 今天讲一些词汇:同步、异步、阻塞、非阻塞 1、OS 程序不能直接访问...
  • 只列举常用的,芯片均出自TI、ADI、SGM、Nexperia等。国产只考虑CH 泌恒。 个人整理,tb均能买到,如有纰漏欢迎纠错。
  • IO多路复用

    千次阅读 2019-06-23 14:48:40
    IO多路复用 同步IO、异步IO、IO多路复用 IO两个阶段 IO过程分两阶段: 数据准备阶段。从设备读取数据到内核空间的缓冲区 内核空间复制回用户空间进程缓冲区阶段 发生IO的时候: 内核从IO设备读数据(例如:淘米...
  • 时钟资源时钟布线资源具有高速、低SKEW的特点,它对系统设计非常重要,即使系统速率不高,也应该关注时钟设计,以消除潜在的时钟危险。Spartan-6器件包含16个全局时钟资源和40个I/O区域...局时钟多路复用器。I/O时钟...
  • 基带信号就是将数字信号1或0直接用两种不同的电压来表示,然后送到线路上去传输。... 常用的多路复用技术有频分多路复用技术和时分多路复用技术。 频分多路复用是将各路信号分别调制到不同的频段进行传输,
  • 管道和多路复用器延迟情况是难以忍受的。现代计算机能以惊人的速度生成数据,并且高速互联网(经常是在重要的服务器之间有多个并行连接)提供了极大的带宽,但是这可恶的延迟意味着电脑花了大量时间等待数据。基于延续...
  • 网络编程–多路复用器select、poll、epol,javaNIO原理和实现 之前已经说过了BIO模型的原理和实现,并根据其不足(阻塞,多线程资源消耗等),介绍了内核的升级实现了accpet和read不阻塞的方法,以及介绍了channel和...
  • 第二就是要想实现多路复用技术就需要一个工具:多路复用器。 拓展:多路复用器的存在意义是为了充分利用通信信道的容量,大大降低系统的成本。例如,对于一对电话线来说,它的通信频带一般在100kHz以上,而每一路...
  • 多路复用时钟

    千次阅读 2015-09-08 19:30:42
    时钟多路器用于使同一个逻辑功能具有不同的时钟,比如需要处理多个频率标准的通信应用常常使用多个时钟。下图是某一类型的时钟多路器: 虽然在时钟信号上引入多路逻辑会产生毛刺等问题,但是在不同的应用中,对...
  • 多路分解:在接收端,运输层检查报文段,...多路复用:从源主机的不同socket收集数据块,并为每个数据块装上首部信息(这将在以后用于分解)从而生成报文段,然后将报文段传递至网络层,所有这些工作称之为多路复用 ...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 73,593
精华内容 29,437
关键字:

多路复用器的作用