NIO 源碼分析(01) NIO 最簡用法

NIO 源碼分析(01) NIO 最簡用法java

Netty 系列目錄(http://www.javashuo.com/article/p-hskusway-em.html)linux

Java NIO 主要由三個部分組成:Channel、Buffer 和 Selector。在分析源碼前最好對 NIO 的基本用法和 Linux NIO 在一個基本的瞭解。服務器

  1. NIO 入門
  2. Linux NIO

本文會提供一個 NIO 最簡使用示例,以後的源碼分析都會基於該示例及其擴展進行。socket

1、服務端

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();
        }
    }
}

2、客戶端

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,完成!!!源碼分析


天天用心記錄一點點。內容也許不重要,但習慣很重要!this

相關文章
相關標籤/搜索