Netty - 眼熟NIO

伊邪那岐丶發表於2020-10-30

Netty系列文章 - 眼熟Nio

什麼是IO?什麼是NIO?

  1. IO:
    • java.io中最為核心的概念是流stream),面向流程式設計,java中,一個流要麼是輸入流,要麼是輸出流
  2. NIO:
    • java.nio中擁有3個核心概念:selector. channel. buffer; java.nio中,面向塊(block)或是緩衝區(buffer)程式設計的,
    • java中的原生8種基本資料型別都有各自對應的buffer型別,(除Boolean外),如IntBuffer,CharBuffer,ByteBuffer,LongBuffer,ShortBuffer
    • 所有資料的讀寫都是通過buffer來進行的,永遠不會出現直接channel中直接寫入,讀取資料
    • stream不同的是,channel雙向的,一個流只可能是InputStream或是OutputStreamchannel則是雙向的,channel開啟後可以進行讀又可以進行寫

3個核心概念

Buffer

Buffer屬性以及相關操作.

屬性說明
capacity 最大容量它永遠不可能為負數,並且是不會變化的
position 位置下一個讀或寫的位置,它永遠不可能為負數,並且不會大於limit
limit 限制它永遠不可能為負數,並且不會大於capacity
mark 標記標記位置,用於記錄某次讀寫的位置,可以通過reset()方法回到這裡
public class Dome1 {
    public static void main(String[] args) {

        // 分配一塊容量大小為8個長度的Buffer塊
        IntBuffer buffer = IntBuffer.allocate(5);
        
        // 生成隨機數存入buffer中
        for (int i = 0; i < buffer.capacity(); i++) {
            int nextInt = new Random().nextInt(20);
            buffer.put(nextInt);
        }

        /**
         * 
         * 反轉一下
         * 其實核心也就是執行了這兩行程式碼
         *         limit = position;
         *         position = 0;
         */
        buffer.flip();
        
        
        while (buffer.hasRemaining()) {
            System.out.print(buffer.get()+"\t"); //輸出結果: 3	1	4	5	10	
        }
    }
}

Buffer初始化完成後圖示

在這裡插入圖片描述

