netty網絡編程(二)之netty入門以及主要組件介紹

NIO

NIO優點

  • 事件驅動模型
    • 避免多線程
    • 單線程處理多任務
  • 非阻塞IO,IO讀寫不再阻塞,而是返回0
  • 基於block的傳輸,通常比基於流的傳輸更加高效
  • 更高級的IO函數,zero-copy
  • IO多路複用大大提高了java網絡應用的可伸縮性和實用性

netty

有了NIO,爲什麼還要學習netty

  • 併發量大
    Netty是一款基於NIO(Nonblocking I/O,非阻塞IO)開發的網絡通信框架,對比於BIO(Blocking I/O,阻塞IO),他的併發性能得到了很大提高
  • 傳輸快
    Netty的傳輸快其實也是依賴了NIO的一個特性——零拷貝。我們知道,Java的內存有堆內存、棧內存和字符串常量池等等,其中堆內存是佔用內存空間最大的一塊,也是Java對象存放的地方,一般我們的數據如果需要從IO讀取到堆內存,中間需要經過Socket緩衝區,也就是說一個數據會被拷貝兩次才能到達他的的終點,如果數據量大,就會造成不必要的資源浪費。
    Netty針對這種情況,使用了NIO中的另一大特性——零拷貝,當他需要接收數據的時候,他會在堆內存之外開闢一塊內存,數據就直接從IO讀到了那塊內存中去,在netty裏面通過ByteBuf可以直接對這些數據進行直接操作,從而加快了傳輸速度。
  • NIO做了進一步的封裝
    簡化了編程。

netty組件調用關係圖

在這裏插入圖片描述

netty主要組件介紹

NIO編程回顧

在這裏插入圖片描述
在這裏插入圖片描述

Netty主要組件

  • 1.Transport Channel ---- 對應NIO中的channel
  • 2.EventLoop ---- 對應於NIO中的while循環
    • EventLoopGroup: 多個EventLoop
  • 3.ChannelHandler和ChannelPipeline — 對應於NIO中的客戶邏輯實現handleRead/handleWrite (interceptor pattern)
  • 4.ByteBuf ---- 對應於NIO 中的ByteBuffer
  • 5.Bootstrap 和 ServerBootstrap — 對應NIO中的Selector、ServerSocketChannel等的創建、配置、啓動等

Netty Server啓動主要流程

  • 設置服務端ServerBootStrap啓動參數
    • group(parentGroup, childGroup):
    • channel(NioServerSocketChannel): 設置通道類型
    • handler():設置NioServerSocketChannel的ChannelHandlerPipeline
    • childHandler(): 設置NioSocketChannel的ChannelHandlerPipeline
  • 通過ServerBootStrap的bind方法啓動服務端,bind方法會在parentGroup中註冊NioServerScoketChannel,監聽客戶端的連接請求
    • 會創建一個NioServerSocketChannel實例,並將其在parentGroup中進行註冊

Netty Server執行主要流程

  • Client發起連接CONNECT請求,parentGroup中的NioEventLoop不斷輪循是否有新的客戶端請求,如果有,ACCEPT事件觸發。
  • ACCEPT事件觸發後,parentGroup中NioEventLoop會通過NioServerSocketChannel獲取到對應的代表客戶端的NioSocketChannel,並將其註冊到childGroup中。
  • childGroup中的NioEventLoop不斷檢測自己管理的NioSocketChannel是否有讀寫事件準備好,如果有的話,調用對應的ChannelHandler進行處理。
    在這裏插入圖片描述

Netty Transport-傳輸層

  • 提供了統一的API,支持不同類型的傳輸層
    • OIO-阻塞IO
    • NIO-Java nio
    • Epoll - Linux Epoll(JNI)
    • Local Transport - IntraVM調用
    • Embedded Transport - 供測試使用的嵌入傳輸
    • UDS - Unix套接字的本地傳輸

在這裏插入圖片描述

Netty EventLoop——對應於NIO中的while循環

  • ServerBootstrap:包括2個不同類型的EventLoopGroup:
    • Parent EventLoop:負責處理Accept事件,接收請求
    • Child EventLoop:負責處理讀寫事件
  • EventLoopGroup
    • 包括多個EventLoop
    • 多個EventLoop之間不交互
  • EventLoop:
    • 每個EventLoop對應一個線程
    • 所有連接(channel)都將註冊到一個EventLoop,並且只註冊到一個,整個生命週期中都不會變化
    • 每個EventLoop管理着多個連接(channel)
    • EventLoop來處理連接(Channel)上的讀寫事件
      在這裏插入圖片描述

