Netty版Helloworld

本文作者:禹明明,叩丁狼高級講師。原創(chuàng)文章,轉載請注明出處。

作為一個已經(jīng)有了幾年工作經(jīng)驗的JAVA程序員,網(wǎng)絡編程是一個必須接觸的一個領域.如果你還只是停留在會用MVC框架,優(yōu)化只會用用緩存,平常寫寫CRUD的水平,那么你對編程的理解還停留在一個非常表層的階段!
如果想要深入理解各種服務器,Netty就是我們要過的第一道坎

Netty是什么

我在NIO的文章中簡單學習了NIO的使用,現(xiàn)在回想一下,可以發(fā)現(xiàn)NIO的API設計過于復雜,代碼量比較大,使用中需要考慮的細節(jié)也很多,如果對底層了解不深的同學寫出來的代碼可能會有各種各樣的BUG導致服務不夠穩(wěn)定.
Netty就是為了解決這個問題而開發(fā)的一套簡單易用封裝良好的NIO框架,使用Netty可以快速輕松地開發(fā)協(xié)議服務器和客戶端等網(wǎng)絡應用程序。它極大地降低了網(wǎng)絡編程的開發(fā)難度,極大簡化了開發(fā)過程。

Netty可以用來干什么?

  • 開發(fā)自定義的HTTP服務器
  • 開發(fā)自定義的FTP服務器
  • 開發(fā)自定義的UDP服務器
  • 開發(fā)自定義的RPC服務器,例如Dubbo就是基于Netty
  • 開發(fā)自定義的WebSocket服務器
  • 開發(fā)自定義的Proxy服務器,例如MySQL的Proxy服務器等
    總之就是可以開發(fā)定制符合自己需求的各種自定義協(xié)議和服務器

為什么選擇Netty?

NIO框架有Netty , Mina , xSocket , Grizzly等,為什么選擇Netty呢?
雖然NIO框架有很多,但是使用比較廣泛的就是Netty和Mina,從學習成本和后期維護難度上來考慮選擇流行的框架可以降低開發(fā)維護難度和風險.
Netty 和Mina的作者其實都是同一個人Trustin Lee (韓國人),但是Netty出生的更晚, 作者在寫出了Mina之后又搞出了Netty, 所以從這方面來說Netty應該更加完善.
從使用上來講目前很多著名的開源項目比如阿里的Dubbo,Apache Spark , FaceBook Nifty , Google gRPC 等都是基于Netty.
其實最根本的還是超高的性能和簡單的API

Netty架構

這是Netty4.1官方架構圖,我們先大概了解一下


Netty4.x架構總覽.png

Netty中幾個重要概念

Channel

Channel是Netty最核心的接口,一個Channel就是一個聯(lián)絡Socket的通道,通過Channel,你可以對Socket進行各種操作。

ChannelHandler

ChannelHandler:每一個ChannelHandler都用來處理一些邏輯,所有的handler形成一個鏈表結構,作用類似于springMVC中的攔截棧或者一個個的過濾器

ChannelHandlerContext

ChannelHandlerContext是ChannelPipeline的上下文,負責傳遞上下文數(shù)據(jù). ChannelHandlerContext.channel()方法可以得到和Context綁定的Channel,調用ChannelHandlerContext.handler()方法可以得到和Context綁定的Handler。

ChannelPipeline

ChannelPipeline:可以把ChannelPipeline看成是一個ChandlerHandler的鏈表,當需要對Channel進行某種處理的時候,Pipeline負責依次調用每一個Handler進行處理。每個Channel都有一個屬于自己的Pipeline,調用XXChannel.pipeline()方法可以獲得Channel的Pipeline,調用XXPipeline.channel()方法可以獲得Pipeline的Channel。
他們的關系圖如下:

Netty ChannelPipeline原理.png

簡單了解完這些概念之后我們就可以寫個helloworld來體驗一把Netty了

