bio/nio/aio

先看一下这三种的名词解释。
bio:同步阻塞io;
nio:同步非阻塞io;
aio:异步非阻塞io;

通过解释之后,引申出几个概念,什么是同步与异步,什么是阻塞与非阻塞。

同步就是需要自己去监听事件是佛欧有返回结果,而异步是其他线程去做这件事。
阻塞的意思的,比如在bio中,我调用了accept方法,会一直阻塞在这里,直到有连接事件发生,这种酒属于阻塞事件,非阻塞就是在nio中我们调用accept方法,会马上返回结果,线程可以去处理其他事情,这就是非阻塞。

bio模型:
bio/aio/nio以及多路复用器_netty

nio模型:

bio/aio/nio以及多路复用器_epoll_02

那么为什么netty不使用看上去更高大上的aio,而是使用nio的,其实netty5是使用aio的,但是因为操作系统对aio的支持还不是特别好,而且aio的代码处理逻辑又特别复杂,所以netty5官方不建议使用,现在主流的版本是netty4还是在使用nio的。

多路复用器

当使用bio我们服用断的代码是这样的。

package cn.darwin.base.bio;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;

public class BioServer {

    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(9001);
        for (; ; ) {
            final Socket accept = serverSocket.accept();
            new Thread(() -> {
                try {
                    while (true) {
                        byte[] bytes = new byte[1024];
                        final int read = accept.getInputStream().read(bytes);
                        if (read != -1) {
                            System.out.println(new String(bytes));
                        }
                        accept.getOutputStream().write("欢迎".getBytes(StandardCharsets.UTF_8));
                        accept.getOutputStream().flush();
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

在调用accept的时候会堵塞住,做不了其他的操作,这也就是为什么bio会有著名的c10k问题,就是一个服务端连接1W个客户端,性能就会有极大的瓶颈。

而nio将channel设置为非阻塞之后,accept将会立刻返回结果,我们可以将这些个channel放到一个集合中去,然后循环遍历这个list有没有事件发生,即可一个线程处理多个客户端的连接,代码如下:

package cn.darwin.base.nio;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ServerChannel;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

public class NioServer {

    public static void main(String[] args) throws IOException {

        List<SocketChannel> channelList = new LinkedList<>();

        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.socket().bind(new InetSocketAddress(9002));

        final Selector open = Selector.open();

        serverSocketChannel.configureBlocking(false);
        while (true) {
            final SocketChannel accept = serverSocketChannel.accept();
            if (accept != null) {
                accept.configureBlocking(false);
                channelList.add(accept);
            }
            final Iterator<SocketChannel> iterator = channelList.iterator();
            while (iterator.hasNext()) {
                ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                final int read = iterator.next().read(byteBuffer);
                if (read != -1) {
                    System.out.println(new String(byteBuffer.array()));
                }
            }
        }
    }
}

那么,这样就是比较好的操作了么,假设我们现在一共有100000个链接,每次只有10个左右的连接会有事件发生,我每次要去遍历所有的list么?多路复用器就是我们只去获取那些有事件发生的文件描述符。这种在操作系统层面有几种实现,poll、select、epoll,poll和select还是轮训每个文件操作符看哪个有事件发生,而epoll是在内部维护了一个红黑树和链表,当有数据到达网卡的时候,会触发硬中断,会把这些有事件的放到红黑树中,然后当调用系统函数epoll_wait的时候会将有事件的附件描述符返回。epoll还有两个系统调用,分别是epoll_create和epoll_ctl,分别用于初始化epoll和把文件描述符添加到红黑树中。这就是nio的核心组件selector主要做的事情。

package cn.darwin.base.nio;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ServerChannel;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectableChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Set;

public class NioServer {

    public static void main(String[] args) throws IOException {

        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.socket().bind(new InetSocketAddress(9002));
        serverSocketChannel.configureBlocking(false);

        final Selector selector = Selector.open();
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        while (true) {
            selector.select();
            final Iterator<SelectionKey> iterator = selector.selectedKeys().iterator();
            while (iterator.hasNext()) {
                SelectionKey selectionKey = iterator.next();
                if (selectionKey.isAcceptable()) {
                    ServerSocketChannel channel = (ServerSocketChannel)selectionKey.channel();
                    final SocketChannel accept = channel.accept();
                    accept.configureBlocking(false);
                    accept.register(selector,SelectionKey.OP_READ);
                } else {
                    SocketChannel socketChannel = (SocketChannel)selectionKey.channel();
                    ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
                    final int read = socketChannel.read(byteBuffer);
                    if (read != -1) {
                        System.out.println(new String(byteBuffer.array()));
                    }
                }
                iterator.remove();
            }
        }
    }
}