Netty Buffers——ByteBuf

  • 相比JDK ByteBuffer,更加的易於使用
    • 爲讀寫分別維護單獨的指針,不需要通過flip()進行讀寫模式切換
    • 容量自動伸縮(類似ArrayList,StringBuilder)
    • Fluent API(鏈式調用)
  • 更好地性能
    • 通過內置的CompositeBuffer來減少數據拷貝(Zero copy)
    • 支持內存池,減少GC壓力

ByteBuf分配

  • 不直接通過new來創建,而是通過ByteBufAllocator來創建
    在這裏插入圖片描述
    在這裏插入圖片描述
  • Unpooled的工具類,它提供了靜態的輔助方法來創建未池化的ByteBuf實例
    在這裏插入圖片描述

Netty ChannelHandler——對應於NIO中的客戶邏輯實現handleRead/handleWrite (interceptor pattern)

ChannelHandler——業務處理核心邏輯,用戶自定義

ChannelHandler介紹

Netty 提供2個重要的 ChannelHandler 子接口:
ChannelInboundHandler - 處理進站數據和所有狀態更改事件
ChannelOutboundHandler - 處理出站數據,允許攔截各種操作
在這裏插入圖片描述
注意:我們一般繼承ChannelInboundHandlerAdapter,就可以不用實現所有的方法,只處理我們需要的方法,其餘方法採用默認實現

Netty Channel狀態及轉換

  • Channel的狀態及其轉換如下圖所示:
    在這裏插入圖片描述
    在這裏插入圖片描述
  • 當 ChannelHandler 添加到 ChannelPipeline,或者從ChannelPipeline 移除後,對應的方法將會被調用
    在這裏插入圖片描述

Netty ChannelInboundHandler

ChannelInboundHandler 回調方法在下表中:

  • 當接收到數據或者與之關聯的 Channel 狀態改變時調用
  • 與 Channel 的生命週期接近

在這裏插入圖片描述

Netty ChannelOutboundHandler

在這裏插入圖片描述

Netty——ChannelPipeline

ChannelPipeline 是ChannelHandler容器

  • 包括一系列的ChannelHandler 實例,用於攔截流經一個 Channel 的入站和出站事件
  • 每個Channel都有一個其ChannelPipeline
  • 可以修改 ChannelPipeline 通過動態添加和刪除 ChannelHandler
  • 定義了豐富的API調用來回應入站和出站事件

在這裏插入圖片描述

Netty—— ChannelHandlerContext

ChannelHandlerContext表示 ChannelHandler 和ChannelPipeline 之間的關聯
在 ChannelHandler 添加到 ChannelPipeline 時創建
在這裏插入圖片描述

netty編程示例一

pom文件中引入

<dependency>
    <groupId>io.netty</groupId>
    <artifactId>netty-all</artifactId>
    <version>4.1.30.Final</version>
</dependency>

服務器端代碼:

package test13;

 import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;

public class EchoServerHandler extends ChannelInboundHandlerAdapter {
   @Override
   public void channelRead(ChannelHandlerContext ctx, Object msg) {
       ByteBuf in = (ByteBuf) msg;
       System.out.println(
               "服務器接收到消息:" + in.toString(CharsetUtil.UTF_8));
       ctx.write(in);
   }

   @Override
   public void channelReadComplete(ChannelHandlerContext ctx)
           throws Exception {
       ctx.writeAndFlush(Unpooled.EMPTY_BUFFER)
               .addListener(ChannelFutureListener.CLOSE);
   }

   @Override
   public void exceptionCaught(ChannelHandlerContext ctx,
       Throwable cause) {
       cause.printStackTrace();
       ctx.close();
   }
}
package test13;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;

public class EchoServer {

    public static void main(String[] args) throws Exception {
        int port = 8080;
        if (args != null && args.length > 0) {
            try {
                port = Integer.valueOf(args[0]);
            } catch (NumberFormatException e) {
                // 採用默認值
            }
        }
        new EchoServer().bind(port);
    }

