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

    2021-05-06 06:13:02
    多路复用器将接收的复合数据流,依照信道分离数据,并将它们送到对应的输出线上,故称为解多路复用器。 实际生活中,使用多路复用器可使多路数据信息共享一路信道,能充分地利用通信信道的容量,大大降低系统的成本...

    意义

    多路复用器将接收的复合数据流,依照信道分离数据,并将它们送到对应的输出线上,故称为解多路复用器。

    实际生活中,使用多路复用器可使多路数据信息共享一路信道,能充分地利用通信信道的容量,大大降低系统的成本。

    在这里插入图片描述

    举个例子:对于一对电话线来说,它的通信频带一般在100kHz以上,而每一路电话信号的频带一般限制在4kHz以下。此时,信道的容量远大于一路电话的信息传送量。

    逻辑电路原理

    多路复用器能将 N 个输入通道的数据复用到一个输出通道上。以4选 1 多路复用器为例。

    结构与真值表

    在这里插入图片描述在这里插入图片描述
    通过改变A与B的值,就能输出端F到底输出D1,D2,D3,D4中的哪个值。
    在这里插入图片描述

    逻辑电路

    在这里插入图片描述

    实现代码

    4 选1 多路复用器的实现代码

    library IEEE;
    use IEEE.STD_LOGIC_1164.ALL;
    use IEEE.STD_LOGIC_ARITH.ALL;
    use IEEE.STD_LOGIC_UNSIGNED.ALL;
    entity MUX4_1 is
        Port ( c0 : in  STD_LOGIC;
               c1 : in  STD_LOGIC;
               c2 : in  STD_LOGIC;
               c3 : in  STD_LOGIC;
               s : in  STD_LOGIC_VECTOR (1 downto 0);
               z : out  STD_LOGIC);
    end MUX4_1;
    architecture Behavioral of MUX4_1 is
    begin
         process(s)          --if 语句描述
         begin
          if(s="00") then z<=c0;
          elsif(s="01") then z<=c1;
          elsif(s="10") then z<=c2;
          else  z<=c3;
          end if;
        end process;
      --process(s)            --case 语句描述
    --begin
    --   case s is
    --       when "00" =>z<=c0;
    --      when "01" =>z<=c1;
    --      when "10" =>z<=c2;
    --      when others =>z<=c3;
    --  end case;
    --end process;
    end Behavioral;
    

    参考资料

    https://wiki.dzsc.com/4133.html

    展开全文
  • 1 模拟开关和多路复用器有... 2 模拟开关和多路复用器主要技术指标是什么?  导通电阻(RDS(ON))对于视频和音频信号来说都非常重要,因为导通电阻不仅会产生插入损耗,而且还会随着信号电压而变化,从而导致
  • 多路复用器

    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模式,但其有可能会出现就绪通知被忽视的情况,即连接请求丢失的情况。

    展开全文
  • 1 模拟开关和多路复用器有... 2 模拟开关和多路复用器主要技术指标是什么?  导通电阻(RDS(ON))对于视频和音频信号来说都非常重要,因为导通电阻不仅会产生插入损耗,而且还会随着信号电压而变化,从而导致
  • 了解Nginx的高性能之前我们先要知道两个重要的知识点,零拷贝与多路复用器,以及在Nginx中是如何应用的

    零拷贝(Zero Copy)

    零拷贝概念

    零拷贝指的是,从一个存储区域到另一个存储区域的 copy 任务没有 CPU 参与。零拷贝通常用于网络文件传输,以减少 CPU 消耗和内存带宽占用,减少用户空间与 CPU 内核空间的拷贝过程,减少用户上下文与 CPU 内核上下文间的切换,提高系统效率。
    用户空间指的是用户可操作的内存缓存区域,CPU 内核空间是指仅 CPU 可以操作的寄存器缓存及内存缓存区域。
    用户上下文指的是用户状态环境,CPU 内核上下文指的是 CPU 内核状态环境。
    零拷贝需要 DMA 控制器的协助。DMA,Direct Memory Access,直接内存存取,是 CPU的组成部分,其可以在 CPU 内核(算术逻辑运算器 ALU 等)不参与运算的情况下将数据从一个地址空间拷贝到另一个地址空间。

    传统拷贝方式

    首先通过应用程序的 read()方法将文件从硬盘读取出来,然后再调用 send()方法将文件发送出去。
    在这里插入图片描述

    该拷贝方式共进行了 4 次用户空间与内核空间的上下文切换,以及 4 次数据拷贝,其中两次拷贝存在 CPU 参与。
    我们发现一个很明显的问题:应用程序的作用仅仅就是一个数据传输的中介,最后将kernel buffer 中的数据传递到了 socket buffer。显然这是没有必要的。所以就引入了零拷贝。

    零拷贝方式

    Linux 系统(CentOS6 及其以上版本)对于零拷贝是通过 sendfile 系统调用实现的。
    在这里插入图片描述

    该拷贝方式共进行了 2 次用户空间与内核空间的上下文切换,以及 3 次数据拷贝,但整个拷贝过程均没有 CPU 的参与,这就是零拷贝。
    我们发现这里还存在一个问题:kernel buffer 到 socket buffer 的拷贝需要吗?kernelbuffer 与 socket buffer 有什么区别呢?DMA 控制器所控制的拷贝过程有一个要求,数据在源头的存放地址空间必须是连续的。kernel buffer 中的数据无法保证其连续性,所以需要将数据再拷贝到 socket buffer,socket buffer 可以保证了数据的连续性。
    这个拷贝过程能否避免呢?可以,只要主机的 DMA 支持 Gather Copy 功能,就可以避免由 kernel buffer 到 socket buffer 的拷贝。

    Gather Copy DMA 零拷贝方式

    由于该拷贝方式是由 DMA 完成,与系统无关,所以只要保证系统支持 sendfile 系统调用功能即可。
    该方式中没有数据拷贝到 socket buffer。取而代之的是只是将 kernel buffer 中的数据描述信息写到了 socket buffer 中。数据描述信息包含了两方面的信息:kernel buffer 中数据的地址及偏移量。
    在这里插入图片描述
    该拷贝方式共进行了 2 次用户空间与内核空间的上下文切换,以及 2 次数据拷贝,并且整个拷贝过程均没有 CPU 的参与。
    该拷贝方式的系统效率是高了,但与传统相比,也存在有不足。传统拷贝中 user buffer中存有数据,因此应用程序能够对数据进行修改等操作;零拷贝中的 user buffer 中没有了数据,所以应用程序无法对数据进行操作了。Linux 的 mmap 零拷贝解决了这个问题。

    mmap 零拷贝

    mmap 零拷贝是对零拷贝的改进。当然,若当前主机的 DMA 支持 Gather Copy,mmap
    同样可以实现 Gather Copy DMA 的零拷贝。
    该方式与零拷贝的唯一区别是,应用程序与内核共享了 Kernel buffer。由于是共享,所以应用程序也就可以操作该 buffer 了。当然,应用程序对于 Kernel buffer 的操作,就会引发用户空间与内核空间的相互切换。
    在这里插入图片描述
    该拷贝方式共进行了 4 次用户空间与内核空间的上下文切换,以及 2 次数据拷贝,并且
    整个拷贝过程均没有 CPU 的参与。虽然较之前面的零拷贝增加了两次上下文切换,但应用
    程序可以对数据进行修改了。

    多路复用器 select|poll|epoll

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

    了解多路复用器我们先要了解多进程/多线程连接处理模型
    在这里插入图片描述

    在该模型下,一个用户连接请求会由一个内核进程处理,而一个内核进程会创建一个应用程序进程,即 app 进程来处理该连接请求。应用程序进程在调用 IO 时,采用的是 BIO 通讯方式,即应用程序进程在未获取到 IO 响应之前是处于阻塞态的。
    该模型的优点是,内核进程不存在对app进程的竞争,一个内核进程对应一个app进程。 但,也正因为如此,所以其弊端也就显而易见了。需要创建过多的 app 进程,而该创建过程十分的消耗系统资源。且一个系统的进程数量是有上限的,所以该模型不能处理高并发的情况。

    多路复用连接处理模型

    在这里插入图片描述

    在该模型下,只有一个 app 进程来处理内核进程事务,且 app 进程一次只能处理一个内核进程事务。故这种模型对于内核进程来说,存在对 app 进程的竞争。
    在前面的“多进程/多线程连接处理模型”中我们说过,app 进程只要被创建了就会执行内核进程事务。那么在该模型下,应用程序进程应该执行哪个内核进程事务呢?谁的准备
    就绪了,app 进程就执行哪个。但 app 进程怎么知道哪个内核进程就绪了呢?需要通过“多
    路复用器”来获取各个内核进程的状态信息。那么多路复用器又是怎么获取到内核进程的状
    态信息的呢?不同的多路复用器,其算法不同。常见的有三种:select、poll 与 epoll。
    app 进程在进行 IO 时,其采用的是 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 模式,但其有可能会出现就绪通知被忽视的情况,即连接请求丢失的情况。

    Nginx的并发处理机制

    一般情况下并发处理机制有三种:多进程、多线程,与异步机制。Nginx 对于并发的处理同时采用了三种机制。当然,其异步机制使用的是异步非阻塞方式。
    我们知道 Nginx 的进程分为两类:master 进程与 worker 进程。每个 master 进程可以生成多个 worker 进程,所以其是多进程的。其中master进程负责worker进程的生命周期,接受外部命令,解析perl脚本等。Worker进程负责接收和处理用户请求。
    每个 worker 进程可以同时处理多个用户请求,每个用户请求会由一个线程来处理,所以其是多线程的。
    在这里插入图片描述

    那么,如何解释其“异步非阻塞”并发处理机制呢?
    worker 进程采用的就是 epoll 多路复用机制来对后端服务器进行处理的。当后端服务器返回结果后,后端服务器就会回调 epoll 多路复用器,由多路复用器对相应的 worker 进程进行通知。此时,worker 进程就会挂起当前正在处理的事务,拿 IO 返回结果去响应客户端请求。响应完毕后,会再继续执行挂起的事务。这个过程就是“异步非阻塞”的。

    展开全文
  • 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

    展开全文
  • Goji Goji是一个HTTP请求多路复用器,类似于net / http.ServeMux。 它将传入的请求与已注册的模式列表进行比较,并分派到与第一个匹配的模式相对应的http.Handler。 Goji als Goji Goji是一个HTTP请求多路复用器,...
  • GPIO的寄存器 ...功能选择控制寄存器 GPxMUX 方向控制寄存器 GPxDIR 输入限定控制寄存器 GPxQUAL 数据寄存器: 数据寄存器 GPxDAT 置位寄存器 GPxSET 清除寄存器 GPxClear 取反寄存器 GPxTOGGLE ...
  • Python中的终端多路复用器(如tmux) pip install pymux 问题,疑问,希望,评论,反馈,意见? 请创建一个GitHub问题,我对此表示感谢。 安装 只需使用pip安装pymux : pip install pymux 通过键入pymux启动它...
  • 选择与I/O多路复用

    2020-06-09 00:36:29
    可以将通道注册进选择中,其主要作用就是使用1个线程来对个通道中的已就绪通道进行选择,然后就可以对选择的通道进行数据处理,属于一对的关系,也就是使用1个线程来操作个通道,这种机制在NIO技术中称为“I...
  • 终端 7 是一个具有完整手势支持的终端多路复用器。 使用 Terminal 7,您可以滑动以拆分窗格,用两根手指点击以进行缩放等(手势可查看完整列表)。 tmux 是我们的灵感来源,我们努力支持尽可能多的功能和特性。 ...
  • 多路复用技术

    千次阅读 2018-11-28 12:59:27
    多路复用器:在发送端根据某种规则把多个低带宽的信号复合成一个高带宽的信号。多路分配器:在接收端根据同一规则将高带宽的信号分解成多个低带宽的信号。多路复用器和多路分配器统称为多路器(MUX)。 频分复用 ...
  • 多路复用技术是把多个低速信道组合成一个高速信道的技术,它可以有效的提高数据链路的...多路复用技术的实质是,将一个区域的多个用户数据通过发送多路复用器进行汇集,然后将汇集后的数据通过一个物理线路进行传...
  • 多路复用

    2019-06-06 06:55:36
    3.还是单线程处理多个任务,但是使用多路复用选择 优缺点 1.单线程 优点 简单易用 缺点 阻塞。只有当前面一个任务执行完之后,才能执行下一个任务,cpu被闲置。 2.多线程 优点 解决了单线程的缺点。 缺点 1)需要...
  • BIO,NIO,多路复用器 书中自有黄金屋,书中自有颜如玉 ———————————————————————————————————— 本文在《码出高效:Java开发手册》书本讲解内容的基础上,将和大家一起对JDK1.8...
  • IO多路复用

    2018-08-29 21:14:00
    epoll 是惰性的事件回调:惰性事件回调 是由用户进程 自己调用的,操作系统只起到 通知的作用,目前Linux上效率最高的 IO多路复用 技术。 并发服务实现: 服务端 import socket import selectors#IO多路选择...
  • 实验背景:多路复用器是一个组合电路,它可以从多个输入中选择一个输入,并将信息直接传输到输出。选择哪一条输入线由一组输入变量控制,它们被称为选择输入。通常,2n条输入线要 n 个选择输入,选择输入的位组合...
  • Redis I/O 多路复用

    万次阅读 多人点赞 2019-05-19 19:18:01
    什么 Redis 中要使用 I/O 多路复用这种技术呢? 首先,Redis 是跑在单线程中的,所有的操作都是按照顺序线性执行的,但是由于读写操作等待用户输入或输出都是阻塞的,所以 I/O 操作在一般情况下往往不能直接返回...
  • Gir章节或项目名称多路复用技术/宽带接入技术本次授课类型理论□实验□理实一体□实训□实习班级地点周次星期节次授课进度符合□超前□滞后□符合□超前□滞后□符合□超前□滞后□符合□超前□滞后教学目标1、了解...
  • Java NIO -- 多路复用器、Channle简介、编码实现服务端和客户端通讯前言多路复用Channel代码实现 前言 上一篇BIO说下篇博客要写NIO,真是作呀…学完NIO才知道NIO如此复杂和精妙,直接涉及到操作系统内核… widnows ...
  • 目录2-to-1 multiplexer2-to-...创建一个一位宽的2比1多路复用器。当sel = 0时,选择一个。当sel = 1时,选择b。 提示:三元运算符(cond?iftrue:iffalse)更易于阅读。 solution: module top_module( input a, b,
  • 一般来说,服务器端的I/O主要有两种情况: 一是来自网络的I/O; 二是对文件(设备)的I/O。 首先一个IO操作其实分成了两个步骤:发起IO请求和实际的IO操作,同步IO和异步IO的区别就在于第二个步骤是否阻塞,如果实际的...
  • 核心接口Channel,这里主要讲SelectableChannel。 java.nio.channels.Channel 接口: |--SelectableChannel |--SocketChannel(tcp) |--ServerSocketChannel(tcp) |--DatagramChan...
  • 数字电路实验(二)——三态门与多路复用器

    千次阅读 多人点赞 2019-11-04 10:30:42
    2个vhd文件,用来定义和实现带参数顶层实体文件,一个用来调用顶层实体(示例)和实现该参数下的多路复用器 1个vwf文件,用来进行波形仿真,将验证的波形输入 1、 新建,编写源代码。 (1).选择保存项和芯片类型:...
  • 在数电中,我们都学过数据选择(MUX),常用的有4选1数据选择、8选1数据选择,用于数据的选择切换,从一组输入信号中选出指定的一个送至输出端的组合逻辑电路,输入一输出,常见的形式是这个样子: ...
  • 什么 Redis 中要使用 I/O 多路复用这种技术呢? 首先,Redis 是跑在单线程中的,所有的操作都是按照顺序线性执行的,但是由于读写操作等待用户输入或输出都是阻塞的,所以 I/O 操作在一般情况下往往不能直接返回...
  • TS3DV642是一种12通道1:2或2:1双向多路替代器/多路复用器。TS3DV642接入2.6V至4.5V的电源供电,适用于电池供电。电阻(RON)最小和I / O电容较小,能够实现典型值高达7.5GHz的带宽。该器件可为HDMI和DisplayPort...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 54,351
精华内容 21,740
关键字:

多路复用器的主要功能是什么