亚洲在线久爱草,狠狠天天香蕉网,天天搞日日干久草,伊人亚洲日本欧美

Netty 入門案例

1. 前言

本節主要是使用 Netty 來開發服務端和客戶端,Netty 的開發模式基本上都是主啟動類 + 自定義業務 Handler,Netty 是基于責任鏈的模式來管理自定義部分的 Handler,本節帶大家感受一下 Netty 的開發。

需求: 本節主要通過 Netty 來實現我們的第一個 Demo,主要功能是分別建立兩個項目(客戶端和服務端),客戶端向服務端發送 Hello World,服務端接受到數據之后打印到控制臺,并且給客戶端響應。

2. 環境搭建

第一步: 使用 Maven 構建工程,項目結構如下:圖片描述

第二步: netty-demo-clientnetty-demo-server 兩個工程的 pom.xml 導入 Netty 坐標,Netty 主流有三個版本,分別是 3.x、4.x、5.x,一般主流都是使用 4.x 版本。

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

第三步: netty-demo-client 工程相關類

建立兩個類,分別是客戶端啟動類 NettyClient.java 和業務處理類 NettyClientHandler.java。

圖片描述

第四步: netty-demo-server 工程相關類

建立兩個類,分別是服務端啟動類 NettyServer.java 和業務處理類 NettyServerHandler.java
圖片描述

3. 核心流程

客戶端和服務端通信流程圖如下圖所示:
圖片描述
核心步驟說明:

  1. 在 NettyClientHandler 的 channelActive () 方法往服務端發送消息;
  2. 在 NettyServerHandler 的 channelRead () 方法接受消息,并且響應消息給客戶端;
  3. 在 NettyClientHandler 的 channelRead () 方法接受服務端的響應消息。

4. 如何自定義 Handler

在 Netty 的開發當中,最核心就是自定義 Handler,通常根據不同的業務定義不同的 Handler。自定義 Handler 一般分為三個核心步驟:

  1. 需要繼承 ChannelInboundHandlerAdapter 類;

實例:

public class TestHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
     	//業務處理   
    }
}

  1. 重寫幾個核心的方法,其中 channelRead () 是業務邏輯編寫,使用最多;
方法名稱 觸發時機 常見業務場景
channelActive 連接就緒時觸發 連接時進行登錄認證
channelRead 通道有數據可讀取時觸發 讀取數據并且做處理,這個是用的最多的方法
channelInactive 連接斷開時觸發 連接斷開,刪除服務端對于的 Session 關系;也可以在這里實現斷開重新
exceptionCaught 發生異常時觸發 發生日常時,做日志記錄
  1. 把 Handler 添加到 Pipeline 管道里面

實例:

bootstrap.handler(new ChannelInitializer<SocketChannel>() {
    @Override
    public void initChannel(SocketChannel ch) {
        //自定義業務 Handler
        ch.pipeline().addLast(new TestHandler());
    }
});

5. 服務端實現

5.1 服務端啟動類

public class NettyServer {
    public static void main(String[] args) {
        //線程組-主要是監聽客戶端請求
        NioEventLoopGroup bossGroup = new NioEventLoopGroup();
        //線程組-主要是處理具體業務
        NioEventLoopGroup workerGroup = new NioEventLoopGroup();
		//啟動類
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap
            	//指定線程組
                .group(bossGroup, workerGroup)
            	//指定 NIO 模式
                .channel(NioServerSocketChannel.class)
            	//雙向鏈表管理
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    protected void initChannel(NioSocketChannel ch) {
                        //責任鏈,指定自定義處理業務的 Handler
                        ch.pipeline().addLast(new NettyServerHandler());
                    }
                });
		//綁定端口號
        serverBootstrap.bind(80);
    }
}

代碼說明:

  1. 以上都是模板代碼,需要變動的是根據不同的業務自定義對應的 Handler,并且在 initChannel () 添加邏輯處理器;
  2. 根據實際情況指定 bind () 方法的端口號,注意的是端口號不能和其它端口號沖突;
  3. 這里大家先熟練掌握模板代碼的編寫,后面章節會講解 NioEventLoopGroup、Pipeline 等核心組件的原理。

5.2 自定義 Handler

