JAVA IO模型演进及Reactor模式

一、传统BIO模型

在基于传统同步阻塞模型中:ServerSocket负责绑定IP地址,启动监听端口;Socket负责发起连接操作。连接成功后,双方通过输入输出流进行同步阻塞式通信。

JAVA IO模型演进及Reactor模式_第1张图片

JAVA IO模型演进及Reactor模式_第2张图片

通信过程:

1)服务端通常由一个独立的Acceptor线程负责监听客户端的连接;

2)Acceptor监听到客户端的连接请求后,为每个客户端创建一个新的线程进行链路处理;

3)链路处理线程完成客户端请求的处理后,通过输出流返回应答给客户端,然后线程销毁。

模型缺点:

1)服务端线程个数与客户端并发访问连接数是1:1的关系;

2)随着客户端并发访问量增大,服务端线程个数线性膨胀,系统性能急剧下降。


二、优化后的BIO模型

服务端通过线程池来处理多个客户端的接入请求,通过线程池约束及调配服务端线程资源。形成客户端个数M:服务端线程池最大线程数N的比例关系。

JAVA IO模型演进及Reactor模式_第3张图片

通信过程:

1)当有新的客户端接入时,将客户端Socket封装成一个Task投递到服务端任务队列;

2)服务端任务线程池中的多个线程对任务队列中的Task进行并行处理;

3)任务线程处理完当前Task后,继续从任务队列中取新的Task进行处理。

模型缺点:

1)BIO的读和写操作都是同步阻塞的,阻塞时间取决于对端IO线程的处理速度和网络IO的传输速度,可靠性差;

2)当线程池中所有线程都因对端IO线程处理速度慢导致阻塞时,所有后续接入的客户端连接请求都将在任务队列中排队阻塞堆积;

3)任务队列堆积满后,新的客户端连接请求将被服务端单线程Acceptor阻塞或拒绝,客户端会发生大量连接失败和连接超时。


三、NIO模型

多路复用器Selector是NIO模型的基础,一个多路复用器Selector可以同时轮询多个注册在它上面的Channel,服务端只需要一个线程负责Selector的轮询,就可以接入成千上万的客户端连接。

JAVA IO模型演进及Reactor模式_第4张图片

JAVA IO模型演进及Reactor模式_第5张图片


模型优点:

1)NIO中Channel是全双工的,Channel比流可以更好地映射底层操作系统的API(UNIX网络编程模型中,底层操作系统的通道都是全双工的,同时支持读写操作);

2)客户端发起的连接操作是异步的,不需要像之前的客户端那样被同步阻塞;

3)JDK的Selector在Linux等主流操作系统上通过epoll实现,它没有连接句柄数的限制,使得一个Selector线程可以同时处理成千上万个客户端连接,而且性能不会随客户端连接的增加而线性下降,适合做高性能高负载的网络服务器方案。


四、AIO模型

NIO2.0的异步套接字通道是真正的异步非阻塞IO,对应于UNIX网络编程中的事件驱动IO(AIO)。

它不需要通过多路复用器Selector对注册的通道进行轮询操作即可实现异步读写。通过事件驱动+回调函数的方式完成。

JAVA IO模型演进及Reactor模式_第6张图片

五、几种IO模型的功能特性对比


JAVA IO模型演进及Reactor模式_第7张图片

六、NIO模型+Reactor模式的网络服务端

1、Reactor模式思想:分而治之+事件驱动

1)分而治之

一个connection里完整的网络处理过程一般分为accept、read、decode、process、encode、send这几步。

Reactor模式将每个步骤映射为一个Task,服务端线程执行的最小逻辑单元不再是一次完整的网络请求,而是Task,且采用非阻塞方式执行。

2)事件驱动

每个Task对应一个特定事件,当Task准备就绪时,对应的事件通知就会发出。

Reactor收到事件通知后,分发给绑定了对应事件的Handler执行Task。

2、单线程版本Reactor模式

1)结构图

JAVA IO模型演进及Reactor模式_第8张图片

Reactor:负责响应事件,将事件分发给绑定了该事件的Handler处理;

Handler:事件处理器,绑定了某类事件,负责执行对应事件的Task对事件进行处理;

Acceptor:Handler的一种,绑定了connect事件。当客户端发起connect请求时,Reactor会将accept事件分发给Acceptor处理。

2)客户端连接服务端

JAVA IO模型演进及Reactor模式_第9张图片

