高性能IO框架Netty一-第一個Netty程序

目錄

一、Netty 簡介

1、Netty是什麼?

2、爲什麼要用Netty?

3、爲什麼Netty使用NIO而不是AIO?

4、爲什麼不用Netty5

二、Hello Netty!

1、NettyServer

2、NettyServerHandler

3、NettyClient

4、NettyClientHandler

5、程序演示

6、總結


Netty作爲一個高性能IO框架,基本上所有使用JAVA技術棧的大廠,底層的IO通信框架都是通過Netty實現的。例如 dubbo,Spring gateway等等。所以不管是已經工作的還是在校學生。學會Netty,在你面試大廠的時候,無疑都是加分項。是所有從事JAVA工作的必備技能。完整介紹Netty的書籍不是很多,主要有華爲大牛李林峯的《Netty權威指南》,和上年剛出的《Netty進階:跟着案例學Netty》,以及國外的譯本《Netty實戰》。初學者建議選擇《Netty權威指南》或者《Netty實戰》。等學會之後在看《Netty進階》。

中間一個月比較忙,鴿了三天,今天開始更新Netty相關文章.

一、Netty 簡介

1、Netty是什麼?

以下資料摘自百度百科,總結的已經比較好了。

Netty是由Jboss提供的一個Java開源框架,現爲 Github上的獨立項目。Netty提供異步的、時間驅動的網絡應用程序框架和工具,用以快速開發高性能、高可靠性的網絡服務器和客戶端程序。

也就是說,Netty 是一個基於NIO的客戶、服務器端編程框架,使用Netty 可以確保你快速和簡單的開發出一個網絡應用,例如實現了某種協議的客戶、服務端應用。Netty相當於簡化和流線化了網絡應用的編程開發過程,例如:基於TCP和UDP的socket服務開發。

2、爲什麼要用Netty?

1、雖然JAVA NIO框架提供了 多路複用IO的支持,但是並沒有提供上層“信息格式”的良好封裝。例如前兩者並沒有提供針對 Protocol Buffer、JSON這些信息格式的封裝,但是Netty框架提供了這些數據格式封裝(基於責任鏈模式的編碼和解碼功能);

2、NIO的類庫和API相當複雜,使用它來開發,需要非常熟練地掌握Selector、ByteBuffer、ServerSocketChannel、SocketChannel等,需要很多額外的編程技能來輔助使用NIO,例如,因爲NIO涉及了Reactor線程模型,所以必須必須對多線程和網絡編程非常熟悉才能寫出高質量的NIO程序

3、要編寫一個可靠的、易維護的、高性能的NIO服務器應用。除了框架本身要兼容實現各類操作系統的實現外。更重要的是它應該還要處理很多上層特有服務,例如:客戶端的權限、還有上面提到的信息格式封裝、簡單的數據讀取,斷連重連,半包讀寫,心跳等等,這些Netty框架都提供了響應的支持。

4、高性能,擁有比核心 Java API 更好的吞吐量,較低的延時,並且資源消耗更少,這個得益於共享池和重用,減少內存拷貝,實現0拷貝。

4、JAVA NIO框架存在一個poll/epoll bug:Selector doesn’t block on Selector.select(timeout),不能block意味着CPU的使用率會變成100%(這是底層JNI的問題,上層要處理這個異常實際上也好辦)。當然這個bug只有在Linux內核上才能重現。

這個問題在JDK 1.7版本中還沒有被完全解決,但是Netty已經將這個bug進行了處理。

這個Bug與操作系統機制有關係的,JDK雖然僅僅是一個兼容各個操作系統平臺的軟件,但在JDK5和JDK6最初的版本中(嚴格意義上來將,JDK部分版本都是),這個問題並沒有解決,而將這個帽子拋給了操作系統方,這也就是這個bug最終一直到2013年才最終修復的原因(JDK7和JDK8之間)。

3、爲什麼Netty使用NIO而不是AIO?

Netty不看重Windows上的使用,在Linux系統上,AIO的底層實現仍使用EPOLL,沒有很好實現AIO,因此在性能上沒有明顯的優勢,而且被JDK封裝了一層不容易深度優化。

AIO還有個缺點是接收數據需要預先分配緩存, 而不是NIO那種需要接收時才需要分配緩存, 所以對連接數量非常大但流量小的情況, 內存浪費很多。

據說Linux上AIO不夠成熟,處理回調結果速度跟不上處理需求,有點像外賣員太少,顧客太多,供不應求,造成處理速度有瓶頸。

作者原話:

Not faster than NIO (epoll) on unix systems (which is true)

There is no daragram suppport

Unnecessary threading model (too much abstraction without usage)

4、爲什麼不用Netty5

1. netty5 中使用了 ForkJoinPool,增加了代碼的複雜度,但是對性能的改善卻不明顯

2. 多個分支的代碼同步工作量很大

3. 作者覺得當下還不到發佈一個新版本的時候

