JAVA IO模型演进及Reactor模式

来源:互联网 发布:超级基因优化液无弹窗 编辑:程序博客网 时间:2024/06/14 23:43

一、传统BIO模型

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



通信过程:

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

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

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

模型缺点:

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

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


二、优化后的BIO模型

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


通信过程:

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

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

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

模型缺点:

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

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

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


三、NIO模型

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




模型优点:

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

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

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


四、AIO模型

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

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


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



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

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

1)分而治之

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

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

2)事件驱动

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

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

2、单线程版本Reactor模式

1)结构图


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

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

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

2)客户端连接服务端


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)服务端处理客户端请求


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


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

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

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


2)Multiple reactor threads


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<SelectionKey> 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);    }}



0 0
原创粉丝点击