Netty介紹及NIO詳解

Netty簡介


Netty是一個異步、基於事件驅動的網絡應用框架

BIO、NIO、AIO的簡介

應用場景


  • 分佈式系統中各節點遠程過程調用(RPC:Dubbo)
  • 遊戲服務器
  • Hadoop通信

NIO


三大組件

NIO三大組件:Selector、Channel、Buffer

三大組件關係

  • 一個Channel對應一個Buffer,一個Selector管理多個Channel,一個線程對應一個Selector
  • 程序切換到哪個Channel由事件決定,Event
  • Buffer就是一個內存塊,底層是數組。Client通過Buffer進行數據的讀寫,NIO中的Buffer是雙向的,BIO中的輸入流、輸出流不是雙向的。
  • Channel也是雙向的

Buffer

抽象類Buffer中的屬性:

  • mark : 標記
  • position : 位置,下一次要讀寫的元素的位置。
  • limit : 緩衝區的終點,不能超過緩衝區的最大位置,可以修改
  • capacity :容量,緩衝區創建時指定
		IntBuffer intBuffer = IntBuffer.allocate(5);
        for (int i = 0; i < intBuffer.capacity(); i++) {
            // 將元素插入position位置
            intBuffer.put(i << 10);
        }
        // 讀寫翻轉
        intBuffer.flip();
        // 將limit指定爲3,只能獲取索引小於3的元素
        intBuffer.limit(3);
        System.out.println(intBuffer.capacity());
        while (intBuffer.hasRemaining()){
            // 獲取position位置的元素
            System.out.println(intBuffer.get());
        }

基本類型中除了bool,其他類型都有對應的Buffer類。

IntegerBuffer類中的重要屬性:

	// 存儲數據的數組
	final int[] hb;                  // Non-null only for heap buffers
    final int offset;
    // 是否只讀
    boolean isReadOnly;

Channel

Channel可以同時讀寫,可以異步讀寫數據。

  • FileChannel:文件讀寫
    FileOutPutStream和FileInputStream中包含了FileChannel屬性,可以通過這兩個類的實例獲得Channel。
    實例:
		FileOutputStream outputStream = new FileOutputStream("D://hello.txt");
        // 從FileOutPutStream獲取FileChannelImpl
        FileChannel channel = outputStream.getChannel();
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        String s = "hello,World";
        // 將byte數組放入緩衝區,buffer的position等於數組長度
        buffer.put(s.getBytes());
        // 讀寫翻轉,limit=position,而position置0
        buffer.flip();
        // 寫入
        channel.write(buffer);
        outputStream.close();
        channel.close();
		FileInputStream inputStream = new FileInputStream("D://hello.txt");
        // FileInputStream獲取FileChannel,實際類型是FileChannelImpl
        FileChannel channel = inputStream.getChannel();
        ByteBuffer buffer = ByteBuffer.allocate(1024);
        int read = channel.read(buffer);
        System.out.println(read);
        String s = new String(buffer.array(), 0, read,"utf-8");
        System.out.println(s);
        inputStream.close();
        channel.close();
		FileInputStream inputStream = new FileInputStream("D://hello.txt");
        FileOutputStream outputStream = new FileOutputStream("D://hello2.txt");
        FileChannel inChannel = inputStream.getChannel();
        FileChannel outputChannel = outputStream.getChannel();
        ByteBuffer buffer = ByteBuffer.allocate(2);
        while (true){
            // clear不重置的話,position=limit,則read一直等於0
            // 標誌位重置,position重置爲0,limit設爲capacity
            buffer.clear();
            int read = inChannel.read(buffer);
            if (read == -1){
                break;
            }
            buffer.flip();
            outputChannel.write(buffer);
        }
        inputStream.close();
        outputStream.close();
		// 使用transfrom拷貝文件
		FileInputStream inputStream = new FileInputStream("D://a.jpg");
        FileOutputStream outputStream = new FileOutputStream("D://a2.jpg");
        FileChannel inChannel = inputStream.getChannel();
        FileChannel outputChannel = outputStream.getChannel();
        // transferFrom拷貝
        outputChannel.transferFrom(inChannel,0,inChannel.size());
        inputStream.close();
        outputStream.close();