4. 在發佈版本之前,還有更多問題需要調查一下,比如是否應該廢棄 exceptionCaught, 是否暴露EventExecutorChooser等等。

二、Hello Netty!

接下來我們就開始從一個簡單地demo,進入netty的世界吧。

該demo實現了創建一個Netty服務器和一個netty客戶端,服務端接收到客戶端請求的時候便打印相應的信息。

1、NettyServer

/**
 * 作者:DarkKing
 * 創建日期:2019/10/02
 * 類說明:netty服務端
 *
 */
public class NettyServer  {

    private final int port;

    public NettyServer(int port) {
        this.port = port;
    }

    public static void main(String[] args) throws InterruptedException {
        int port = 9999;
        NettyServer echoServer = new NettyServer(port);
        System.out.println("服務器啓動");
        echoServer.start();
        System.out.println("服務器關閉");
    }

    public void start() throws InterruptedException {
        final NettyServerHandler serverHandler = new NettyServerHandler();
        /*線程組*/
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            /*服務端啓動必須*/
            ServerBootstrap b = new ServerBootstrap();
            b.group(group)/*將線程組傳入*/
                    .channel(NioServerSocketChannel.class)/*指定使用NIO進行網絡傳輸*/
                    .localAddress(new InetSocketAddress(port))/*指定服務器監聽端口*/
                    /*服務端每接收到一個連接請求,就會新啓一個socket通信,也就是channel,
                    所以下面這段代碼的作用就是爲這個子channel增加handle*/
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        protected void initChannel(SocketChannel ch) throws Exception {
                            /*添加到該子channel的pipeline的尾部*/
                            ch.pipeline().addLast(serverHandler);
                        }
                    });
            ChannelFuture f = b.bind().sync();/*異步綁定到服務器,sync()會阻塞直到完成*/
            f.channel().closeFuture().sync();/*阻塞直到服務器的channel關閉*/

        } finally {
            group.shutdownGracefully().sync();/*優雅關閉線程組*/
        }
    }
}

2、NettyServerHandler

/**
 * 作者:DarkKing
 * 創建日期:2019/10/02
 * 類說明:netty服務端處理handler
 *
 */
public class NettyServerHandler extends ChannelInboundHandlerAdapter {

    /*客戶端讀到數據以後,就會執行*/
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf in = (ByteBuf)msg;
        System.out.println("Server accept"+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)
            throws Exception {
        cause.printStackTrace();
        ctx.close();
    }
}

3、NettyClient

public class NettyClient {

    private final int port;
    private final String host;


    public NettyClient(int port, String host) {
        this.port = port;
        this.host = host;
    }

    public void start() throws InterruptedException {
        /*線程組*/
        EventLoopGroup group = new NioEventLoopGroup();
        try{
            /*客戶端啓動必備*/
            Bootstrap b = new Bootstrap();
            b.group(group)/*把線程組傳入*/
                    /*指定使用NIO進行網絡傳輸*/
                    .channel(NioSocketChannel.class)
                    .remoteAddress(new InetSocketAddress(host,port))
                    .handler(new NettyClientHandler());
            /*連接到遠程節點,阻塞直到連接完成*/
            ChannelFuture f = b.connect().sync();
            /*阻塞程序,直到Channel發生了關閉*/
            f.channel().closeFuture().sync();
        }finally {
            group.shutdownGracefully().sync();
        }
    }
    public static void main(String[] args) throws InterruptedException {
        new NettyClient(9999,"127.0.0.1").start();
    }
}

4、NettyClientHandler

/**
 * 作者:DarkKing
 * 創建日期:2019/10/02
 * 類說明:netty客戶端處理器
 *
 */
public class NettyClientHandler extends SimpleChannelInboundHandler<ByteBuf> {

    /*客戶端讀到數據以後,就會執行*/
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg)
            throws Exception {
        System.out.println("client acccept:"+msg.toString(CharsetUtil.UTF_8));
    }

    /*連接建立以後*/
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        ctx.writeAndFlush(Unpooled.copiedBuffer(
                "Hello Netty",CharsetUtil.UTF_8));
        //ctx.fireChannelActive();
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
            throws Exception {
        cause.printStackTrace();

        ctx.close();
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        super.userEventTriggered(ctx, evt);
    }
}

5、程序演示

1、啓動NettyServer,打印服務器啓動

2、啓動客戶端,客戶端連接服務器端成功,併發送Hello,Netty!並收到服務器端返回的消息,Hello,Netty!

3、服務器端打印 Server accept:Hello Netty

6、總結

根據上一章網絡編程四-原生JDK的NIO及其應用相比,使用Netty大大簡化了我們的開發工作量,並將原生JDK複雜的Selector、ByteBuffer、ServerSocketChannel、SocketChannel等組件進行封裝。使我們開發者不需要關係具體底層的運行原理和機制。進行模板化的開發。提高開發的效率、以及容錯率。

本章主要簡單介紹了下Netty的入門。代碼已放到github上,https://github.com/379685397/netty。下章對Netty的組件進行介紹。

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