1. 程式人生 > 其它 >初識netty之永遠的helloworld

初識netty之永遠的helloworld

一、Netty概述

官方的介紹:
Netty is an asynchronous event-driven network application framework
for rapid development of maintainable high performance protocol servers & clients.
Netty是 一個非同步事件驅動的網路應用程式框架,用於快速開發可維護的高效能協議伺服器和客戶端

二、為什麼使用Netty

從官網上介紹,Netty是一個網路應用程式框架,開發伺服器和客戶端。也就是用於網路程式設計的一個框架。既然是網路程式設計,Socket就不談了,為什麼不用NIO呢?

2.1 NIO的缺點

  • NIO的類庫和API繁雜,學習成本高,你需要熟練掌握Selector、ServerSocketChannel、SocketChannel、ByteBuffer等
  • 需要熟悉Java多執行緒程式設計。這是因為NIO程式設計涉及到Reactor模式,你必須對多執行緒和網路程式設計非常熟悉,才能寫出高質量的NIO程式
  • 臭名昭著的epoll bug。它會導致Selector空輪詢,最終導致CPU 100%。直到JDK1.7版本依然沒得到根本性的解決

2.2 Netty的優點

  • API使用簡單,學習成本低。
  • 功能強大,內建了多種解碼編碼器,支援多種協議。
  • 效能高,對比其他主流的NIO框架,Netty的效能最優。
  • 社群活躍,發現BUG會及時修復,迭代版本週期短,不斷加入新的功能。
  • Dubbo、Elasticsearch都採用了Netty,質量得到驗證。

三、架構

  • 綠色的部分Core核心模組,包括零拷貝、API庫、可擴充套件的事件模型。
  • 橙色部分Protocol Support協議支援,包括Http協議、webSocket、SSL(安全套接字協議)、谷歌Protobuf協議、zlib/gzip壓縮與解壓縮、Large File Transfer大檔案傳輸等等
  • 紅色的部分Transport Services傳輸服務,包括Socket、Datagram、Http Tunnel等等。
    以上可看出Netty的功能、協議、傳輸方式都比較全,比較強大。

四、業界使用netty

五、搭建永遠的 Hello Word

5.1 引入Maven依賴

使用的版本是4.1.20,相對比較穩定的一個版本。
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <java.version>1.8</java.version>
        <netty-all.version>4.1.20.Final</netty-all.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>${netty-all.version}</version>
        </dependency>
    </dependencies>

5.2程式碼編寫

專案結構

  netty-helloworld
    ├── client
      ├── Client.class -- 客戶端啟動類
      ├── ClientHandler.class -- 客戶端邏輯處理類
      ├── ClientHandler.class -- 客戶端初始化類
    ├── server 
      ├── Server.class -- 服務端啟動類
      ├── ServerHandler -- 服務端邏輯處理類
      ├── ServerInitializer -- 服務端初始化類

服務端程式碼

/**
 * @author wuzhixuan
 * @version 1.0.0
 * @ClassName Server.java
 * @Description netty 服務端
 * @createTime 2021年11月18日 11:22:00
 */
public class Server {

    public static void main(String[] args) {
        /* 建立兩個EventLoopGroup物件*/
        // 建立boss執行緒組 用於服務端介面客戶端的連結
        EventLoopGroup boss = new NioEventLoopGroup();
        // 建立worker執行緒組,用於客戶端的進行SocketChannel資料讀寫
        EventLoopGroup worker = new NioEventLoopGroup();
        try {


            // 建立 ServerBootstrap 物件,
            ServerBootstrap bootstrap = new ServerBootstrap();

            //設定使用的EventLoopGroup
            bootstrap.group(boss, worker)
                    //設定要被例項化的為 NioServerSocketChannel 類
                    .channel(NioServerSocketChannel.class)
                    // 設定 NioServerSocketChannel 的處理器
                    .handler(new LoggingHandler(LogLevel.INFO))
                    // 設定連入服務端的 Client 的 SocketChannel 的處理器
                    .childHandler(new ServerInitializer());

            // 繫結埠,啟動客戶端
            final ChannelFuture bind = bootstrap.bind(8888);
            // 監聽服務端關閉,並阻塞等待
            bind.channel().closeFuture().sync();


        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            boss.shutdownGracefully();
            worker.shutdownGracefully();
        }

    }
}

連入服務端的 Client 的 SocketChannel 的處理器

/**
 * @author wuzhixuan
 * @version 1.0.0
 * @ClassName ServerInitializer.java
 * @Description 連入服務端的 Client 的 SocketChannel 的處理器
 * @createTime 2021年11月18日 11:41:00
 */
public class ServerInitializer extends ChannelInitializer<SocketChannel> {
    private static final StringDecoder DECODER = new StringDecoder();
    private static final StringEncoder ENCODER = new StringEncoder();

    private static final ServerHandler SERVER_HANDLER = new ServerHandler();

    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        final ChannelPipeline pipeline = socketChannel.pipeline();

        // 新增幀限定符來防止粘包現象
        pipeline.addLast(new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
        pipeline.addLast(DECODER);
        pipeline.addLast(ENCODER);

        // 業務邏輯實現類
        pipeline.addLast(SERVER_HANDLER);

    }
}

服務端具體業務邏輯

