Netty源码解析(一) —— nio的基本操作和reactor模型

大家都知道传统的bio(即阻塞io)的并发性能不高,原因是什么呢?

  1. ServerSocket要accept接受客户端socket是阻塞的,那么这个线程就一直被占用,这样才能保证客户端的连接都能被处理
  2. accept到客户端的socket连接之后要再开一个线程来处理读写操作,这样又一个线程被占用
  3. 所以当并发访问量非常高时,服务器的线程调度数量到达峰值,就导致响应客户端性能下降

那么怎么解决io线路复用的问题呢,答案就是nio(非阻塞io)的出现,下面我们来看下nio的基本操作方法

public class NioServer {
    ServerSocketChannel ssc;
    private final Selector selector;

    public NioServer() throws IOException {
        //打开serversocketchannel
        ssc = ServerSocketChannel.open();
        //设置为非阻塞
        ssc.configureBlocking(false);
        //绑定端口
        ssc.socket().bind(new InetSocketAddress(8000));
        //创建selector
        selector = Selector.open();

        //注册channel到selector  并且绑定accept事件
        ssc.register(selector,SelectionKey.OP_ACCEPT);

        //处理服务端channel接受问题
        handleAccept();
    }

selector是一个底层的轮询器,他可以去轮询ServerSocketChannel是否有新连接accept,有阻塞和非阻塞轮询方式,如果ServerSocketChannel关注是否有客户端accept进来,就需要向selector注册accept事件

    private void handleAccept() throws IOException {
        for (;;){
            //阻塞轮询  阻塞3s来轮询是否有新连接到来 num返回SelectionKey的个数
            int num = selector.select(3 * 1000L);
            if(num == 0)continue;
            //拿到selectKey的迭代器  返回的是一个set集合
            Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()){
                SelectionKey key = iterator.next();
                //拿到有accept事件的selectKey  从集合中移除
                iterator.remove();
                //验证key的有效性
                if(!key.isValid())continue;
                handlekey(key);
            }

        }
    }

这段代码主要就是调用selector去阻塞的轮询客户端到来的连接,拿到SelectionKey 进行io操作

    private void handlekey(SelectionKey key) throws IOException {
        //事件是accept
        if(key.isAcceptable()){
            //拿到服务端的channel 调用accept方法  返回客户端的channel连接
            SocketChannel socketChannel = ((ServerSocketChannel) key.channel()).accept();
            //设置channel非阻塞
            socketChannel.configureBlocking(false);
            //把channel注册到selector上  对读事件感兴趣
            socketChannel.register(selector,SelectionKey.OP_READ);
        }
        if(key.isReadable()){
            //可读的事件 客户端channel
            SocketChannel channel = (SocketChannel) key.channel();
            //创建ByteBuffer  从channel往buf写数据
            ByteBuffer buffer = ByteBuffer.allocate(1024);
            int count = channel.read(buffer);
            if(count==-1)return;
            //重置buf 到可读状态
            buffer.flip();
            String content = new String(buffer.array());
            System.out.println(content);

            //返回数据
            ByteBuffer buf = ByteBuffer.allocate(1024);
            buf.put("hellow word".getBytes());
            buf.flip();
            //channel 写数据
            channel.write(buf);
        }
    }

拿到channel注册到selector轮询器read事件,在read事件到来的时候读数据,向客户端写数据,这种nio的模型为实现io复用提供了很大的方便,单个线程可以同时处理很多的请求,在read事件准备好之前不会阻塞线程,这段时间可以处理其他已经准备就绪的io操作,极大的提高了io线路复用率


再仔细考虑一下单线程模型下单个selector轮询accept read write等全部事件,虽然说非阻塞但还是单线程,必然造成请求的响应缓慢问题,参考一下Netty的Reactor模型

  1. 服务端又一个专门负责轮询accept的线程(boss线程)
  2. 轮询到的key交给work线程来继续轮询read write操作以及pipline的执行逻辑
  3. work线程和boss线程都可以设置成多线程的线程池,这样就提高了io链路的吞吐量

下一篇开始分析netty的源码流程

猜你喜欢

转载自blog.csdn.net/u011702633/article/details/81942942