public class NettyServerHandler extends ChannelInboundHandlerAdapter {
    //接受客戶端端響應時觸發該事件
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        //轉換為 ByteBuf 緩沖區(底層是 byte[] 數組)
        ByteBuf buffer=(ByteBuf)msg;
        //定義一個 byte[] 數組
        byte[] bytes=new byte[buffer.readableBytes()];
        //緩沖區把數據寫到 byte[] 數組
        buffer.readBytes(bytes);
        //把 byte[] 轉換字符串
        String req=new String(bytes,"UTF-8");
        System.out.println("客戶端請求:"+req);
        
        //給客戶端響應信息>>>>>>>>>>>>>>>>>>>>>>>>>>>>
        String res="Hello World>>>>Client";
        //把字符串轉換 ByteBuf
        ByteBuf buf=getByteBuf(ctx,res);
        
		//把 ByteBuf 寫到通道并且刷新
        ctx.writeAndFlush(buf);
    }
    
    private ByteBuf getByteBuf(ChannelHandlerContext ctx,String str) {
        // 1. 獲取二進制抽象 ByteBuf
        ByteBuf buffer = ctx.alloc().buffer();
        // 2. 準備數據,指定字符串的字符集為 utf-8
        byte[] bytes = str.getBytes(Charset.forName("utf-8"));
        // 3. 填充數據到 ByteBuf
        buffer.writeBytes(bytes);
        return buffer;
    }
}

代碼說明:

  1. 這個邏輯處理器繼承自 ChannelInboundHandlerAdapter,然后覆蓋了 channelRead () 方法;

  2. channelRead () 方法,接受客戶端請求數據時會觸發該方法,一般是用來處理具體的業務;

  3. Netty 是面向 ByteBuf 通訊的,接受數據和響應數據都需要轉換 ByteBuf,ByteBuf 的 API 后面再詳細講解,這里我們需要知道的是常見創建 ByteBuf 有常見兩種方式,①通過 Unpooled 非池化工具類來操作;②通過 ctx.alloc().buffer() 來獲取。最后我們調用 ctx.channel().writeAndFlush() 把數據寫到服務端。

6. 客戶端實現

6.1 客戶端啟動類

public class NettyClient {
    public static void main(String[] args) throws InterruptedException {
        NioEventLoopGroup workerGroup = new NioEventLoopGroup();

        Bootstrap bootstrap = new Bootstrap();
        bootstrap
                // 1.指定線程模型
                .group(workerGroup)
                // 2.指定 IO 類型為 NIO
                .channel(NioSocketChannel.class)
                // 3.IO 處理邏輯
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    public void initChannel(SocketChannel ch) {
                        //自定義業務 Handler
                        ch.pipeline().addLast(new NettyClientHandler());
                    }
                });
        // 4.建立連接
        ChannelFuture future=bootstrap.connect("127.0.0.1", 80).sync();
    }
}

代碼說明:

  1. 以上都是模板代碼,需要變動的是根據不同的業務自定義對應的 Handler,并且在 initChannel () 添加邏輯處理器;
  2. connect () 方法,指定對應服務端 ip 和 port。

6.2 自定義 Handler

public class NettyClientHandler extends ChannelInboundHandlerAdapter {
    //客戶端連接成功之后觸發該事件,只會觸發一次
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        ctx.channel().writeAndFlush(Unpooled.copiedBuffer("Hello World".getBytes()));
    }

    //接受服務端響應時觸發該事件
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf buffer=(ByteBuf)msg;
        byte[] bytes=new byte[buffer.readableBytes()];
        buffer.readBytes(bytes);
        String res=new String(bytes,"UTF-8");
        System.out.println("服務端響應:"+res);
    }
}

代碼說明:

  1. 這個邏輯處理器繼承自 ChannelInboundHandlerAdapter,然后覆蓋了 channelActive () 和 channelRead () 方法;
  2. channelActive (),這個方法會在客戶端連接建立成功之后被調用,可以在這個方法里面,做一些初始化的工作,該方法僅被調用一次;
  3. channelRead 方法,在接受客戶端響應時觸發,會觸發多次。

7. 測試效果

服務端打?。?br> 圖片描述

客戶端打?。?br> 圖片描述

8. 視頻演示

9. 小結

通過以上的代碼,我們主要實現了客戶端和服務端之間的通訊,需要掌握以下關鍵點:

  1. 客戶端和服務端的啟動類代碼,這個基本上是固定寫法;
  2. 掌握 Handler 的作用以及如何自定義,幾個核心方法的觸發時機以及常見的應用場景;
  3. 和傳統的 socket 編程不同的是,Netty 里面數據是以 ByteBuf 為單位的。