netty入门篇-1-《BIO、NIO、AIO》

93 阅读6分钟

一、介绍

IO是Java中比较重要知识点,随着Java的发展,目前有三种IO共存;分别是BIO、NIO和AIO。

Java BIO[Blocking I/O] | 同步阻塞I/O模式

  • BIO 是一种同步且阻塞的通信模式。是一个比较传统的通信方式,模式简单,使用方便。但并发处理能力低,通信耗时,依赖网速。
  • 当用户程序的线程调用 read 获取网络数据的时候,首先这个数据得有,也就是网卡得先收到客户端的数据,然后这个数据有了之后需要拷贝到内核中,然后再被拷贝到用户空间内,这整一个过程用户线程都是被阻塞的。
  • 假设没有客户端发数据过来,那么这个用户线程就会一直阻塞等着,直到有数据。即使有数据,那么两次拷贝的过程也得阻塞等着。

Java NIO[New I/O] | 同步非阻塞模式

  • Java NIO,全程 Non-Block IO ,是Java SE 1.4版以后,针对网络传输效能优化的新功能。是一种非阻塞同步的通信模式。
  • NIO 与原来的 I/O 有同样的作用和目的, 他们之间最重要的区别是数据打包和传输的方式。原来的 I/O 以流的方式处理数据,而 NIO 以块的方式处理数据。
  • 在没数据的时候可以不再傻傻地阻塞等着,而是直接返回错误,告知暂无准备就绪的数据!这里要注意,从内核拷贝到用户空间这一步,用户线程还是会被阻塞的。
  • 这个模型相比于同步阻塞 I/O 而言比较灵活,比如调用 read 如果暂无数据,则线程可以先去干干别的事情,然后再来继续调用 read 看看有没有数据。

Java AIO[Asynchronous I/O] | 异步非阻塞I/O模型

  • Java AIO,全程 Asynchronous IO,是异步非阻塞的IO。
  • 异步 I/O 其实就是用户线程调用 aio_read ,然后包括将数据从内核拷贝到用户空间那步,所有操作都由内核完成,当内核操作完毕之后,再调用之前设置的回调,此时用户线程就拿着已经拷贝到用户控件的数据可以继续执行后续操作。在整个过程中,用户线程没有任何阻塞点,这才是真正的非阻塞I/O。

分别对三种IO进行案例演示,通过对三种的IO的认知来方便学习后续的Netty知识

二、开发环境

  1. Jdk1.8
  2. NetAssist 网络调试助手

下载地址

wwrj.lanzouw.com/b00oc7nqba
密码:1hl5

三、BIO案例测试

代码结构

ChannelAdapter.java | 适配器

@Slf4j
public abstract class ChannelAdapter extends Thread {

    private final Socket socket;
    private final ChannelHandler channelHandler;
    private final Charset charset;

    protected ChannelAdapter(Socket socket, Charset charset) {
        this.socket = socket;
        this.charset = charset;
        channelHandler = new ChannelHandler(this.socket, charset);
        channelActive(channelHandler);
    }

    @Override
    public void run() {
        try {
            BufferedReader input = new BufferedReader(new InputStreamReader(this.socket.getInputStream(), charset));
            String str;
            while ((str = input.readLine()) != null) {
                channelRead(channelHandler, str);
            }
        } catch (IOException e) {
            log.error("异常", e);
        }
    }

    // 通知抽象类
    public abstract void channelActive(ChannelHandler ctx);

    // 读取消息抽象类
    public abstract void channelRead(ChannelHandler ctx, Object msg);

}

BioClient.java | 客户端

