Nio得知3——该示范基地:多路复用器模式

Reactor模式和NIO

本文可以看作是Doug Lea Scalable IO in Java一文的翻译。

当前分布式计算 Web Services盛行天下,这些网络服务的底层都离不开对socket的操作。他们都有一个共同的结构:

1. Read request

2. Decode request

3. Process service

4. Encode reply

5. Send reply

经典的网络服务的设计例如以下图。在每一个线程中完毕对数据的处理:

但这样的模式在用户负载添加时,性能将下降非常的快。我们须要又一次寻找一个新的方案,保持数据处理的流畅。非常显然。事件触发机制是最好的解决的方法,当有事件发生时,会触动handler,然后開始数据的处理。

Reactor模式类似于AWT中的Event处理:

Reactor模式參与者

1.Reactor 负责响应IO事件。一旦发生。广播发送给对应的Handler去处理,这类似于AWT的thread

2.Handler 是负责非阻塞行为。类似于AWT ActionListeners;同一时候负责将handlers与event事件绑定,类似于AWT addActionListener

如图:

Java的NIO为reactor模式提供了实现的基础机制,它的Selector当发现某个channel有数据时,会通过SlectorKey来告知我们。在此我们实现事件和handler的绑定。

我们来看看Reactor模式代码:

public class Reactor implements Runnable{

  final Selector selector;

  final ServerSocketChannel serverSocket;

  Reactor(int port) throws IOException {

    selector = Selector.open();

    serverSocket = ServerSocketChannel.open();

    InetSocketAddress address = new InetSocketAddress(InetAddress.getLocalHost(),port);

    serverSocket.socket().bind(address);

    serverSocket.configureBlocking(false);

    //向selector注冊该channel

     SelectionKey sk =serverSocket.register(selector,SelectionKey.OP_ACCEPT);

    logger.debug("-->Start serverSocket.register!");

    //利用sk的attache功能绑定Acceptor 假设有事情。触发Acceptor

    sk.attach(new Acceptor());

    logger.debug("-->attach(new Acceptor()!");

  }

  public void run() { // normally in a new Thread

    try {

    while (!Thread.interrupted())

    {

      selector.select();

      Set selected = selector.selectedKeys();

      Iterator it = selected.iterator();

      //Selector假设发现channel有OP_ACCEPT或READ事件发生,下列遍历就会进行。

      while (it.hasNext())

        //来一个事件 第一次触发一个accepter线程

        //以后触发SocketReadHandler

        dispatch((SelectionKey)(it.next()));

        selected.clear();

      }

    }catch (IOException ex) {

        logger.debug("reactor stop!"+ex);

    }

  }

  //执行Acceptor或SocketReadHandler

  void dispatch(SelectionKey k) {

    Runnable r = (Runnable)(k.attachment());

    if (r != null){

      // r.run();

    }

  }

  class Acceptor implements Runnable { // inner

    public void run() {

    try {

      logger.debug("-->ready for accept!");

      SocketChannel c = serverSocket.accept();

      if (c != null)

        //调用Handler来处理channel

        new SocketReadHandler(selector, c);

      }

    catch(IOException ex) {

      logger.debug("accept stop!"+ex);

    }

    }

  }

}

以上代码中巧妙使用了SocketChannel的attach功能,将Hanlder和可能会发生事件的channel链接在一起。当发生事件时,能够马上触发对应链接的Handler。

再看看Handler代码:

public class SocketReadHandler implements Runnable {

  public static Logger logger = Logger.getLogger(SocketReadHandler.class);

  private Test test=new Test();

  final SocketChannel socket;

  final SelectionKey sk;

   static final int READING = 0, SENDING = 1;

  int state = READING;

  public SocketReadHandler(Selector sel, SocketChannel c)

    throws IOException {

    socket = c;

    socket.configureBlocking(false);

     sk = socket.register(sel, 0);

    //将SelectionKey绑定为本Handler 下一步有事件触发时,将调用本类的run方法。

    //參看dispatch(SelectionKey k)

    sk.attach(this);

    //同一时候将SelectionKey标记为可读,以便读取。

    sk.interestOps(SelectionKey.OP_READ);

    sel.wakeup();

  }

