天天看點

Netty服務端與用戶端雙向通信

作者:技術探究猿
Netty服務端與用戶端雙向通信

要了解Netty服務端與用戶端的雙向通信,我們首先要了解服務端與用戶端的啟動過程,然後再了解服務端發送/接收資料的過程以及用戶端發送/接收資料的過程。本文将通過代碼示例的方式讓大家對Netty有一個初步的認識。

服務端啟動過程

流程:

服務端引導類 ServerBootstrap

1.建立兩個NioEventLoopGroup

bossGroup: 監聽端口

workerGroup: 處理每一條連接配接的資料讀寫的線程組

2.配置兩大線程組,通過.group(bossGroup, workerGroup)給引導類配置兩大線程組,即指定線程模型

3.指定服務端的IO模型,通過.channel(NioServerSocketChannel.class)來指定 IO 模型

4.調用childHandler()方法,給引導類建立一個ChannelInitializer,定義連接配接資料讀寫處理邏輯

代碼:

public static void main(String[] args) {
        // 建立一個引導類
        ServerBootstrap serverBootstrap = new ServerBootstrap();
        // 1.建立兩個NioEventLoopGroup
        // bossGroup 監聽端口
        NioEventLoopGroup bossGroup = new NioEventLoopGroup();
        // workerGroup 處理每一條連接配接的資料讀寫的線程組
        NioEventLoopGroup workerGroup = new NioEventLoopGroup();
        serverBootstrap
            // 2.配置兩大線程組
            .group(bossGroup, workerGroup)
            // 3.指定服務端的IO模型
            .channel(NioServerSocketChannel.class)
            // 4.調用childHandler()方法,給引導類建立一個ChannelInitializer,定義連接配接資料讀寫處理邏輯
            .childHandler(new ChannelInitializer<NioSocketChannel>() {
                protected void initChannel(NioSocketChannel ch) {
                }
            }).bind(8000);
    }
           

總結:要啟動一個Netty服務端,必須要指定三類屬性,分别是線程模型、IO 模型、連接配接讀寫處理邏輯,然後再調用bind(8000),我們就可以在本地綁定一個 8000 端口啟動起來。

用戶端啟動流程

流程:

用戶端啟動的引導類是 Bootstrap

  1. 與服務端的啟動一樣,我們需要給它指定線程模型,驅動着連接配接的資料讀寫
  2. 指定 IO 模型為 NioSocketChannel,表示 IO 模型為 NIO
  3. 給引導類指定一個 handler,這裡主要就是定義連接配接的業務處理邏輯
  4. 配置完線程模型、IO 模型、業務處理邏輯之後,調用 connect 方法進行連接配接,由于 connect 方法傳回的是一個 Future,是一個異步方法,可以通過 addListener 方法可以監聽到連接配接是否成功,進而列印出連接配接資訊

代碼:

public static void main(String[] args) throws InterruptedException {
 // 用戶端引導類
 Bootstrap bootstrap = new Bootstrap();
 NioEventLoopGroup group = new NioEventLoopGroup();
 bootstrap
  // 1.指定線程模型
  .group(group)
  // 2.指定IO類型為NIO
  .channel(NioSocketChannel.class)
  // IO 處理邏輯
  .handler(new ChannelInitializer<Channel>() {
   @Override
   protected void initChannel(Channel ch) {
    ch.pipeline().addLast(new StringEncoder());
   }
  });
 bootstrap.connect("127.0.0.1", 8000).addListener(future -> {
  if (future.isSuccess()) {
   System.out.println("連接配接成功!");
  } else {
   System.err.println("連接配接失敗!");
  }
 });
}
           

用戶端與服務端雙向通信

服務端讀/寫資料

通過服務端引導類 ServerBootstrap 的childHandler()方法指定服務端相關資料的讀寫邏輯,即 在 initChannel() 方法裡面給服務端添加一個邏輯處理器,負責向用戶端寫資料和讀用戶端的資料

.childHandler(new ChannelInitializer<NioSocketChannel>() {
      protected void initChannel(NioSocketChannel ch) {
          ch.pipeline().addLast(new FirstServerHandler());
      }
  }).bind(8000);
           

FirstServerHandler

writeAndFlush():寫資料至用戶端 channelRead():從用戶端讀資料

public class FirstServerHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf byteBuf = (ByteBuf) msg;
        System.out.println(new Date() + ": 服務端讀到資料 -> " + byteBuf.toString(Charset.forName("utf-8")));
        // 回複資料到用戶端
        System.out.println(new Date() + ": 服務端寫出資料");
        ByteBuf out = getByteBuf(ctx);
        ctx.channel().writeAndFlush(out);
    }
    private ByteBuf getByteBuf(ChannelHandlerContext ctx) {
        byte[] bytes = "你好,歡迎關注我的微信公衆号,《技術探究猿‍》!".getBytes(Charset.forName("utf-8"));
        ByteBuf buffer = ctx.alloc().buffer();
        buffer.writeBytes(bytes);
        return buffer;
    }
}
           

用戶端讀/寫資料

通過用戶端引導類 Bootstrap 的 handler() 方法指定用戶端相關的資料讀寫邏輯,即在 initChannel() 方法裡面給用戶端添加一個邏輯處理器,用來負責向服務端寫資料和讀服務端的資料

.handler(new ChannelInitializer<SocketChannel>() {
    @Override
    protected void initChannel(SocketChannel ch) {
        ch.pipeline().addLast(new FirstClientHandler());
    }
});
           

FirstClientHandler

writeAndFlush():寫資料至服務端

channelRead():從服務端讀資料

public class FirstClientHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println(new Date() + ": 用戶端寫出資料");
        // 1. 擷取資料
        ByteBuf buffer = getByteBuf(ctx);
        // 2. 寫資料
        ctx.channel().writeAndFlush(buffer);
    }
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        ByteBuf byteBuf = (ByteBuf) msg;
        System.out.println(new Date() + ": 用戶端讀到資料 -> " + byteBuf.toString(Charset.forName("utf-8")));
    }
    private ByteBuf getByteBuf(ChannelHandlerContext ctx) {
        // 1. 擷取二進制抽象 ByteBuf
        ByteBuf buffer = ctx.alloc().buffer();
        // 2. 準備資料,指定字元串的字元集為 utf-8
        byte[] bytes = "你好,技術猿!".getBytes(Charset.forName("utf-8"));
        // 3. 填充資料到 ByteBuf
        buffer.writeBytes(bytes);
        return buffer;
    }
}
           

寫在最後

一張圖總結一下知識點:

Netty服務端與用戶端雙向通信

總的來說,Netty的用戶端和服務端的啟動過程還是比較簡單的,感興趣的小夥伴可以跑跑代碼,對Netty有一個初步的印象。接下來将會帶大家了解一下Netty的核心元件Channel、EventLoop、ChannelHandler、EventLoopGroup等

繼續閱讀