/**
 * @author wuzhixuan
 * @version 1.0.0
 * @ClassName ServerHandler.java
 * @Description TODO
 * @createTime 2021年11月18日 11:48:00
 *
 * 使用Netty編寫業務層的程式碼,我們需要繼承ChannelInboundHandlerAdapter 或SimpleChannelInboundHandler類,
 * 在這裡順便說下它們兩的區別吧。
 * 1.繼承SimpleChannelInboundHandler類之後,會在接收到資料後會自動release掉資料佔用的Bytebuffer資源。並且繼承該類需要指定資料格式。
 * 2.繼承ChannelInboundHandlerAdapter則不會自動釋放,需要手動呼叫ReferenceCountUtil.release()等方法進行釋放。繼承該類不需要指定資料格式。
 * 個人推薦服務端繼承ChannelInboundHandlerAdapter,手動進行釋放,防止資料未處理完就自動釋放了。而且服務端可能有多個客戶端進行連線,並且每一個客戶端請求的資料格式都不一致,這時便可以進行相應的處理。
 * 客戶端根據情況可以繼承SimpleChannelInboundHandler類。好處是直接指定好傳輸的資料格式,就不需要再進行格式的轉換了
 */
public class ServerHandler extends SimpleChannelInboundHandler<String> {


    /**
     * 建立連線時,傳送一條慶祝訊息
     *
     * @param ctx
     * @throws Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        ctx.write("Welcome to " + InetAddress.getLocalHost().getHostName() + "!\r\n");
        ctx.write("It is " + new Date() + " now.\r\n");
        ctx.flush();

    }

    /**
     * 業務邏輯處理
     * @param ctx
     * @param request
     * @throws Exception
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, String request) throws Exception {
        String response;
        boolean close = false;
        if (request.isEmpty()) {
            response = "Please type something.\r\n";
        } else if ("bye".equals(request.toLowerCase())) {
            response = "Have a good day!\r\n";
            close = true;
        } else {
            response = "Did you say '" + request + "'?\r\n";
        }

        ChannelFuture future = ctx.write(response);

        if (close) {
            future.addListener(ChannelFutureListener.CLOSE);
        }
    }

    /**
     * 異常處理
     * @param ctx
     * @param cause
     * @throws Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        cause.printStackTrace();
        ctx.close();
    }

    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        ctx.flush();
    }
}

客戶端

客戶端主類

/**
 * @author wuzhixuan
 * @version 1.0.0
 * @ClassName Client.java
 * @Description 客戶端程式碼實現
 * @createTime 2021年11月18日 11:56:00
 */
public class Client {


    public static void main(String[] args) {
        EventLoopGroup group = new NioEventLoopGroup();

        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.group(group)
                    .channel(NioSocketChannel.class)
                    .handler(new ClientInitializer());

            Channel ch = bootstrap.connect("127.0.0.1",8888).sync().channel();


            ChannelFuture lastWriteFuture = null;
            BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
            for (;;) {
                String line = in.readLine();
                if (line == null) {
                    break;
                }

                // Sends the received line to the server.
                lastWriteFuture = ch.writeAndFlush(line + "\r\n");

                // If user typed the 'bye' command, wait until the server closes
                // the connection.
                if ("bye".equals(line.toLowerCase())) {
                    ch.closeFuture().sync();
                    break;
                }
            }
            // Wait until all messages are flushed before closing the channel.
            if (lastWriteFuture != null) {
                lastWriteFuture.sync();
            }
        } catch (InterruptedException | IOException e) {
            e.printStackTrace();
        } finally {
            group.shutdownGracefully();
        }
        
    }
}
/**
 * @author wuzhixuan
 * @version 1.0.0
 * @ClassName ClientHandler.java
 * @Description TODO
 * @createTime 2021年11月18日 14:12:00
 */
public class ClientHandler extends SimpleChannelInboundHandler<String> {


    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, String msg) throws Exception {
        System.err.println(msg);
    }

    //異常資料捕獲
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        ctx.close();
    }

}
/**
 * @author wuzhixuan
 * @version 1.0.0
 * @ClassName ClientInitializer.java
 * @Description TODO
 * @createTime 2021年11月18日 14:10:00
 */
public class ClientInitializer extends ChannelInitializer<SocketChannel> {

    private static final StringDecoder DECODER = new StringDecoder();
    private static final StringEncoder ENCODER = new StringEncoder();

    private static final ClientHandler CLIENT_HANDLER = new ClientHandler();
    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        ChannelPipeline pipeline = socketChannel.pipeline();
        pipeline.addLast(new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter()));
        pipeline.addLast(DECODER);
        pipeline.addLast(ENCODER);

        pipeline.addLast(CLIENT_HANDLER);
    }
}

到這裡netty就簡單搭建完成了

服務端輸出

十一月 18, 2021 2:15:23 下午 io.netty.handler.logging.LoggingHandler channelRegistered
資訊: [id: 0x54ec564b] REGISTERED
十一月 18, 2021 2:15:24 下午 io.netty.handler.logging.LoggingHandler bind
資訊: [id: 0x54ec564b] BIND: 0.0.0.0/0.0.0.0:8888
十一月 18, 2021 2:15:24 下午 io.netty.handler.logging.LoggingHandler channelActive
資訊: [id: 0x54ec564b, L:/0:0:0:0:0:0:0:0:8888] ACTIVE
十一月 18, 2021 2:15:35 下午 io.netty.handler.logging.LoggingHandler channelRead
資訊: [id: 0x54ec564b, L:/0:0:0:0:0:0:0:0:8888] RECEIVED: [id: 0xb0450fea, L:/127.0.0.1:8888 - R:/127.0.0.1:50272]

客戶端輸出

Connected to the target VM, address: '127.0.0.1:50101', transport: 'socket'
Welcome to WuZhiXuan!
It is Thu Nov 18 14:15:35 CST 2021 now.
hello
Did you say 'hello'?
Please type something.
Please type something.
s
Did you say 's'?

Please type something.
sad
Did you say 'sad'?
asd
Did you say 'asd'?
s
Did you say 's'?