  public void run() {

    try{

    // test.read(socket,input);

      readRequest() ;

    }catch(Exception ex){

    logger.debug("readRequest error"+ex);

    }

  }

/**

* 处理读取data

* @param key

* @throws Exception

*/

private void readRequest() throws Exception {

  ByteBuffer input = ByteBuffer.allocate(1024);

  input.clear();

  try{

    int bytesRead = socket.read(input);

    ......

    //激活线程池 处理这些request

    requestHandle(new Request(socket,btt));

    .....

  }catch(Exception e) {

  }

}

注意在Handler里面又运行了一次attach,这样,覆盖前面的Acceptor,下次该Handler又有READ事件发生时,将直接触发Handler.从而開始了数据的读 处理 写 发出等流程处理。

将数据读出后,能够将这些数据处理线程做成一个线程池,这样,数据读出后,马上扔到线程池中。这样加速处理速度:

更进一步,我们能够使用多个Selector分别处理连接和读事件。

一个高性能的Java网络服务机制就要形成,激动人心的集群并行计算即将实现。

两种I/O多路复用模式:Reactor和Proactor

一般地,I/O多路复用机制都依赖于一个事件多路分离器(Event Demultiplexer)。分离器对象可将来自事件源的I/O事件分离出来,并分发到相应的read/write事件处理器(Event Handler)。开发者预先注冊须要处理的事件及其事件处理器(或回调函数);事件分离器负责将请求事件传递给事件处理器。两个与事件分离器有关的模式是Reactor和Proactor。

Reactor模式採用同步IO。而Proactor採用异步IO。

在Reactor中,事件分离器负责等待文件描写叙述符或socket为读写操作准备就绪,然后将就绪事件传递给相应的处理器,最后由处理器负责完毕实际的读写工作。

而在Proactor模式中,处理器--或者兼任处理器的事件分离器,仅仅负责发起异步读写操作。IO操作本身由操作系统来完毕。

传递给操作系统的參数须要包含用户定义的数据缓冲区地址和数据大小。操作系统才干从中得到写出操作所需数据,或写入从socket读到的数据。事件分离器捕获IO操作完毕事件,然后将事件传递给相应处理器。比方,在windows上,处理器发起一个异步IO操作。再由事件分离器等待IOCompletion事件。典型的异步模式实现,都建立在操作系统支持异步API的基础之上,我们将这样的实现称为“系统级”异步或“真”异步。由于应用程序全然依赖操作系统运行真正的IO工作。

举个样例。将有助于理解Reactor与Proactor二者的差异,以读操作为例(类操作类似)。

在Reactor中实现读

- 注冊读就绪事件和对应的事件处理器

- 事件分离器等待事件

- 事件到来,激活分离器,分离器调用事件相应的处理器。

- 事件处理器完毕实际的读操作,处理读到的数据,注冊新的事件,然后返还控制权。

与例如以下Proactor(真异步)中的读过程比較:

- 处理器发起异步读操作(注意:操作系统必须支持异步IO)。在这样的情况下,处理器无视IO就绪事件,它关注的是完毕事件。

- 事件分离器等待操作完毕事件

- 在分离器等待过程中。操作系统利用并行的内核线程运行实际的读操作。并将结果数据存入用户自己定义缓冲区,最后通知事件分离器读操作完毕。

- 事件分离器呼唤处理器。

- 事件处理器处理用户自己定义缓冲区中的数据,然后启动一个新的异步操作,并将控制权返回事件分离器。

实践现状

由Douglas Schmidt等人开发的开源C++开发框架ACE,提供了大量与平台无关,支持并发的底层类(线程,相互排斥量等),且在高抽象层次上,提供了两组不同的类--ACE Reactor和ACE Proactor的实现。

只是。尽管二者都与平台无关,提供的接口却各异。

ACE Proactor在windows平台上具有更为优异的性能表现。由于windows在操作系统提供了高效的异步API支持(见http://msdn2.microsoft.com/en-us/library/aa365198.aspx)。

然而,并不是全部的操作系统都在系统级大力支持异步。像非常多Unix系统就没做到。因此,在Unix上,选择ACE Reactor解决方式可能更好。但这样一来,为了获得最好的性能,网络应用的开发者必须为不同的操作系统维护多份代码:windows上以ACE Proactor为基础。而Unix系统上则採用ACE Reactor解决方式。

改进方案

  在这部分,我们将尝试应对为Proactor和Reactor模式建立可移植框架的挑战。在改进方案中。我们将Reactor原来位于事件处理器内的read/write操作移至分离器(最好还是将这个思路称为“模拟异步”),以此寻求将Reactor多路同步IO转化为模拟异步IO。以读操作为样例,改进步骤例如以下:

- 注冊读就绪事件及其处理器,并为分离器提供数据缓冲区地址,须要读取数据量等信息。

- 分离器等待事件(如在select()上等待)

- 事件到来。激活分离器。分离器运行一个非堵塞读操作(它有完毕这个操作所需的所有信息),最后调用相应处理器。

- 事件处理器处理用户自己定义缓冲区的数据,注冊新的事件(当然相同要给出数据缓冲区地址,须要读取的数据量等信息),最后将控制权返还分离器。

如我们所见,通过对多路IO模式功能结构的改造,可将Reactor转化为Proactor模式。改造前后,模型实际完毕的工作量没有添加,仅仅只是參与者间对工作职责稍加调换。没有工作量的改变,自然不会造成性能的削弱。对例如以下各步骤的比較,能够证明工作量的恒定:

标准/典型的Reactor:

- 步骤1:等待事件到来(Reactor负责)

- 步骤2:将读就绪事件分发给用户定义的处理器(Reactor负责)

- 步骤3:读数据(用户处理器负责)

- 步骤4:处理数据(用户处理器负责)

改进实现的模拟Proactor:

- 步骤1:等待事件到来(Proactor负责)

- 步骤2:得到读就绪事件,运行读数据(如今由Proactor负责)

- 步骤3:将读完毕事件分发给用户处理器(Proactor负责)

- 步骤4:处理数据(用户处理器负责)

对于不提供异步IO API的操作系统来说。这样的办法能够隐藏socket API的交互细节,从而对外暴露一个完整的异步接口。

借此,我们就能够进一步构建全然可移植的,平台无关的。有通用对外接口的解决方式。

Scalable IO in Java原文

NIO原理与应用

用NIO开发一个高性能聊天系统

Socket打造高性能server

很多其它NIO专题系列讨论....

server后端性能大比拼

事件驱动编程

并发模型

Rx (Reactive Extensions)介绍

Reactive编程

EDA

2002年大神住址:http://www.jdon.com/concurrent/reactor.htm

版权声明:本文博主原创文章,博客,未经同意不得转载。

时间: 2024-10-13 13:34:32

Nio得知3——该示范基地:多路复用器模式的相关文章

从Jetty、Tomcat和Mina中提炼NIO构架网络服务器的经典模式

转自:http://blog.csdn.net/cutesource/article/details/6192016 http://blog.csdn.net/cutesource/article/details/6192145 http://blog.csdn.net/cutesource/article/details/6192163 如何正确使用NIO来构架网络服务器一直是最近思考的一个问题,于是乎分析了一下Jetty.Tomcat和Mina有关NIO的源码,发现大伙都基于类似的方式,我感

Tomcat Connector(BIO, NIO, APR)三种运行模式(转)

Tomcat支持三种接收请求的处理方式:BIO.NIO.APR . BIO 阻塞式I/O操作即使用的是传统 I/O操作,Tomcat7以下版本默认情况下是以BIO模式运行的,由于每个请求都要创建一个线程来处理,线程开销较大,不能处理高并发的场景,在三种模式中性能也最低. 配置如下(tomcat安装目录下的/conf/server.xml): <Connector port="8080" protocol="HTTP/1.1" connectionTimeout

Java进阶知识点5:服务端高并发的基石 - NIO与Reactor模式以及AIO与Proactor模式

一.背景 要提升服务器的并发处理能力,通常有两大方向的思路. 1.系统架构层面.比如负载均衡.多级缓存.单元化部署等等. 2.单节点优化层面.比如修复代码级别的性能Bug.JVM参数调优.IO优化等等. 一般来说,系统架构的合理程度,决定了系统在整体性能上的伸缩性(高伸缩性,简而言之就是可以很任性,性能不行就加机器,加到性能足够为止):而单节点在性能上的优化程度,决定了单个请求的时延,以及要达到期望的性能,所需集群规模的大小.两者双管齐下,才能快速构建出性能良好的系统. 今天,我们就聊聊在单节点

Java NIO中的Glob模式详解

Java NIO中的Glob模式详解 作者:chszs,转载需注明.博客主页:http://blog.csdn.net/chszs 一.什么是Glob? 在编程设计中,Glob是一种模式,它使用通配符来指定文件名.例如:.java就是一个简单的Glob,它指定了所有扩展名为"java"的文件.Glob模式中广泛使用了两个通配符""和"?".其中星号表示"任意的字符或字符组成字符串",而问号则表示"任意单个字符&quo

Netty5序章之BIO NIO AIO演变

Netty5序章之BIO NIO AIO演变 Netty是一个提供异步事件驱动的网络应用框架,用以快速开发高性能.高可靠的网络服务器和客户端程序.Netty简化了网络程序的开发,是很多框架和公司都在使用的技术.更是面试的加分项.Netty并非横空出世,它是在BIO,NIO,AIO演变中的产物,是一种NIO框架.而BIO,NIO,AIO更是笔试中要考,面试中要问的技术.也是一个很好的加分项,加分就是加工资,你还在等什么?本章带你细细品味三者的不同! 流程图: 技术:BIO,NIO,AIO 说明:g

漫谈Java IO之 NIO那些事儿

前面一篇中已经介绍了基本IO的使用以及最简单的阻塞服务器的例子,本篇就来介绍下NIO的相关内容,前面的分享可以参考目录: 网络IO的基本知识与概念 普通IO以及BIO服务器 NIO的使用与服务器Hello world Netty入门与服务器Hello world Netty深入浅出 NIO,也叫做new-IO或者non-blocking-IO,就暂且理解为非阻塞IO吧. 为什么选择NIO 那么NIO相对于IO来说,有什么优势呢?总结来说: IO是面向流的,数据只能从一端读取到另一端,不能随意读写

开启Tomcat APR运行模式,优化并发性能

Tomcat支持三种接收请求的处理方式:BIO.NIO.APR 1>.BIO模式:阻塞式I/O操作,表示Tomcat使用的是传统JavaI/O操作(即Java.io包及其子包).Tomcat7以下版本默认情况下是以bio模式运行的,由于每个请求都要创建一个线程来处理,线程开销较大,不能处理高并发的场景,在三种模式中性能也最低.启动tomcat看到如下日志,表示使用的是BIO模式:  2>.NIO模式:是javaSE 1.4及后续版本提供的一种新的I/O操作方式(即java.nio包及其子包).

java 简单工厂模式实现

简单工厂模式:也可以叫做静态工厂方法,属于类创建型模式,根据不同的参数,返回不同的类实现. 主要包含了三个角色: A.抽象产品角色 一般用接口 或是 抽象类实现 B.具体的产品角色,具体的类的实现 C 工厂角色,实例的产生,根据不同参数,返回不同的类的实现. 结合枚举类的实现代码如下: 1.抽象的产品角色: package factory.simple; /** * 抽象产品角色 交通工具接口 * * @author lilin * */ public interface Car { /** *

java io 网络编程 高性能NIO

很久没写了,一是觉得这后台不怎么方便,二是 写的时候突然觉得没兴趣了. 还好,今天突然想记一下,那就随便写吧.  1.一开始还是放几个连接.  什么是 同步,异步,阻塞,非阻塞 : http://blog.csdn.net/historyasamirror/article/details/5778378        从Jetty.Tomcat和Mina中提炼NIO构架网络服务器的经典模式:http://blog.csdn.net/cutesource/article/details/61920