put或get一次資料``position`就會往右移動一次

在這裡插入圖片描述

呼叫buffer.flip();後limit = position; position = 0;
在這裡插入圖片描述

Channel

常見的Channel實現有

FileChannel:檔案讀寫資料通道
SocketChannel:TCP讀寫網路資料通道
ServerSocketChannel:服務端網路資料讀寫通道,可以監聽TCP連線。對每一個新進來的連線都會建立一個SocketChannel: 客戶端網路資料讀寫通道.
DatagramChannel:UDP讀寫網路資料通道

通道表示開啟到 IO 裝置(例如:檔案、套接字)的連線。若需要使用 NIO 系統,需要獲取用於連線 IO 裝置的通道以及用於容納資料的緩衝區。然後操作緩衝區,對資料進行處理。
Channel相比IO中的Stream更加高效,可以非同步雙向傳輸,但是必須和buffer一起使用。

// 讀一個檔案內容
public class Dome2 {

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

        FileInputStream fileInputStream = new FileInputStream("dome2.txt");
        FileChannel channel = fileInputStream.getChannel();
        ByteBuffer byteBuffer = ByteBuffer.allocate(1024);

        channel.read(byteBuffer);
        byteBuffer.flip();

        while (byteBuffer.remaining() > 0) {
            System.err.println((char) byteBuffer.get());
        }

        fileInputStream.close();
    }

}

輸出結果

H
e
l
l
o

// 從一個檔案讀資料並寫到另一個檔案內!
public class Dome4 {
    public static void main(String[] args) throws Exception {
        FileOutputStream fileOutputStream = new FileOutputStream("dome4write.txt");
        FileInputStream fileInputStream = new FileInputStream("dome4read.txt");

        FileChannel channelRead = fileInputStream.getChannel();
        FileChannel channelWrite = fileOutputStream.getChannel();
        ByteBuffer byteBuffer = ByteBuffer.allocate(100);
        while (true) {
          	// 注意這個clear方法
            byteBuffer.clear();
            System.out.println(byteBuffer.position());
            int readNumber = channelRead.read(byteBuffer);
            System.out.println(readNumber);
            if (-1 == readNumber) {
                break;
            }
            byteBuffer.flip();
            channelWrite.write(byteBuffer);
        }
        fileOutputStream.close();
        fileInputStream.close();
    }
}

Selector(選擇器)(多路複用器)

1. Selector的建立

通過呼叫Selector.open()方法建立一個Selector物件,如下:

Selector selector = Selector.open();

2. 註冊Channel到Selector

channel.configureBlocking(false);
SelectionKey key = channel.register(selector, Selectionkey.OP_READ);

Channel必須是非阻塞的
所以FileChannel不適用Selector,因為FileChannel不能切換為非阻塞模式,更準確的來說是因為FileChannel沒有繼承SelectableChannel。Socket channel可以正常使用。

SelectableChannel抽象類 有一個 configureBlocking() 方法用於使通道處於阻塞模式或非阻塞模式。

abstract SelectableChannel configureBlocking(boolean block)  

示例程式碼

NIOServer端

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

    //得到serverSocketChannel物件   老大
    ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();

    //得到Selector物件   間諜
    Selector selector = Selector.open();

    //繫結埠
    serverSocketChannel.bind(new InetSocketAddress(9090));

    //設定非阻塞式
    serverSocketChannel.configureBlocking(false);

    //把ServerSocketChannel註冊給Selector
    serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);   //監聽連線

    //幹活
    while (true) {
        try {
            //監控客戶端
            if (selector.select(2000) == 0) {
                System.out.println("2秒內沒有客戶端來連線我");
                continue;
            }
            //得到SelectionKey物件,判斷是事件
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            for (SelectionKey selectionKey : selectionKeys) {
                if (selectionKey.isAcceptable()) {     //連線事件
                    System.out.println("有人來連線");
                    //獲取網路通道
                    SocketChannel clientSocket = serverSocketChannel.accept();
                    //設定非阻塞式
                    clientSocket.configureBlocking(false);
                    //連線上了  註冊讀取事件
                    clientSocket.register(selector, SelectionKey.OP_READ, ByteBuffer.allocate(1024));
                }
                if (selectionKey.isReadable()) {     //讀取客戶端資料事件
                    SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
                    ByteBuffer byteBuffer = (ByteBuffer) selectionKey.attachment();
                    socketChannel.read(byteBuffer);
                    System.out.println(new String(byteBuffer.array()));
                    byteBuffer.put("你好".getBytes());
                    byteBuffer.flip();
                    socketChannel.write(byteBuffer);
                }
                if (selectionKey.isWritable()) {
                    SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
                    ByteBuffer byteBuffer = (ByteBuffer) selectionKey.attachment();
                    byteBuffer.put("你好".getBytes());
                    byteBuffer.clear();
                    socketChannel.write(byteBuffer);
                    selectionKey.interestOps(SelectionKey.OP_READ);
                }
                //手動從當前集合將本次執行完的物件刪除
                selectionKeys.remove(selectionKey);
            }
        } catch (Exception e) {
            System.out.println("錯了");
        }
    }

}

NIOClient客戶端

public static void main(String[] args) throws Exception {
    //得到一個網路通道
    SocketChannel socketChannel = SocketChannel.open();
    //設定非阻塞式
    socketChannel.configureBlocking(false);
    //提供伺服器ip與埠
    InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 9090);
    //連線伺服器端
    if (!socketChannel.connect(inetSocketAddress)) {     //如果連線不上
        while (!socketChannel.finishConnect()) {
            System.out.println("nio非阻塞");
        }
    }
    new Thread(new MyRunble(socketChannel)).start();
    while (true) {
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        int read = socketChannel.read(buffer);
        if (read > 0) {
            System.out.println(new String(buffer.array()));
        }
    }
}


static class MyRunble implements Runnable {
    SocketChannel socketChannel;

    MyRunble(SocketChannel channel) {
        this.socketChannel = channel;
    }

    @Override
    public void run() {
        while (true) {
            //建立一個buffer物件並存入資料
            Scanner scanner = new Scanner(System.in);
            String message = scanner.nextLine();
            ByteBuffer buffer = ByteBuffer.wrap(message.getBytes());
            //傳送資料
            try {
                socketChannel.write(buffer);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

相關文章