a、服务端将绑定了accept事件的Acceptor注册到Reactor中,准备accept新的connection;

b、服务端循环执行Reactor的多路复用器Selector的select功能,监听就绪事件;

c、客户端connect服务器;

d、Reactor的多路复用器监听到accept事件,分发给Acceptor处理事件;

e、Acceptor执行事件Task,接收建立与客户端的连接,并创建一个Handler用于执行该连接的后续请求事件;

f、Handler绑定连接的read事件,并将自己注册到Reactor的Selector中监听。

3)服务端处理客户端请求

JAVA IO模型演进及Reactor模式_第10张图片

a、客户端发送请求;

b、客户端请求到达服务端时,Reactor监听到read事件,将事件分发给对应Handler处理;

c、Handler处理read事件,异步读取客户端请求数据;

d、Handler解析(decode)客户端请求数据;

e、Handler处理(process)客户端请求;

f、Handler重新绑定write事件;

g、当连接可以开始write时,Reactor监听到write事件,将事件分发给Handler处理;

h、Handler处理write事件,异步写出服务端响应数据。


4)模型优缺点

a、单线程版本Reactor模型优点是不需要做并发控制,代码实现简单清晰;

b、缺点是不能利用多核CPU,一个线程需要执行处理所有的accept、read、decode、process、encode、send事件,如果其中decode、process、encode事件的处理很耗时,则服务端无法及时响应其他客户端的请求事件。


3、Reactor模式的其他版本

1)Worker threads

JAVA IO模型演进及Reactor模式_第11张图片

a、使用线程池执行数据的具体处理过程decode、process、encode,提高数据处理过程的响应速度;

b、Reactor所在单线程只需要专心监听处理客户端请求事件accept、read、write;

c、因为Reactor仍是单线程,无法并行响应多个客户端的请求事件(比如同一时刻只能read一个客户端的请求数据)。


2)Multiple reactor threads

JAVA IO模型演进及Reactor模式_第12张图片

a、采用多个Reactor,每个Reactor在自己单独线程中执行,可以并行响应多个客户端的请求事件;

b、Netty采用类似这种模式,boss线程池就是多个mainReactor,worker线程池就是多个subReactor。


注:以上内容参照《Netty权威指南》和网络文章

4、NIO单线程Reactor模式示例代码

package com.zhangyiwen.study.nio.reactor_demo;

import java.io.IOException;
import java.nio.channels.SelectionKey;

/**
 * Created by zhangyiwen on 16/11/8.
 */
public interface Handler {

    void handle(SelectionKey sk) throws IOException;
}

package com.zhangyiwen.study.nio.reactor_demo;

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

/**
 * Created by zhangyiwen on 16/11/7.
 */
public class EventHandler implements Handler{

    SocketChannel socketChannel;
    SelectionKey selectionKey;

    ByteBuffer readBuffer = ByteBuffer.allocate(MAXIN);
    ByteBuffer outBuffer = ByteBuffer.allocate(MAXOUT);

    static final int MAXIN = 256*1024;
    static final int MAXOUT = 256*1024;
    static final Charset charset = Charset.forName("UTF-8");

    EventHandler(SocketChannel socketChannel, Selector selector) throws IOException {
        this.socketChannel = socketChannel;
        // 用selector注册套接字,并返回对应的SelectionKey,同时设置Key的interest set为监听该连接上得read事件
        this.selectionKey = socketChannel.register(selector, SelectionKey.OP_READ);
        // 绑定handler
        this.selectionKey.attach(this);
    }

    @Override
    public void handle(SelectionKey sk) throws IOException{
        if (sk.isReadable()) {
            System.out.println("[event]read");
            read();
        } else if (sk.isWritable()) {
            System.out.println("[event]write");
            write();
        }
    }

    /**
     * 处理read事件
     * @throws IOException
     */
    private void read() throws IOException{
        // 读取数据
        readBuffer.clear();
        StringBuilder content = new StringBuilder();
        int readNum = socketChannel.read(readBuffer);
        if(readNum==0){
            return;
        }else if(readNum<0){
            throw new IOException("exception.");
        }else {
            readBuffer.flip();
            content.append(charset.decode(readBuffer)); //decode
        }
        while(socketChannel.read(readBuffer) > 0)
        {
            readBuffer.flip();
            content.append(charset.decode(readBuffer)); //decode
        }
        // 处理数据
        process(content.toString());
        //
        selectionKey.interestOps(SelectionKey.OP_WRITE);
    }

