其他分享
首页 > 其他分享> > netty前传:从传统I/O到Reactor的三种模型

netty前传:从传统I/O到Reactor的三种模型

作者:互联网

netty前传:从传统I/O到Reactor的三种模型

 

对最近学习netty的一些知识的一个汇总,对前人知识总结的一些个人理解。

 

一. 传统阻塞I/O

 传统的阻塞I/O的网络服务端架构如下:

 

 

 这里衍生出一些问题:

1. 我们在处理客户端读到的数据时,会发生阻塞。

2. 虽然,我们可以使用多线程的方式,来避免第一个问题,但每次收到一个client端的连接请求,就创建一个线程,如果client过多,那必然导致线程也很多,线程的创建、切换、销毁,消耗都是很大的。

因此,这种模式,不能适用于一些高并发的场景,只适用于一些连接不多,且固定的场景。

 

二. NIO

正由于传统I/O的缺点,引申出了NIO,NIO的大致架构如下:

 

 

详解:

1. 使用ServerSocketChannel的对象,监听对应端口,并注册到Selector中,用于监听OP_ACCEPT事件。

2. Selector获取到Client端连接请求,这个过程不阻塞,没有请求到达的时候,selector.select(time)可以直接返回,然后程序可以去干自己的事

3. 获取到Client请求后,通过ServerSocketChannel对象,去生成一个SocketChannel对象,并将它注册到Selector中,用于监听OP_READ事件。

4. 同样,Selector监听Client发消息的读事件,此过程也不阻塞。收到消息后,server端读数据直接从Buffer中获取,不需要像I/O那样,使用read(),在那阻塞等待client端的数据。

5. server端拿到数据后,就可以去处理这些数据了,处理完后,Selector继续监听新的事件。

 

Server端的代码也很简单,如下

public class Test {
    public static void main(String[] args) throws Exception {
        new Test().server();
    }

    public void server() throws Exception {
        Selector selector = Selector.open();

        ServerSocketChannel server = ServerSocketChannel.open();
        server.bind(new InetSocketAddress(5678));
        server.configureBlocking(false);
        server.register(selector, SelectionKey.OP_ACCEPT);

        while (true) {
            int keys = selector.select();
            if (keys <= 0) {
                continue;
            }

            Set slKeys = selector.selectedKeys();
            Iterator iterator = slKeys.iterator();
            while (iterator.hasNext()) {
                SelectionKey key = (SelectionKey) iterator.next();
                deal(key, server, selector);
                iterator.remove();
            }
        }
    }

    public void deal(SelectionKey key, ServerSocketChannel server, Selector selector) throws IOException {
        if (key.isAcceptable()) {
            SocketChannel channel = server.accept();
            channel.configureBlocking(false);
            channel.register(selector, SelectionKey.OP_READ);
        } else if (key.isReadable()) {

            SocketChannel channel = (SocketChannel) key.channel();

            //实际业务数据操作

        }
    }
}

 

这种方式,解决了I/O的几个问题

1. 不需要每个Client连接就创建新的线程,一个selector,就可以轮询的去获取到client的连接事件,且不阻塞。

2. 数据读取不需要阻塞,有一个Buffer和SocketChannel绑定了,OP_READ事件到达的时候,直接从Buffer中取数据即可。

但是,NIO依旧存在一些问题:

1. 编码过于繁琐

2. selector还是有瓶颈,如果Client数量过多,那每次拿到请求事件,然后再去把SocketChannel注册到selector,然后selector得到读事件,然后再去读取数据,处理业务,这个过程是同步执行的,如果数据量大,处理业务复杂,那还是会影响到其他的client端

因此这种方式适用的场景依旧是,client不是特别多,并且业务逻辑,数据处理相对简单的场景,比如聊天系统等。

针对NIO的一些问题,就产生了netty。针对netty,又有必要介绍reactor的三种线程模型

 

三. Reactor的三种线程模型

 1. 单Reactor单线程模型

 

 

 

 

 一个线程采用selecor执行所有的accept请求、处理所有业务逻辑。

前面介绍的NIO用的就是这种

缺点:1)单个线程不能利用多核cpu优势  

      2)不适用高并发场景  

           3)如果线程异常或者有死循环,会导致整个系统通信模块不可用。

 

 2. 单Reactor多线程模型

 

 

 

 

 一个线程通过selector不断轮询接收新的请求,将连接交给线程池中某个线程来处理。

这种方式,其实就是将处理具体业务部分抽离出来,放到线程中去处理,但是数据的读取和发送,还是在那一个主线程中。

缺点同样是,如果client太多,那么accept的那个线程处理不过来。

 

 3. 主从Reactor多线程模型(netty就是使用这种)

 

 

就是在单reactor多线程基础之上,再弄线程,一个线程(其实是线程池)来专门处理连接请求,一个线程(其实是线程池)用来处理read和send请求,然后具体的业务,再按照单reactor多线程的方式处理。

介绍了这么多,其实Reactor的这些模型就是在原始NIO基础之上形成的,总结来说,就是,一个线程池用来处理连接请求(mainReactor),一个线程池用来处理read和write(subReactor),另外的线程池用来处理具体业务逻辑

 

标签:netty,前传,处理,Selector,NIO,selector,线程,server,Reactor
来源: https://www.cnblogs.com/hliy032/p/13889849.html