    public void bind(int port) throws Exception {
        // 配置服務端的NIO線程組
    	// 主線程組, 用於接受客戶端的連接,但是不做任何具體業務處理,像老闆一樣,負責接待客戶,不具體服務客戶
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        // 工作線程組, 老闆線程組會把任務丟給他,讓手下線程組去做任務,服務客戶
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
        	 // 類ServerBootstrap用於配置Server相關參數,並啓動Server
            ServerBootstrap b = new ServerBootstrap();
            
            //鏈式調用 
            //配置parentGroup和childGroup
            b.group(bossGroup, workerGroup)
                    //配置Server通道
                    .channel(NioServerSocketChannel.class)
                    //配置通道的ChannelPipeline
                     .childHandler(new ChildChannelHandler());
            
            // 綁定端口,並啓動server,同時設置啓動方式爲同步
            ChannelFuture f = b.bind(port).sync();
            
            System.out.println(EchoServer.class.getName() +
                    " 啓動成功,在地址[" + f.channel().localAddress() +"]上等待客戶請求......");

            // 等待服務端監聽端口關閉
            f.channel().closeFuture().sync();
        } finally {
            // 釋放線程池資源
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    private class ChildChannelHandler extends ChannelInitializer<SocketChannel> {
        @Override
        protected void initChannel(SocketChannel channel) throws Exception {
        	channel.pipeline().addLast(new EchoServerHandler());
        }
    }
}

客戶端代碼

package test13;

import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.util.CharsetUtil;


public class EchoClientHandler extends SimpleChannelInboundHandler<ByteBuf> {
	@Override
	public void channelActive(ChannelHandlerContext ctx) {
		ctx.writeAndFlush(Unpooled.copiedBuffer("這是一個Netty示例程序!\n", CharsetUtil.UTF_8));
	}

	@Override
	public void channelRead0(ChannelHandlerContext ctx, ByteBuf in) {
		System.out.println("客戶端接收到消息: " + in.toString(CharsetUtil.UTF_8));
	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
		cause.printStackTrace();
		ctx.close();
	}
}
package test13;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

public class EchoClient {

    public static void main(String[] args) throws Exception {
        int port = 8080;
        if (args != null && args.length > 0) {
            try {
                port = Integer.valueOf(args[0]);
            } catch (NumberFormatException e) {
             }
        }
        new EchoClient().connect(port, "127.0.0.1");
    }

    public void connect(int port, String host) throws Exception {
        // 工作線程組, 老闆線程組會把任務丟給他,讓手下線程組去做任務,服務客戶
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(group).channel(NioSocketChannel.class)
                    .option(ChannelOption.TCP_NODELAY, true)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        public void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(new EchoClientHandler());
                        }
                    });

            // 發起異步連接操作
            ChannelFuture f = b.connect(host, port).sync();

            // 等待客戶端鏈路關閉
            f.channel().closeFuture().sync();
        } finally {
            // 釋放線程池資源
            group.shutdownGracefully();
        }
    }
}

運行結果如下

服務器端
在這裏插入圖片描述
在這裏插入圖片描述
客戶端:
在這裏插入圖片描述

netty編程示例二

package com.demo.netty.bytebuf;

import io.netty.buffer.*;
import io.netty.util.ByteProcessor;
import java.nio.charset.Charset;

public class ByteBufExamples {
    public static void main(String[] args) {
    	byteBufSetGet();
        System.out.println("============");
    	byteBufWriteRead();
        System.out.println("============");
    	writeAndRead();
        System.out.println("============");
    	byteBufSlice();
        System.out.println("============");
    	byteBufCopy();
        System.out.println("============");
    	byteBufComposite();
        System.out.println("============");
    	directBuffer();
        System.out.println("============");
    	heapBuffer();
        System.out.println("============");
    	   
    }
    