    /**
     * 处理客户端请求数据
     * @param content
     */
    private void process(String content) throws IOException{
        System.out.println("[receive from client] -> client:" + socketChannel.getRemoteAddress() + ", content: " + content);
        outBuffer = ByteBuffer.wrap(content.toUpperCase().getBytes());
    }

    /**
     * 处理write事件
     * @throws IOException
     */
    private void write() throws IOException {
        // 写数据
        socketChannel.write(outBuffer);
        if (outBuffer.remaining() > 0) {
            return;
        }
        //
        selectionKey.interestOps(SelectionKey.OP_READ);
    }
}

package com.zhangyiwen.study.nio.reactor_demo;

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

/**
 * Created by zhangyiwen on 16/11/8.
 */
public class Acceptor implements Handler{

    static final Charset charset = Charset.forName("UTF-8");

    private ServerSocketChannel serverChannel;

    private Selector selector;

    public Acceptor(ServerSocketChannel serverChannel, Selector selector) {
        this.serverChannel = serverChannel;
        this.selector = selector;
    }

    @Override
    public void handle(SelectionKey sk) throws IOException{
        System.out.println("[event]connect");
        // 建立连接
        SocketChannel socketChannel = serverChannel.accept();
        System.out.println("[new client connected] client:" + socketChannel.getRemoteAddress());
        // 设置为非阻塞
        socketChannel.configureBlocking(false);
        // 创建Handler,专门处理该连接后续发生的OP_READ和OP_WRITE事件
        new EventHandler(socketChannel, this.selector);
        // 发送欢迎语
        socketChannel.write(charset.encode("welcome my client."));
    }

}

package com.zhangyiwen.study.nio.reactor_demo;

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

/**
 * Created by zhangyiwen on 16/11/7.
 * 服务端,会先发欢迎语,后续会将客户端发来的消息转成大写后返回
 */
public class NioServer {

    private InetAddress hostAddress;

    private int port;

    private Selector selector;

    private ServerSocketChannel serverChannel;


    public NioServer(InetAddress hostAddress, int port) throws IOException {
        this.hostAddress = hostAddress;
        this.port = port;

        //初始化selector.绑定服务端监听套接字,感兴趣事件,对应的handler
        initSelector();
    }