Buffer和Channel注意事項

  • ByteBuffer,put什麼類型,取得時候就要相應的類型去get。

  • Buffer可以設置爲只讀

    // 只讀Buffer,不可寫,否則會報ReadOnlyBufferException
    ByteBuffer readOnlyBuffer = buffer.asReadOnlyBuffer();
    
  • MappedByteBuffer可以直接在內存(堆外內存)中修改,操作系統不需要拷貝一次

    		// 直接在內存中修改,不用操作系統再拷貝一次
            RandomAccessFile accessFile = new RandomAccessFile("D://a.txt", "rw");
            FileChannel channel = accessFile.getChannel();
            /**
             * 參數說明;
             * 1.FileChannel.MapMode.READ_WRITE 使用讀寫模式
             * 2.直接修改的起始位置
             * 3.從起始位置映射到內存的大小(不是索引),超過字節大小將不能修改
             */
            MappedByteBuffer mappedByteBuffer = channel.map(FileChannel.MapMode.READ_WRITE, 0, 5);
            mappedByteBuffer.put(0,(byte) 'a');
            mappedByteBuffer.put(3,(byte) '9');
            accessFile.close();
    
  • NIO支持多個Buffer的Scatting和Gathering

    /**
             * Scatting:將數據寫入到buffer時,可以使用buffer數組,依次寫入
             * Gathering:從buffer讀取數據時,可以採用buffer數組,依次讀取
             */
            ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
            InetSocketAddress socketAddress = new InetSocketAddress(7000);
            serverSocketChannel.socket().bind(socketAddress);
    
            // 創建buffer數組
            ByteBuffer[] buffers = new ByteBuffer[2];
            buffers[0] = ByteBuffer.allocate(5);
            buffers[1] = ByteBuffer.allocate(8);
            int messageLength = 10;
            // 等待連接
            SocketChannel socketChannel = serverSocketChannel.accept();
            // 循環讀取
            while (true) {
                int byteRead = 0;
                while (byteRead < messageLength){
                    long l = socketChannel.read(buffers);
                    byteRead += l;
                    System.out.println("byteRead = "+byteRead);
                    Arrays.asList(buffers).stream().map(buffer -> "position = "+buffer.position() +",limit = "+buffer.limit()).forEach(System.out::println);
                }
    
                Arrays.asList(buffers).forEach(byteBuffer -> byteBuffer.flip());
                int byteWrite = 0;
                while (byteWrite < messageLength){
                    long l = socketChannel.write(buffers);
                    byteWrite += l;
                }
                Arrays.asList(buffers).forEach(byteBuffer -> byteBuffer.clear());
                System.out.println("byteRead = "+byteRead + ",byteWrite = "+byteWrite +",messageLength = "+messageLength);
    

Selector

  • Channel註冊到Selector,Selector能夠檢測到Channel是否有事件發生。如果有事件發生,則進行相應的處理。這樣可以實現一個線程管理多個Channel(即多個連接和請求)
  • 只有通道真正有讀寫事件發生時,纔會進行讀寫。減少了創建的線程數,降低了系統開銷
  • 減少了上下文的切換,用戶態和系統態的切換

以ServerSocketChannel爲例說明:

  1. 當有客戶端連接時,ServerSocketChannel會返回一個SocketChannel
  2. SocketChannel註冊到Selector。(register方法)
  3. register方法會返回一個SelectionKey,SelectionKey與Channel關聯
  4. Selector監聽select方法,返回有事件的個數
  5. 進一步得到SelectionKey
  6. 通過SelectionKey獲取SocketChannel(SelectionKey中的channel方法)
  7. 通過獲取的channel,執行業務處理

代碼說明:

SeverSocketChannel端:

 		// 創建ServerSocketChannel
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        // 創建Selector
        Selector selector = Selector.open();
        // 綁定ip
        serverSocketChannel.socket().bind(new InetSocketAddress(9000));
        // 設置爲不阻塞
        serverSocketChannel.configureBlocking(false);
        // 將ServerSocketChannel註冊到selector。指定關心的事件爲OP_ACCEPT,
        // 當有關心的事件發生時,會返回這個SelectionKey,通過SelectionKey可以拿到Channel
        serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);

        while (true){
            // Selector監聽,等於0說明此時沒有事件發生。
            if (selector.select(1000) == 0) {
                System.out.println("Selector監聽了一秒");
                continue;
            }
            Set<SelectionKey> keys = selector.selectedKeys();
            Iterator<SelectionKey> keyIterator = keys.iterator();
            while (keyIterator.hasNext()){
                SelectionKey key = keyIterator.next();
                if (key.isAcceptable()){
                    // 獲得SocketChannel,此處的accept不會阻塞
                    SocketChannel socketChannel = serverSocketChannel.accept();
                    // 此處socketChannel也要設置爲非阻塞模式
                    socketChannel.configureBlocking(false);
                    // 註冊Selector。第三個參數是連接的對象,通過SelectionKey可以連接到這個對象
                    socketChannel.register(selector,SelectionKey.OP_READ,ByteBuffer.allocate(1024));
                }
                if (key.isReadable()){
                    SocketChannel channel = (SocketChannel)key.channel();
                    ByteBuffer buffer = (ByteBuffer) key.attachment();
                    int read = channel.read(buffer);
                    System.out.println("客戶端 : "+new String(buffer.array(),0,read));
                }
                // 手動刪除避免重複
                keyIterator.remove();
            }
        }

SocketClient端;

		SocketChannel socketChannel = SocketChannel.open();
        // 設置非阻塞模式
        socketChannel.configureBlocking(false);
        boolean connect = socketChannel.connect(new InetSocketAddress("127.0.0.1",9000));
        if (!connect){
            while (!socketChannel.finishConnect()){
                System.out.println("因爲連接需要時間,客戶端不會阻塞,可以做一些其他工作");
            }
        }
        ByteBuffer buffer = ByteBuffer.wrap("This is a message!".getBytes());
        socketChannel.write(buffer);
        System.in.read();
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章