要求JDK1.6或以上
Netty5.0官方已經(jīng)不再支持了,不建議大家使用
目前最新穩(wěn)定版是4.1

添加依賴

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

編寫Server端

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

public class NettyServer {

    public static void main(String[] args) throws InterruptedException {
        //NioEventLoopGroup可以看做是一個線程池,parentGroup用來接收所有請求,childGroup用來處理具體IO任務
        NioEventLoopGroup parentGroup = new NioEventLoopGroup();//用來處理服務器端接受客戶連接
        NioEventLoopGroup childGroup = new NioEventLoopGroup(); //用來進行網(wǎng)絡通信(網(wǎng)絡讀寫)
        ServerBootstrap bootstrap = new ServerBootstrap();      //創(chuàng)建服務器通道配置的輔助工具類
        bootstrap.group(parentGroup,childGroup)                 //配置每個NioEventLoopGroup的用途
                .channel(NioServerSocketChannel.class)          //指定Nio模式為Server模式
                .option(ChannelOption.SO_BACKLOG,1024)    //指定tcp緩沖區(qū)
                .option(ChannelOption.SO_SNDBUF,10*1024)  //指定發(fā)送緩沖區(qū)大小
                .option(ChannelOption.SO_RCVBUF,10*1024)  //指定接收緩沖區(qū)大小
                .option(ChannelOption.SO_KEEPALIVE,Boolean.TRUE)//是否保持連接,默認true
                .childHandler(new ChannelInitializer<SocketChannel>() {//具體的數(shù)據(jù)接收方法
                    @Override
                    protected void initChannel(SocketChannel sc) throws Exception {     //添加ChannelHandler,handler用來自定義消息處理邏輯
                        sc.pipeline().addLast(new ServerHandler());//其實可以添加多個Handler實例對象
                    }
                });

        ChannelFuture cfuture = bootstrap.bind(9999).sync();//異步綁定端口
        cfuture.channel().closeFuture().sync();//阻塞程序,等待關閉
        parentGroup.shutdownGracefully();//關閉應用
        childGroup.shutdownGracefully();
    }
}

服務端涉及到了一個自定義的Handler用來處理接受到的數(shù)據(jù)

/*
ChannelInboundHandlerAdapter 中有很多方法可以覆蓋,這些方法覆蓋了一個請求處理的整個生命周期,
一般來說我們只需要關心channelRead和exceptionCaught 方法
*/
public class ServerHandler extends ChannelInboundHandlerAdapter {

    //數(shù)據(jù)讀取邏輯
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        //ByteBuf如果只用來讀數(shù)據(jù)而沒有writeAndFlush寫數(shù)據(jù)則使用完必須使用調用release()方法,釋放內(nèi)存
        ByteBuf buf = (ByteBuf) msg;
        byte[] bytes = new byte[buf.readableBytes()];
        buf.readBytes(bytes);
        String rev = new String(bytes,"utf-8");
        System.out.println("server收到數(shù)據(jù):"+ rev);
        //給客戶端響應一條數(shù)據(jù)
        ctx.writeAndFlush(Unpooled.copiedBuffer("你好,我是Server".getBytes()))
                //添加監(jiān)聽器,寫出數(shù)據(jù)后關閉通道,原理上只要拿到Futrue對象server端和client端都可以主動關閉,一般在server端關閉較好
                .addListener(ChannelFutureListener.CLOSE);
        buf.release();//釋放ByteBuf
    }

    //拋出異常時處理邏輯
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        super.exceptionCaught(ctx, cause);
        System.out.println("exceptionCaught");
    }
}

編寫Client端