    public static void main(String[] args) {
        try {
            // 启动服务器
            new NioServer(null, 9090).start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 初始化selector,绑定服务端监听套接字、感兴趣事件及对应的handler
     * @return
     * @throws IOException
     */
    private void initSelector()throws IOException {
        // 创建一个selector
        selector = SelectorProvider.provider().openSelector();
        // 创建并打开ServerSocketChannel
        serverChannel = ServerSocketChannel.open();
        // 设置为非阻塞
        serverChannel.configureBlocking(false);
        // 绑定端口
        serverChannel.socket().bind(new InetSocketAddress(hostAddress, port));
        // 用selector注册套接字,并返回对应的SelectionKey,同时设置Key的interest set为监听客户端连接事件
        SelectionKey selectionKey = serverChannel.register(selector, SelectionKey.OP_ACCEPT);
        // 绑定handler
        selectionKey.attach(new Acceptor(serverChannel,selector));
    }

    public void start() {
        while (true) {
            /*
             * 选择事件已经ready的selectionKey,该方法是阻塞的.
             * 只有当至少存在selectionKey,或者wakeup方法被调用,或者当前线程被中断,才会返回.
             */
            try {
                selector.select();
            } catch (IOException e) {
                e.printStackTrace();
            }

            // 循环处理每一个事件
            Iterator items = selector.selectedKeys().iterator();
            while (items.hasNext()) {
                SelectionKey key = items.next();
                items.remove();
                if (!key.isValid()) {
                    continue;
                }
                // 事件处理分发
                dispatch(key);
            }
        }
    }

    /**
     * 事件处理分发
     * @param sk 已经ready的selectionKey
     */
    private void dispatch(SelectionKey sk){
        // 获取绑定的handler
        Handler handler = (Handler) sk.attachment();
        try {
            if (handler != null) {
                handler.handle(sk);
            }
        } catch (IOException e) {
            e.printStackTrace();
            sk.channel();
            try {
                if(sk.channel()!=null){
                    sk.channel().close();
                }
            } catch (IOException e1) {
                e1.printStackTrace();
            }
        }
    }

}

package com.zhangyiwen.study.nio.reactor_demo;

import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.channels.spi.SelectorProvider;
import java.nio.charset.Charset;
import java.util.Iterator;
import java.util.Scanner;

/**
 * Created by zhangyiwen on 16/11/8.
 * 手动输入客户端
 */
public class NioClient {

    private InetAddress hostAddress;
    private int port;

    private Selector selector;
    private SocketChannel socketChannel;

    private ByteBuffer readBuffer = ByteBuffer.allocate(8192);
    static final Charset charset = Charset.forName("UTF-8");

    public NioClient(InetAddress hostAddress, int port) throws IOException {
        this.hostAddress = hostAddress;
        this.port = port;
        initSelector();
    }

    public static void main(String[] args) {
        try {
            new NioClient(InetAddress.getByName("localhost"), 9090);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private void initSelector() throws IOException {
        // 创建一个selector
        selector = SelectorProvider.provider().openSelector();
        // 打开SocketChannel
        socketChannel = SocketChannel.open();
        // 设置为非阻塞
        socketChannel.configureBlocking(false);
        // 连接指定IP和端口的地址
        socketChannel.connect(new InetSocketAddress(this.hostAddress, this.port));
        // 用selector注册套接字,并返回对应的SelectionKey,同时设置Key的interest set为监听服务端已建立连接的事件
        socketChannel.register(selector, SelectionKey.OP_CONNECT);

        // 开启新线程执行
        new Thread(new ClientThread()).start();

        //在主线程中 从键盘读取数据输入到服务器端
        Scanner scan = new Scanner(System.in);
        while(scan.hasNextLine())
        {
            String line = scan.nextLine();
            if("".equals(line)) continue; //不允许发空消息
            socketChannel.write(charset.encode(line));//sc既能写也能读,这边是写
        }
    }

    private class ClientThread implements Runnable {
        @Override
        public void run() {
            while (true) {
                try {
                    selector.select();
                } catch (Exception e) {
                    e.printStackTrace();
                }

                Iterator selectedKeys = selector.selectedKeys().iterator();
                while (selectedKeys.hasNext()) {
                    SelectionKey key = (SelectionKey) selectedKeys.next();
                    selectedKeys.remove();
                    if (!key.isValid()) {
                        continue;
                    }
                    dispatch(key);
                }
            }
        }

        /**
         * 事件处理分发
         * @param key 已经ready的selectionKey
         */
        private void dispatch(SelectionKey key){
            try {
                if (key.isConnectable()) {
                    System.out.println("[event]connect.");
                    finishConnection(key);
                } else if (key.isReadable()) {
                    System.out.println("[event]read");
                    read(key);
                }
            } catch (IOException e) {
                e.printStackTrace();
                key.channel();
                try {
                    if(key.channel()!=null){
                        key.channel().close();
                    }
                } catch (IOException e1) {
                    e1.printStackTrace();
                }
            }
        }
    }

    /**
     * 完成与服务端连接
     * @param key
     * @throws IOException
     */
    private void finishConnection(SelectionKey key) throws IOException {
        SocketChannel socketChannel = (SocketChannel) key.channel();
        // 判断连接是否建立成功,不成功会抛异常
        socketChannel.finishConnect();
        // 设置Key的interest set为OP_WRITE事件
        key.interestOps(SelectionKey.OP_READ);
    }

    /**
     * 处理read
     * @param key
     * @throws IOException
     */
    private void read(SelectionKey key) throws IOException {
        // 读取数据
        SocketChannel socketChannel = (SocketChannel) key.channel();
        readBuffer.clear();
        StringBuilder content = new StringBuilder();
        int readNum = socketChannel.read(readBuffer);
        if(readNum==0){
            return;
        }else if(readNum<0){
            throw new IOException("exception.");
        }else {
            readBuffer.flip();
            content.append(charset.decode(readBuffer)); //decode
        }
        while(socketChannel.read(readBuffer) > 0)
        {
            readBuffer.flip();
            content.append(charset.decode(readBuffer));
        }
        // 处理数据
        process(content.toString(), key);
        // 设置Key的interest set为OP_READ事件
//        key.interestOps(SelectionKey.OP_READ);
    }

    /**
     * 处理服务端响应数据
     * @param content
     */
    private void process(String content,SelectionKey key) {
        System.out.println("[Client receive from server] -> content: " + content);
    }


}



你可能感兴趣的:(计算机网络)