    public static void byteBufSetGet() {
        Charset utf8 = Charset.forName("UTF-8");
        ByteBuf buf = Unpooled.copiedBuffer("Netty in Action rocks!", utf8);
        System.out.println((char)buf.getByte(0));
        int readerIndex = buf.readerIndex();
        int writerIndex = buf.writerIndex();
        System.out.println("readerIndex = " + readerIndex + "; writerIndex = " + writerIndex);
        buf.setByte(0, (byte)'B');
        System.out.println((char)buf.getByte(0));
        System.out.println("readerIndex = " + buf.readerIndex() + "; writerIndex = " + buf.writerIndex());
    }
    
  
    public static void byteBufWriteRead() {
        Charset utf8 = Charset.forName("UTF-8");
        ByteBuf buf = Unpooled.copiedBuffer("Netty in Action rocks!", utf8);
        System.out.println((char)buf.readByte());
        int readerIndex = buf.readerIndex();
        int writerIndex = buf.writerIndex();
        System.out.println("readerIndex = " + readerIndex + "; writerIndex = " + writerIndex);

        buf.writeByte((byte)'?');
        System.out.println("readerIndex = " + buf.readerIndex() + "; writerIndex = " + buf.writerIndex());
        
        buf.readByte();
        System.out.println("readerIndex = " + buf.readerIndex() + "; writerIndex = " + buf.writerIndex());


    }
    
    public static void writeAndRead() {
         ByteBuf buffer = Unpooled.buffer(20); //get reference form somewhere
         int i = 0;
        while (buffer.writableBytes() >= 4) {
            buffer.writeInt(i++);
        }
        
        while (buffer.isReadable()) {
            System.out.println(buffer.readInt());
        }
    }
    
    public static void byteProcessor() {
        Charset utf8 = Charset.forName("UTF-8");

        ByteBuf buffer = Unpooled.copiedBuffer("Netty\r in Action rocks! ", utf8);
        int index = buffer.forEachByte(ByteProcessor.FIND_CR);
    }
    public static void byteBufSlice() {
        Charset utf8 = Charset.forName("UTF-8");
        ByteBuf buf = Unpooled.copiedBuffer("Netty in Action rocks!", utf8);
        ByteBuf sliced = buf.slice(0, 15);
        System.out.println(sliced.toString(utf8));
        buf.setByte(0, (byte)'J');
        System.out.println(sliced.toString(utf8));
    }

    public static void byteBufCopy() {
        Charset utf8 = Charset.forName("UTF-8");
        ByteBuf buf = Unpooled.copiedBuffer("Netty in Action rocks!", utf8);
        ByteBuf copy = buf.copy(0, 15);
        System.out.println(copy.toString(utf8));
        buf.setByte(0, (byte)'J');
        System.out.println(copy.toString(utf8));
    }

    public static void byteBufComposite() {
        CompositeByteBuf messageBuf = Unpooled.compositeBuffer();

        Charset utf8 = Charset.forName("UTF-8");
        ByteBuf headerBuf = Unpooled.copiedBuffer("Header", utf8);
        ByteBuf bodyBuf = Unpooled.copiedBuffer("This is body", utf8);
        messageBuf.addComponents(headerBuf, bodyBuf);
        for (ByteBuf buf : messageBuf) {
            System.out.println(buf.toString());
        }
        
        messageBuf.removeComponent(0); // remove the header
        for (ByteBuf buf : messageBuf) {
            System.out.println(buf.toString());
        }
    }

    public static void heapBuffer() {
        ByteBuf heapBuf = Unpooled.buffer(16);   
        if (heapBuf.hasArray()) {
            int i = 0;
            while (heapBuf.writableBytes() > 0) {
            	heapBuf.writeByte(i++);
            }
            byte[] array = heapBuf.array();
            int offset = heapBuf.arrayOffset() + heapBuf.readerIndex();
            int length = heapBuf.readableBytes();
            handleArray(array, offset, length);
        }
    }

  
    public static void directBuffer() {
        ByteBuf directBuf = Unpooled.directBuffer(16);    
        if (!directBuf.hasArray()) {
            int i = 0;
            while (directBuf.writableBytes() > 0) {
            	directBuf.writeByte(i++);
            }
            int length = directBuf.readableBytes();
            byte[] array = new byte[length];
            directBuf.getBytes(directBuf.readerIndex(), array);
            handleArray(array, 0, length);
        }
    }

 
 
   
    public static void byteBufCompositeArray() {
        CompositeByteBuf compBuf = Unpooled.compositeBuffer();
        int length = compBuf.readableBytes();
        byte[] array = new byte[length];
        compBuf.getBytes(compBuf.readerIndex(), array);
        handleArray(array, 0, array.length);
    }

  


    private static void handleArray(byte[] array, int offset, int len) {
    	for(int i = 0; i<len; i++) {
    		System.out.println(array[offset+i]);
    	}
    }
}

在這裏插入圖片描述
在這裏插入圖片描述
在這裏插入圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章