public class NettyClient {
    public static void main(String[] args) throws InterruptedException {
        //NioEventLoopGroup可以看做是一個線程池,客戶端只需要用來處理發(fā)送數(shù)據(jù)任務的NioEventLoopGroup即可
        NioEventLoopGroup group = new NioEventLoopGroup();
        Bootstrap b = new Bootstrap();
        b.group(group)
            .channel(NioSocketChannel.class)//指定Nio模式為Client模式
            .handler(new ChannelInitializer<SocketChannel>() {
                @Override
                protected void initChannel(SocketChannel sc) throws Exception {
                    sc.pipeline().addLast(new ClientHandler());//添加自定義的客戶端消息處理Handler
                }
            });

        ChannelFuture cf = b.connect("127.0.0.1",9999);         //連接指定host:ip
        cf.channel().write(Unpooled.copiedBuffer("Hello I am Client ".getBytes()));//write是寫入緩沖區(qū),
        cf.channel().flush();             //flush緩沖數(shù)據(jù),必須flush!! 或者使用writeAndFlush方法發(fā)送數(shù)據(jù)
        cf.channel().closeFuture().sync();//異步監(jiān)聽,傳輸完畢才執(zhí)行此代碼,然后向下執(zhí)行關閉操作
        group.shutdownGracefully();       //關閉應用,斷開和server連接
    }
}

Client端負責數(shù)據(jù)處理的Handler


public class ClientHandler extends ChannelInboundHandlerAdapter {

    //數(shù)據(jù)讀取邏輯
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        try{
            ByteBuf buf = (ByteBuf) msg;
            byte[] bytes = new byte[buf.readableBytes()];
            buf.readBytes(bytes);
            String rev = new String(bytes,"utf-8");
            System.out.println("Client 收到數(shù)據(jù):"+ rev);
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            ReferenceCountUtil.release(msg);//釋放ByteBuf
        }
    }

    //拋出錯誤時處理邏輯
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        super.exceptionCaught(ctx, cause);
        System.out.println("出錯了...");
    }
}

啟動Server—>啟動Client—>查看日志

# server端日志:這里給大家展示覆蓋了ChannelInboundHandlerAdapter 中所有方法打印的日志,方便大家理解整個處理流程
handlerAdded
channelRegistered
channelActive
18:13:27.672 [nioEventLoopGroup-3-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.maxCapacityPerThread: 32768
18:13:27.672 [nioEventLoopGroup-3-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.maxSharedCapacityFactor: 2
18:13:27.672 [nioEventLoopGroup-3-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.linkCapacity: 16
18:13:27.672 [nioEventLoopGroup-3-1] DEBUG io.netty.util.Recycler - -Dio.netty.recycler.ratio: 8
18:13:27.683 [nioEventLoopGroup-3-1] DEBUG io.netty.buffer.AbstractByteBuf - -Dio.netty.buffer.bytebuf.checkAccessible: true
18:13:27.685 [nioEventLoopGroup-3-1] DEBUG io.netty.util.ResourceLeakDetectorFactory - Loaded default ResourceLeakDetector: io.netty.util.ResourceLeakDetector@125622c7
server收到數(shù)據(jù):Hello I am Client 
channelReadComplete
channelInactive
channelUnregistered
handlerRemoved

# Client端日志:
Client 收到數(shù)據(jù):你好,我是Server
18:13:29.903 [nioEventLoopGroup-2-1] DEBUG io.netty.buffer.PoolThreadCache - Freed 2 thread-local buffer(s) from thread: nioEventLoopGroup-2-1

單從代碼量來看用過NIO的同學應該就可以感受到使用Netty比直接使用NIO要簡單了太多了吧,但是Netty更大的好處其實是進行了完好的封裝,我們可以少關注很多繁瑣的細節(jié)的處理

參考資料:
Netty4.0文檔(官方推薦使用4.x版本):http://netty.io/4.0/api/index.html
w3cSchool的Netty手冊:https://www.w3cschool.cn/netty4userguide/
http://ifeve.com/netty-home/
http://www.cnblogs.com/shanyou/p/4085802.html

想獲取更多技術視頻,請前往叩丁狼官網(wǎng):http://www.wolfcode.cn/openClassWeb_listDetail.html

?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

相關閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容