NIO 源码分析(01) NIO 最简用法

NIO 源码分析(01) NIO 最简用法

Netty 系列目录(https://www.cnblogs.com/binarylei/p/10117436.html)

Java NIO 主要由三个部分组成:Channel、Buffer 和 Selector。在分析源码前最好对 NIO 的基本用法和 Linux NIO 在一个基本的了解。

  1. NIO 入门
  2. Linux NIO

本文会提供一个 NIO 最简使用示例,之后的源码分析都会基于该示例及其扩展进行。

一、服务端

public class Server implements Runnable {
    public static void main(String[] args) {
        new Thread(new Server(8765)).start();
    }

    // 1 多路复用器(管理所有的通道)
    private Selector selector;
    // 2 建立缓冲区
    private ByteBuffer readBuf = ByteBuffer.allocate(1024);
    private ByteBuffer writeBuf = ByteBuffer.allocate(1024);

    public Server(int port) {
        try {
            //1. 获取多路复用器
            this.selector = Selector.open();

            //2. 获取服务器端通道
            ServerSocketChannel ssChannel = ServerSocketChannel.open();

            //3. 切换成非阻塞模式
            ssChannel.configureBlocking(false);

            //4. 绑定端口号
            ssChannel.bind(new InetSocketAddress(port));

            //5. 把服务器通道注册到多路复用器上,并且监听阻塞事件
            ssChannel.register(this.selector, SelectionKey.OP_ACCEPT);

            System.out.println("Server start, port :" + port);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public void run() {
        while (true) {
            try {
                // 1. 阻塞到至少有一个通道在你注册的事件上就绪,返回的 int 值表示有多少通道已经就绪
                int wait = selector.select();
                if (wait == 0) continue;

                //2. 遍历多路复用器上已经准备就绪的通道
                Iterator<SelectionKey> iterator = this.selector.selectedKeys().iterator();
                while (iterator.hasNext()) {
                    SelectionKey key = iterator.next();

                    if (key.isValid()) {
                        if (key.isAcceptable()) {
                            // 3.1 如果为 accept 状态,就获取客户端通道并注册到selector上
                            this.accept(key);
                        } else if (key.isReadable()) {
                            // 3.2 如果为可读状态
                            this.read(key);
                        } else if (key.isWritable()) {
                            // 3.3 写数据
                            this.write(key);
                        }
                    }

                    // 4. 注意必须在处理完通道时自己移除
                    iterator.remove();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    private void write(SelectionKey key) {
        //ServerSocketChannel ssc =  (ServerSocketChannel) key.channel();
        //ssc.register(this.seletor, SelectionKey.OP_WRITE);
    }

    private void read(SelectionKey key) {
        try {
            //1. 清空缓冲区旧的数据
            this.readBuf.clear();

            //2. 获取之前注册的socket通道对象
            SocketChannel sChannel = (SocketChannel) key.channel();

            //3. 读数据
            int len = sChannel.read(this.readBuf);
            if (len == -1) {
                key.channel().close();
                key.cancel();
                return;
            }

            //5 有数据则进行读取 读取之前要flip()复位
            this.readBuf.flip();
            byte[] bytes = new byte[this.readBuf.remaining()];
            this.readBuf.get(bytes);

            System.out.println("Server : " + new String(bytes).trim());
            //6. 可以写回给客户端数据
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    private void accept(SelectionKey key) {
        try {
            //1. 获取服务通道ServerSocketChannel
            ServerSocketChannel ssChannel = (ServerSocketChannel) key.channel();

            //2. 获取客户端通道SocketChannel
            SocketChannel sChannel = ssChannel.accept();

            //3. 客户端通道SocketChannel也要设置成非阻塞模式
            sChannel.configureBlocking(false);

            //4 注册到多路复用器上,并设置读取标识
            sChannel.register(this.selector, SelectionKey.OP_READ);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

二、客户端

public static void main(String[] args) throws IOException {
    SocketChannel sChannel = null;
    try {
        //1. 获取通道
        sChannel = SocketChannel.open(
                new InetSocketAddress("127.0.0.1", 8765));

        //2. 切换成非阻塞模式
        sChannel.configureBlocking(false);

        //3. 分配缓冲区
        ByteBuffer buf = ByteBuffer.allocate(1024);

        //4. 把从控制台读到数据发送给服务器端
        Scanner scanner = new Scanner(System.in);
        while (true) {
            String str = scanner.next();
            SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd hh:mm:ss");
            buf.put((dateFormat.format(new Date()) + ":" + str).getBytes());
            buf.flip();// 非阻塞模式下,write()方法在尚未写出任何内容时可能就返回了
            while (buf.hasRemaining()) {
                sChannel.write(buf);
            }
            buf.clear();
        }
    } finally {
        sChannel.close();
    }
}

ok,完成!!!


每天用心记录一点点。内容也许不重要,但习惯很重要!

原文地址:https://www.cnblogs.com/binarylei/p/11134979.html