@Slf4j
public class BioClient {
    public static void main(String[] args) {
        try {
            Socket socket = new Socket("10.211.55.3", 8080);
            log.info("bio client start done");
            BioClientHandler bioClientHandler = new BioClientHandler(socket, StandardCharsets.UTF_8);
            bioClientHandler.start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

BioClientHandler.java | 消息处理器

@Slf4j
public class BioClientHandler extends ChannelAdapter {

    public BioClientHandler(Socket socket, Charset charset) {
        super(socket, charset);
    }

    @Override
    public void channelActive(ChannelHandler ctx) {
        log.info("链接报告LocalAddress:{}", ctx.socket().getLocalAddress());
        ctx.writeAndFlush("BioClient to msg for you \r\n");
    }

    @Override
    public void channelRead(ChannelHandler ctx, Object msg) {
        log.info("{} 接收到消息:{}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()), msg);
        ctx.writeAndFlush("hi 我已经收到你的消息Success!\r\n");
    }
}

BioServer.java | 服务端

@Slf4j
public class BioServer extends Thread {

    private ServerSocket serverSocket = null;

    public static void main(String[] args) {
        BioServer bioServer = new BioServer();
        bioServer.start();
    }

    @Override
    public void run() {
        try {
            serverSocket = new ServerSocket();
            serverSocket.bind(new InetSocketAddress(7397));
            log.info("bio server start done");
            while (true) {
                Socket socket = serverSocket.accept();
                BioServerHandler handler = new BioServerHandler(socket, StandardCharsets.UTF_8);
                handler.start();
            }
        } catch (IOException e) {
            log.error("异常", e);
        }
    }
}

BioServerHandler.java | 消息处理器

@Slf4j
public class BioServerHandler extends ChannelAdapter {

    public BioServerHandler(Socket socket, Charset charset) {
        super(socket, charset);
    }

    @Override
    public void channelActive(ChannelHandler ctx) {
        log.info("链接报告LocalAddress:{}", ctx.socket().getLocalAddress());
        ctx.writeAndFlush("我是从服务端发来的消息 \r\n");
    }

    @Override
    public void channelRead(ChannelHandler ctx, Object msg) {
        log.info("{} 接收到消息:{}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()), msg);
        ctx.writeAndFlush("hi 我已经收到你的消息Success!\r\n");
    }
}

测试结果

四、NIO案例测试

代码结构

ChannelAdapter.java | 适配器

@Slf4j
public abstract class ChannelAdapter extends Thread {

    private Selector selector;

    private ChannelHandler channelHandler;
    private Charset charset;

    protected ChannelAdapter(Selector selector, Charset charset) {
        this.selector = selector;
        this.charset = charset;
    }

    @Override
    public void run() {
        while (true) {
            try {
                selector.select(1000);
                Set<SelectionKey> selectedKeys = selector.selectedKeys();
                Iterator<SelectionKey> it = selectedKeys.iterator();
                SelectionKey key = null;
                while (it.hasNext()) {
                    key = it.next();
                    it.remove();
                    handleInput(key);
                }
            } catch (Exception e) {
                log.error("抛出异常", e);
            }
        }
    }

    private void handleInput(SelectionKey key) throws IOException {
        if (!key.isValid()) return;

        // 客户端SocketChannel
        Class<?> superclass = key.channel().getClass().getSuperclass();
        if (superclass == SocketChannel.class){
            SocketChannel socketChannel = (SocketChannel) key.channel();
            if (key.isConnectable()) {
                if (socketChannel.finishConnect()) {
                    channelHandler = new ChannelHandler(socketChannel, charset);
                    channelActive(channelHandler);
                    socketChannel.register(selector, SelectionKey.OP_READ);
                } else {
                    System.exit(1);
                }
            }
        }

        // 服务端ServerSocketChannel
        if (superclass == ServerSocketChannel.class){
            if (key.isAcceptable()) {
                ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
                SocketChannel socketChannel = serverSocketChannel.accept();
                socketChannel.configureBlocking(false);
                socketChannel.register(selector, SelectionKey.OP_READ);

                channelHandler = new ChannelHandler(socketChannel, charset);
                channelActive(channelHandler);
            }
        }

        if (key.isReadable()) {
            SocketChannel socketChannel = (SocketChannel) key.channel();
            ByteBuffer readBuffer = ByteBuffer.allocate(1024);
            int readBytes = socketChannel.read(readBuffer);
            if (readBytes > 0) {
                readBuffer.flip();
                byte[] bytes = new byte[readBuffer.remaining()];
                readBuffer.get(bytes);
                channelRead(channelHandler, new String(bytes, charset));
            } else if (readBytes < 0) {
                key.cancel();
                socketChannel.close();
            }
        }
    }

    // 通知抽象类
    public abstract void channelActive(ChannelHandler ctx);

    // 读取消息抽象类
    public abstract void channelRead(ChannelHandler ctx, Object msg);

}

NioClient.java | 客户端

@Slf4j
public class NioClient {

    public static void main(String[] args) throws IOException {
        Selector selector = Selector.open();
        SocketChannel socketChannel = SocketChannel.open();
        socketChannel.configureBlocking(false);

        boolean isConnect = socketChannel.connect(new InetSocketAddress("10.211.55.3", 7397));
        if (isConnect) {
            socketChannel.register(selector, SelectionKey.OP_READ);
        } else {
            socketChannel.register(selector, SelectionKey.OP_CONNECT);
        }
        log.info("nio client start done");
        new NioClientHandler(selector, StandardCharsets.UTF_8).start();
    }
}

NioClientHandler.java | 消息处理器

@Slf4j
public class NioClientHandler extends ChannelAdapter {

    public NioClientHandler(Selector selector, Charset charset) {
        super(selector, charset);
    }

    @Override
    public void channelActive(ChannelHandler ctx) {
        try {
            log.info("链接报告LocalAddress:{}", ctx.channel().getLocalAddress());
            ctx.writeAndFlush("NioClient to msg for you");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void channelRead(ChannelHandler ctx, Object msg) {
        log.info("{} 接收到消息:{}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()), msg);
        ctx.writeAndFlush("我已经收到你的消息Success");
    }
}

NioServer.java | 服务端

@Slf4j
public class NioServer {

    private Selector selector;
    private ServerSocketChannel socketChannel;

    public static void main(String[] args) {
        new NioServer().bind(7397);
    }

    public void bind(int port) {
        try {
            selector = Selector.open();
            socketChannel = ServerSocketChannel.open();
            socketChannel.configureBlocking(false);
            socketChannel.socket().bind(new InetSocketAddress(port), 1024);
            socketChannel.register(selector, SelectionKey.OP_ACCEPT);
            log.info("nio server start done.");
            new NioServerHandler(selector, StandardCharsets.UTF_8).start();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

NioServerHandler.java | 消息处理器

@Slf4j
public class NioServerHandler extends ChannelAdapter {

    public NioServerHandler(Selector selector, Charset charset) {
        super(selector, charset);
    }

    @Override
    public void channelActive(ChannelHandler ctx) {
        try {
            log.info("链接报告LocalAddress:{}", ctx.channel().getLocalAddress());
            ctx.writeAndFlush("NioServer to msg for you");
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void channelRead(ChannelHandler ctx, Object msg) {
        log.info("{} 接收到消息:{}", new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()), msg);
        ctx.writeAndFlush("我已经收到你的消息Success");
    }
}

测试结果

五、AIO案例测试

代码结构

AioClient.java | 客户端

@Slf4j
public class AioClient {

    public static void main(String[] args) throws Exception {
        AsynchronousSocketChannel socketChannel = AsynchronousSocketChannel.open();
        Future<Void> future = socketChannel.connect(new InetSocketAddress("10.211.55.2", 7397));
        log.info("aio client start done");
        future.get();
        socketChannel.read(ByteBuffer.allocate(1024), null, new AioClientHandler(socketChannel, StandardCharsets.UTF_8));
        Thread.sleep(100000);
    }

}

AioClientHandler.java | 消息处理器

@Slf4j
public class AioClientHandler extends ChannelAdapter {

    public AioClientHandler(AsynchronousSocketChannel channel, Charset charset) {
        super(channel, charset);
    }

    @Override
    public void channelActive(ChannelHandler ctx) {
        try {
            log.info("链接报告信息:{}", ctx.channel().getRemoteAddress());
            //通知客户端链接建立成功
        } catch (IOException e) {
            log.error("异常",e);
        }
    }
    @Override
    public void channelInactive(ChannelHandler ctx) {
    }

    @Override
    public void channelRead(ChannelHandler ctx, Object msg) {
        log.info("服务端收到:{} {}", new Date(), msg);
        ctx.writeAndFlush("客户端信息处理Success!");
    }
}

AioServer.java | 服务端

@Slf4j
public class AioServer extends Thread {

    private AsynchronousServerSocketChannel serverSocketChannel;

    @Override
    public void run() {
        try {
            serverSocketChannel = AsynchronousServerSocketChannel.open(AsynchronousChannelGroup.withCachedThreadPool(Executors.newCachedThreadPool(), 10));
            serverSocketChannel.bind(new InetSocketAddress(7397));
            log.info("aio server start done");
            // 等待
            CountDownLatch latch = new CountDownLatch(1);
            serverSocketChannel.accept(this, new AioServerChannelInitializer());
            latch.await();
        } catch (Exception e) {
            log.error("异常",e);
        }
    }

    public AsynchronousServerSocketChannel serverSocketChannel() {
        return serverSocketChannel;
    }

    public static void main(String[] args) {
        new AioServer().start();
    }

}

AioServerHandler.java | 消息处理器

@Slf4j
public class AioServerHandler extends ChannelAdapter {

    public AioServerHandler(AsynchronousSocketChannel channel, Charset charset) {
        super(channel, charset);
    }

    @Override
    public void channelActive(ChannelHandler ctx) {
        try {
            log.info("链接报告信息:{}", ctx.channel().getRemoteAddress());
            //通知客户端链接建立成功
            ctx.writeAndFlush("通知服务端链接建立成功" + " " + new Date() + " " + ctx.channel().getRemoteAddress());
        } catch (IOException e) {
            log.error("异常",e);
        }
    }
    @Override
    public void channelInactive(ChannelHandler ctx) {
    }

    @Override
    public void channelRead(ChannelHandler ctx, Object msg) {
        log.info("服务端收到:{} {}", new Date(), msg);
        ctx.writeAndFlush("服务端信息处理Success!");
    }
}

测试结果