Java设计模式之Java NIO 反应器(Reactor)模式

从JDK1.4开始,Java增加了新的IO模式—-nio(new IO),nio在底层采用了新的处理方式,极大的提高了IO的效率.我们使用的Socket也属于IO的一种,nio提供了相应的工具,ServerSocketChannel和SocketChannel,它们分别对应原来的ServerSocket和Socktet.

1.简单场景

记得在上大学的时候,有个同学批发了很多方便面,电话卡和别的日用品在宿舍里卖,而且提供很多送货上门的服务,只要公寓里又打电话买东西,他就送过去,并收钱,最后返回,然后等待下一个电话,这种模式相当于普通的socket处理请求的模式.如果请求不是很多,这种模式是没有问题的.当请求很多的时候这种模式就应付不过来了,如果现在的电商网站也采用这种配送方式,效果大家可想而知,所以电商网站必须采用新的配置模式,这就是现在的快递模式(也许以后还会有更好的模式).快递并不会一件一件的送,而是将很多货物一起拿过去送,而且在中转站都有专门的分拣员负责按配送范围把货物分为不同的送货员,这样效率就高了很多.这种模式就相当于NioSocket的处理模式,Buffer就是所要送的货物,Channel就是送货员(或者开往某个区域的配货车),Selector就是中转站的分拣员.

2.NIO介绍

NioSocket使用中首先创建了ServerSocketChannel,然后注册Selector,接下来就可以用Selector接收请求并处理了.

ServerSocketChannel可以使用自己的静态工厂方法open创建.每个ServerSocketChannel对应一个ServerSocket,可以调用其socket方法来获取,不过如果直接使用获取到ServerSocket来监听请求,那还是原来的处理模式,一般使用获取到的ServerSocket来绑定端口.ServerSocketChannel可以通过configureBlocking方法来设置是否采用阻塞模式,如果要采用非阻塞模式可以用configureBlocking(false)来设置,设置了非阻塞模式之后就可以调用register方法注册Selector来使用了(阻塞模式下不可以使用Selector).

Selector可以通过其静态工厂方法open创建,创建后通过Channel的register方法注册到ServerSocketChannel或者SocketChannel上,注册完之后Selector就可以通过select方法来等待请求,select方法有一个long类型的参数,代表最长等待时间,如果在这段时间里接收到了相应操作的请求则返回可以处理的请求的数量,否则在超时后返回0,程序继续往下走,如果传入的参数为0或者调用无参数的重载方法,select方法会采用阻塞模式直到有相应操作的请求出现.当接收到请求后Selector调用selectedKeys方法返回SelectionKey的集合.

SelectionKey保存了处理当前请求的Channel和Selector,并且提供了不同的操作类型.Channel在注册Selector的时候可以通过register的第二个参数来选择特定的操作,这里的操作就是在SelectionKey中定义的,一种有四种:

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

他们分别表示接受请求操作\连接操作\读操作和写操作,只有在register方法中注册了响应的操作Selector才会关心相应类型操作的请求.

Channel和Selector并没有谁属于谁的关系,就好像一个分拣员可以为多个地区分拣货物一样而每个地区也可以有多个分拣员来分拣一样,他们就好像数据库里面的多对对的关系,不过Selector这个分拣员分拣的更细,他可以按照不同的类型来分拣,分拣后的结果保存在SelectorKey中,也可以分别通过SelectionKey的channel方法和selector方法来获取对应的Channel和Selector,而且也可以通过isAcceptable,isConnectable,siReadable和isWritable方法来判断是什么类型的操作.

3.NIO_Demo

NioSocket中服务端的处理过程可以分为5步:

  1. 创建ServerSocketChannel并设置响应参数
  2. 创建Selector并注册到ServerSocketChannel上
  3. 调用Selector的select方法等待请求
  4. Selector接收到请求后使用selectedKeys返回SelectionKey集合.
  5. 使用SelectionKey获取到Channel和Selector和操作类型并进行具体操作.
package designpattern.nio;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.util.Iterator;

public class NIOServer {

    public static void main(String[] args) throws IOException {
        // 创建ServerSocketChannel,监听8080端口
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.socket().bind(new InetSocketAddress(8080));

        // 设置为非阻塞模式
        serverSocketChannel.configureBlocking(false);

        // 为serverSocketChannel注册选择器
        Selector selector = Selector.open();
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        // 创建处理器
        Handler handler = new Handler(1024);
        while (true) {
            // 等待请求,每次等待阻塞3s,超过3s后线程继续向下运行,如果传入0或者不传入参数将一直阻塞
            if (selector.select(3000) == 0) {
                System.out.println("等待请求超时....");
                continue;
            }
            System.out.println("处理请求....");

            // 获取待处理的SelectionKey
            Iterator keyIter = selector.selectedKeys().iterator();
            while (keyIter.hasNext()) {
                SelectionKey key = keyIter.next();
                try {
                    // 接收到连接请求时
                    if (key.isAcceptable()) {
                        handler.handlerAccept(key);
                    }

                    // 接收到读数据时
                    if (key.isReadable()) {
                        handler.handleRead(key);
                    }

                } catch (Exception e) {
                    keyIter.remove();
                    continue;
                }

                // 处理完毕,从待处理的SelectionKey迭代器中移除当前所使用的key
                keyIter.remove();
            }
        }
    }
}

package designpattern.nio;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;

public class Handler {

    private int bufferSize = 1024;
    private String localCharset = "UTF-8";

    public Handler() {
        super();
    }

    public Handler(int bufferSize) {
        this(bufferSize, null);
    }

    public Handler(String localCharset) {
        this(-1, localCharset);
    }

    public Handler(int bufferSize, String localCharset) {
        if (bufferSize > 0) {
            this.bufferSize = bufferSize;
        }
        if (localCharset != null) {
            this.localCharset = localCharset;
        }
    }

    public void handlerAccept(SelectionKey key) throws IOException {
        SocketChannel socketChannel = ((ServerSocketChannel) key.channel()).accept();
        socketChannel.configureBlocking(false);
        socketChannel.register(key.selector(), SelectionKey.OP_READ, ByteBuffer.allocate(bufferSize));
    }

    public void handleRead(SelectionKey key) throws IOException {
        // 获取channel
        SocketChannel socketChannel = (SocketChannel) key.channel();

        // 获取buffer并重置
        ByteBuffer buffer = (ByteBuffer) key.attachment();
        buffer.clear();

        // 没有读到内容则关闭
        if (socketChannel.read(buffer) == -1) {
            socketChannel.close();
        } else {
            // 将buffer转换为读状态
            buffer.flip();

            // 将buffer中接收到的值按localCharset格式编码后保存到receivedString
            String receivedString = Charset.forName(localCharset).newDecoder().decode(buffer).toString();
            System.out.println("received from client: " + receivedString);

            // 返回数据给客户端
            String sendString = "received data: " + receivedString;
            buffer = ByteBuffer.wrap(sendString.getBytes(localCharset));
            socketChannel.write(buffer);

            // 关闭socket
            socketChannel.close();
        }

    }

}

本机运行结果:

Java设计模式之Java NIO 反应器(Reactor)模式_第1张图片

GitHub代码地址:

https://github.com/DemoTransfer/webmanager/tree/master/datamanager/src/main/java/designpattern/reactor

你可能感兴趣的:(设计模式相关技术)