从实战开始,带你深入了解Netty各个组件和ByteBuf

网络 通信技术
本篇文章想来从实战开始,带我深入了解Netty各个组件是做什么?ByteBuf执行原理又是怎样的?

 [[358902]]

上文对IO模型和Reactor模型进行讲解,是不是感觉有点懵懵的。哈哈哈,反正我并没有对其有深入见解。我是这样安慰自己的,知识在不断的反复学习和思考中有新的感悟。不气馁,继续新的征程。本篇文章想来从实战开始,带我深入了解Netty各个组件是做什么?ByteBuf执行原理又是怎样的?

01一 第一个Netty实例

用Netty实现通信。说白了就是客户端向服务端发消息,服务端接收消息并给客户端响应。所以我来看看服务端和客户端是如何实现的?

11.1 服务端

1. 依赖

  1. <?xml version="1.0" encoding="UTF-8"?> 
  2. <project xmlns="http://maven.apache.org/POM/4.0.0" 
  3.          xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
  4.          xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"
  5.     <modelVersion>4.0.0</modelVersion> 
  6.  
  7.     <groupId>com.haopt.iot</groupId> 
  8.     <artifactId>first-netty</artifactId> 
  9.     <packaging>jar</packaging> 
  10.     <version>1.0-SNAPSHOT</version> 
  11.  
  12.     <dependencies> 
  13.         <dependency> 
  14.             <groupId>io.netty</groupId> 
  15.             <artifactId>netty-all</artifactId> 
  16.             <version>4.1.50.Final</version> 
  17.         </dependency> 
  18.  
  19.         <dependency> 
  20.             <groupId>junit</groupId> 
  21.             <artifactId>junit</artifactId> 
  22.             <version>4.12</version> 
  23.         </dependency> 
  24.     </dependencies> 
  25.  
  26.     <build> 
  27.         <plugins> 
  28.              
  29.             <plugin> 
  30.                 <groupId>org.apache.maven.plugins</groupId> 
  31.                 <artifactId>maven-compiler-plugin</artifactId> 
  32.                 <version>3.2</version> 
  33.                 <configuration> 
  34.                     <source>1.8</source> 
  35.                     <target>1.8</target> 
  36.                     <encoding>UTF-8</encoding> 
  37.                 </configuration> 
  38.             </plugin> 
  39.         </plugins> 
  40.     </build> 
  41. </project> 

2. 服务端-MyRPCServer

  1. package com.haopt.netty.server; 
  2.  
  3. import io.netty.bootstrap.ServerBootstrap; 
  4. import io.netty.buffer.UnpooledByteBufAllocator; 
  5. import io.netty.channel.ChannelFuture; 
  6. import io.netty.channel.ChannelOption; 
  7. import io.netty.channel.EventLoopGroup; 
  8. import io.netty.channel.nio.NioEventLoopGroup; 
  9. import io.netty.channel.socket.nio.NioServerSocketChannel; 
  10.  
  11. public class MyRPCServer { 
  12.     public void start(int port) throws Exception { 
  13.         // 主线程,不处理任何业务逻辑,只是接收客户的连接请求 
  14.         EventLoopGroup boss = new NioEventLoopGroup(1); 
  15.         // 工作线程,线程数默认是:cpu核数*2 
  16.         EventLoopGroup worker = new NioEventLoopGroup(); 
  17.         try { 
  18.             // 服务器启动类 
  19.             ServerBootstrap serverBootstrap = new ServerBootstrap(); 
  20.             serverBootstrap.group(boss, worker) //设置线程组 
  21.                     .channel(NioServerSocketChannel.class)  //配置server通道 
  22.                     .childHandler(new MyChannelInitializer()); //worker线程的处理器 
  23.             //ByteBuf 的分配要设置为非池化,否则不能切换到堆缓冲区模式 
  24.             serverBootstrap.childOption(ChannelOption.ALLOCATOR, UnpooledByteBufAllocator.DEFAULT); 
  25.             ChannelFuture future = serverBootstrap.bind(port).sync(); 
  26.             System.out.println("服务器启动完成,端口为:" + port); 
  27.             //等待服务端监听端口关闭 
  28.             future.channel().closeFuture().sync(); 
  29.         } finally { 
  30.             //优雅关闭 
  31.             boss.shutdownGracefully(); 
  32.             worker.shutdownGracefully(); 
  33.         } 
  34.     } 
  35.  

3. 服务端-ChannelHandler

  1. package com.haopt.netty.server.handler; 
  2. import io.netty.buffer.ByteBuf; 
  3. import io.netty.buffer.Unpooled; 
  4. import io.netty.channel.ChannelHandlerContext; 
  5. import io.netty.channel.ChannelInboundHandlerAdapter; 
  6. import io.netty.util.CharsetUtil; 
  7.  
  8. public class MyChannelHandler extends ChannelInboundHandlerAdapter { 
  9.     /** 
  10.     * 获取客户端发来的数据 
  11.     * @param ctx 
  12.     * @param msg 
  13.     * @throws Exception 
  14.     */ 
  15.     @Override 
  16.     public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 
  17.         ByteBuf byteBuf = (ByteBuf) msg; 
  18.         String msgStr = byteBuf.toString(CharsetUtil.UTF_8); 
  19.         System.out.println("客户端发来数据:" + msgStr); 
  20.         //向客户端发送数据 
  21.         ctx.writeAndFlush(Unpooled.copiedBuffer("ok", CharsetUtil.UTF_8)); 
  22.     } 
  23.      
  24.     /** 
  25.     * 异常处理 
  26.     * @param ctx 
  27.     * @param cause 
  28.     * @throws Exception 
  29.     */ 
  30.     @Override 
  31.     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 
  32.         cause.printStackTrace(); 
  33.         ctx.close(); 
  34.     } 

4. 测试用例

  1. package com.haopt.netty.myrpc; 
  2. import com.haopt.netty.server.MyRPCServer; 
  3. import org.junit.Test; 
  4. public class TestServer { 
  5.     @Test 
  6.     public void testServer() throws Exception{ 
  7.         MyRPCServer myRPCServer = new MyRPCServer(); 
  8.         myRPCServer.start(5566); 
  9.     } 

21.2 客户端

1. 客户端-client

  1. package com.haopt.netty.client; 
  2. import com.haopt.netty.client.handler.MyClientHandler; 
  3. import io.netty.bootstrap.Bootstrap; 
  4. import io.netty.channel.ChannelFuture; 
  5. import io.netty.channel.EventLoopGroup; 
  6. import io.netty.channel.nio.NioEventLoopGroup; 
  7. import io.netty.channel.socket.nio.NioSocketChannel; 
  8. public class MyRPCClient { 
  9.     public void start(String host, int port) throws Exception { 
  10.         //定义⼯作线程组 
  11.         EventLoopGroup worker = new NioEventLoopGroup(); 
  12.         try { 
  13.             //注意:client使⽤的是Bootstrap 
  14.             Bootstrap bootstrap = new Bootstrap(); 
  15.             bootstrap.group(worker) 
  16.             .channel(NioSocketChannel.class) //注意:client使⽤的是NioSocketChannel 
  17.             .handler(new MyClientHandler()); 
  18.             //连接到远程服务 
  19.             ChannelFuture future = bootstrap.connect(host, port).sync(); 
  20.             future.channel().closeFuture().sync(); 
  21.         } finally { 
  22.              worker.shutdownGracefully(); 
  23.         } 
  24.     } 

2. 客户端-(ClientHandler)

  1. package com.haopt.netty.client.handler; 
  2. import io.netty.buffer.ByteBuf; 
  3. import io.netty.buffer.Unpooled; 
  4. import io.netty.channel.ChannelHandlerContext; 
  5. import io.netty.channel.SimpleChannelInboundHandler; 
  6. import io.netty.util.CharsetUtil; 
  7. public class MyClientHandler extends SimpleChannelInboundHandler<ByteBuf> { 
  8.     @Override 
  9.     protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception { 
  10.         System.out.println("接收到服务端的消息:" + 
  11.         msg.toString(CharsetUtil.UTF_8)); 
  12.     } 
  13.     @Override 
  14.     public void channelActive(ChannelHandlerContext ctx) throws Exception { 
  15.         // 向服务端发送数据 
  16.         String msg = "hello"
  17.         ctx.writeAndFlush(Unpooled.copiedBuffer(msg, CharsetUtil.UTF_8)); 
  18.     } 
  19.     @Override 
  20.     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 
  21.         cause.printStackTrace(); 
  22.         ctx.close(); 
  23.     } 

相信代码执行起来没有任何问题(如果有任何问题反应交流)。但是对上面代码为何这样实现有很多疑。嘿嘿,我也是奥。接下来我们对这些代码中用到的组件进行介绍,希望能消除之前疑虑。如果还是不能,可以把疑问写于留言处,嘿嘿,我也不一定会有个好的解答,但是大佬总会有的。

02二 Netty核心组件

我们都知道Netty是基于事件驱动。但是事件发生后,Netty的各个组件都做了什么?来看看下面内容!

3 2.1 Channel

1. 初识Channel

  1. a 可以理解为socket连接,客户端和服务端连接的时候会创建一个channel。 
  2. 负责基本的IO操作,例如:bind()、connect()、read()、write()。 
  3. b Netty的Channel接口所提供的API,大大减少了Socket类复杂性 

2. 常见Channel(不同的协议和阻塞类型的连接会有不同的Channel类型与之对应)

  1. a NioSocketChannel,NIO的客户端 TCP Socket 连接。 
  2.  
  3. b NioServerSocketChannel,NIO的服务器端 TCP Socket 连接。 
  4.  
  5. c NioDatagramChannel, UDP 连接。 
  6.  
  7. d NioSctpChannel,客户端 Sctp 连接。 
  8.  
  9. e NioSctpServerChannel,Sctp 服务器端连接,这些通道涵盖了UDP和TCP⽹络IO以及⽂件IO。 

4 2.2 EventLoopGroup、EventLoop

1. 概述

  1. 有了Channel连接服务,连接之间消息流动。服务器发出消息称为出站,服务器接受消息称为入站。 
  2. 那么消息出站和入站就产生了事件例如:连接已激活;数据读取;用户事件;异常事件;打开连接; 
  3. 关闭连接等等。有了事件,有了事件就需要机制来监控和协调事件,这个机制就是EventLoop。 

2. 初识EventLoopGroup、EventLoop


对上图解释

  1. a 一个EventLoopGroup包含一个或者多个EventLoop 
  2. b 一个EventLoop在生命周期内之和一个Thread绑定 
  3. c EventLoop上所有的IO事件在它专有的Thread上被处理。 
  4. d Channel在它生命周期只注册于一个Event Loop 
  5. e 一个Event Loop可能被分配给一个或者多个Channel 

3. 代码实现

  1. // 主线程,不处理任何业务逻辑,只是接收客户的连接请求 
  2. EventLoopGroup boss = new NioEventLoopGroup(1); 
  3. // ⼯作线程,线程数默认是:cpu*2 
  4. EventLoopGroup worker = new NioEventLoopGroup(); 

5 2.3 ChannelHandler

1. 初识ChannelHandler

  1. 对于数据的出站和入栈的业务逻辑都是在ChannelHandler中。 

2. 对于出站和入站对应的ChannelHandler


  1. ChannelInboundHandler ⼊站事件处理器 
  2. ChannelOutBoundHandler 出站事件处理器 

3. 开发中常用的ChannelHandler(ChannelInboundHandlerAdapter、SimpleChannelInboundHandler)

a 源码

b SimpleChannelInboundHandler的源码(是ChannelInboundHandlerAdapter子类)


注意:

两者的区别在于,前者不会释放消息数据的引⽤,⽽后者会释放消息数据的引⽤。

6 2.4 ChannelPipeline 

1. 初识ChannelPipeline

  1. 将ChannelHandler串起来。一个Channel包含一个ChannelPipeline,而ChannelPipeline维护者一个ChannelHandler列表。 
  2. ChannelHandler与Channel和ChannelPipeline之间的映射关系,由ChannelHandlerContext进⾏维护。 

 

如上图解释

  1. ChannelHandler按照加⼊的顺序会组成⼀个双向链表,⼊站事件从链表的head往后传递到最后⼀个ChannelHandler。 
  2. 出站事件从链表的tail向前传递,直到最后⼀个ChannelHandler,两种类型的ChannelHandler相互不会影响。 

7 2.5 Bootstrap

1. 初识Bootstrap

  1. 是引导作用,配置整个netty程序,将各个组件串起来,最后绑定接口,启动服务。 

2. Bootstrap两种类型(Bootstrap、ServerBootstrap)

  1. 客户端只需要一个EventLoopGroup,服务端需要两个EventLoopGroup。 

 

上图解释

  1. 与ServerChannel相关联的EventLoopGroup 将分配⼀个负责为传⼊连接请求创建 Channel 的EventLoop。 
  2. ⼀旦连接被接受,第⼆个 EventLoopGroup 就会给它的 Channel 分配⼀个 EventLoop。 

8 2.6 Future

1. 初识

  1. 操作完成时通知应用程序的方式。这个对象可以看做异步操作执行结果占位符,它在将来某个时刻完成,并提供对其结果的访问。 

2. ChannelFuture的由来

  1. JDK 预置了 interface java.util.concurrent.Future,但是其所提供的实现, 
  2. 只允许⼿动检查对应的操作是否已经完成,或者⼀直阻塞直到它完成。这是⾮常 
  3. 繁琐的,所以 Netty 提供了它⾃⼰的实现--ChannelFuture,⽤于在执⾏异步 
  4. 操作的时候使⽤。 

3. Netty为什么完全是异步?

  1. a ChannelFuture提供了⼏种额外的⽅法,这些⽅法使得我们能够注册⼀个或者多个  ChannelFutureListener实例。 
  2.  
  3. b 监听器的回调⽅法operationComplete(),将会在对应的操作完成时被调⽤。 
  4.  然后监听器可以判断该操作是成功地完成了还是出错了。 
  5.    
  6. c 每个 Netty 的出站 I/O 操作都将返回⼀个 ChannelFuture,也就是说, 
  7.  它们都不会阻塞。所以说,Netty完全是异步和事件驱动的。 

9 2.7 组件小结


上图解释

  1. 将组件串起来 

03三 缓存区-ByteBuf

ByteBuf是我们开发中代码操作最多部分和出现问题最多的一部分。比如常见的TCP协议通信的粘包和拆包解决,和ByteBuf密切相关。后面文章会详细分析,先不展开。我们这里先了解ByteBuf的常用API和执行内幕。

10 3.1 ByteBuf概述

1. 初识ByteBuf

  1. JavaNIO提供了缓存容器(ByteBuffer),但是使用复杂。因此netty引入缓存ButeBuf, 
  2. 一串字节数组构成。 

2. ByteBuf两个索引(readerIndex,writerIndex)

  1. a readerIndex 将会根据读取的字节数递增 
  2. b writerIndex 也会根据写⼊的字节数进⾏递增 
  3.  
  4. 注意:如果readerIndex超过了writerIndex的时候,Netty会抛出IndexOutOf-BoundsException异常。 

 

11 3.2 ByteBuf基本使用

1. 读取

  1. package com.haopt.netty.myrpc.test; 
  2. import io.netty.buffer.ByteBuf; 
  3. import io.netty.buffer.Unpooled; 
  4. import io.netty.util.CharsetUtil; 
  5. public class TestByteBuf01 { 
  6.     public static void main(String[] args) { 
  7.         //构造 
  8.         ByteBuf byteBuf = Unpooled.copiedBuffer("hello world"
  9.         CharsetUtil.UTF_8); 
  10.         System.out.println("byteBuf的容量为:" + byteBuf.capacity()); 
  11.         System.out.println("byteBuf的可读容量为:" + byteBuf.readableBytes()); 
  12.         System.out.println("byteBuf的可写容量为:" + byteBuf.writableBytes()); 
  13.         while (byteBuf.isReadable()){ //⽅法⼀:内部通过移动readerIndex进⾏读取 
  14.          System.out.println((char)byteBuf.readByte()); 
  15.         } 
  16.         //⽅法⼆:通过下标直接读取 
  17.         for (int i = 0; i < byteBuf.readableBytes(); i++) { 
  18.          System.out.println((char)byteBuf.getByte(i)); 
  19.         } 
  20.         //⽅法三:转化为byte[]进⾏读取 
  21.         byte[] bytes = byteBuf.array(); 
  22.         for (byte b : bytes) { 
  23.         System.out.println((char)b); 
  24.         } 
  25.     } 

2. 写入

  1. package com.haopt.netty.myrpc.test; 
  2. import io.netty.buffer.ByteBuf; 
  3. import io.netty.buffer.Unpooled; 
  4. import io.netty.util.CharsetUtil; 
  5. public class TestByteBuf02 { 
  6.     public static void main(String[] args) { 
  7.         //构造空的字节缓冲区,初始⼤⼩为10,最⼤为20 
  8.         ByteBuf byteBuf = Unpooled.buffer(10,20); 
  9.         System.out.println("byteBuf的容量为:" + byteBuf.capacity()); 
  10.         System.out.println("byteBuf的可读容量为:" + byteBuf.readableBytes()); 
  11.         System.out.println("byteBuf的可写容量为:" + byteBuf.writableBytes()); 
  12.         for (int i = 0; i < 5; i++) { 
  13.          byteBuf.writeInt(i); //写⼊int类型,⼀个int占4个字节 
  14.         } 
  15.         System.out.println("ok"); 
  16.         System.out.println("byteBuf的容量为:" + byteBuf.capacity()); 
  17.         System.out.println("byteBuf的可读容量为:" + byteBuf.readableBytes()); 
  18.         System.out.println("byteBuf的可写容量为:" + byteBuf.writableBytes()); 
  19.         while (byteBuf.isReadable()){ 
  20.          System.out.println(byteBuf.readInt()); 
  21.         } 
  22.     } 

3. 丢弃已读字节


  1. package com.haopt.netty.myrpc.test; 
  2. import io.netty.buffer.ByteBuf; 
  3. import io.netty.buffer.Unpooled; 
  4. import io.netty.util.CharsetUtil; 
  5. public class TestByteBuf03 { 
  6.     public static void main(String[] args) { 
  7.         ByteBuf byteBuf = Unpooled.copiedBuffer("hello world",CharsetUtil.UTF_8); 
  8.         System.out.println("byteBuf的容量为:" + byteBuf.capacity()); 
  9.         System.out.println("byteBuf的可读容量为:" + byteBuf.readableBytes()); 
  10.         System.out.println("byteBuf的可写容量为:" + byteBuf.writableBytes()); 
  11.         while (byteBuf.isReadable()){ 
  12.          System.out.println((char)byteBuf.readByte()); 
  13.         } 
  14.         byteBuf.discardReadBytes(); //丢弃已读的字节空间 
  15.         System.out.println("byteBuf的容量为:" + byteBuf.capacity()); 
  16.         System.out.println("byteBuf的可读容量为:" + byteBuf.readableBytes()); 
  17.         System.out.println("byteBuf的可写容量为:" + byteBuf.writableBytes()); 
  18.     } 

4. clear()


  1. package com.haopt.netty.myrpc.test; 
  2. import io.netty.buffer.ByteBuf; 
  3. import io.netty.buffer.Unpooled; 
  4. import io.netty.util.CharsetUtil; 
  5. public class TestByteBuf04 { 
  6.     public static void main(String[] args) { 
  7.         ByteBuf byteBuf = Unpooled.copiedBuffer("hello world",CharsetUtil.UTF_8); 
  8.         System.out.println("byteBuf的容量为:" + byteBuf.capacity()); 
  9.         System.out.println("byteBuf的可读容量为:" + byteBuf.readableBytes()); 
  10.         System.out.println("byteBuf的可写容量为:" + byteBuf.writableBytes()); 
  11.         byteBuf.clear(); //重置readerIndex 、 writerIndex 为0 
  12.         System.out.println("byteBuf的容量为:" + byteBuf.capacity()); 
  13.         System.out.println("byteBuf的可读容量为:" + byteBuf.readableBytes()); 
  14.         System.out.println("byteBuf的可写容量为:" + byteBuf.writableBytes()); 
  15.     } 

12 3.3 ByteBuf 使⽤模式

3.3.1 根据存放缓冲区,分为三类

1. 堆缓存区(HeapByteBuf)

  1. 内存的分配和回收速度⽐较快,可以被JVM⾃动回收,缺点是,如果进⾏socket的IO读写,需要额外做⼀次内存复制,将堆内存对应的缓冲区复制到内核Channel中,性能会有⼀定程度的下降。 
  2. 由于在堆上被 JVM 管理,在不被使⽤时可以快速释放。可以通过 ByteBuf.array() 来获取 byte[] 数 
  3. 据。 

2. 直接缓存区(DirectByteBuf)

  1. ⾮堆内存,它在对外进⾏内存分配,相⽐堆内存,它的分配和回收速度会慢⼀些,但是 
  2. 将它写⼊或从Socket Channel中读取时,由于减少了⼀次内存拷⻉,速度⽐堆内存块。 

3. 复合缓存区

  1. 顾名思义就是将上述两类缓冲区聚合在⼀起。Netty 提供了⼀个 CompsiteByteBuf, 
  2. 可以将堆缓冲区和直接缓冲区的数据放在⼀起,让使⽤更加⽅便。 

3.3.2 缓存区选择

Netty默认使⽤的是直接缓冲区(DirectByteBuf),如果需要使⽤堆缓冲区(HeapByteBuf)模式,则需要进⾏系统参数的设置。

  1. //netty中IO操作都是基于Unsafe完成的 
  2. System.setProperty("io.netty.noUnsafe""true");  
  3. //ByteBuf的分配要设置为⾮池化,否则不能切换到堆缓冲器模式 
  4. serverBootstrap.childOption(ChannelOption.ALLOCATOR,UnpooledByteBufAllocator.DEFAULT); 

3.3.3 ByteBuf对象是否池化(Netty是默认池化的)

1. 池化化和非池化的实现

  1. PooledByteBufAllocator,实现了ByteBuf的对象的池化,提⾼性能减少并最⼤限度地减少内存碎⽚。 
  2. UnpooledByteBufAllocator,没有实现对象的池化,每次会⽣成新的对象实例。 

2. 代码实现(让Netty中ByteBuf对象不池化)

  1. //通过ChannelHandlerContext获取ByteBufAllocator实例 
  2. ctx.alloc(); 
  3. //通过channel也可以获取 
  4. channel.alloc(); 
  5.  
  6. //Netty默认使⽤了PooledByteBufAllocator 
  7.  
  8. //可以在引导类中设置⾮池化模式 
  9. serverBootstrap.childOption(ChannelOption.ALLOCATOR,UnpooledByteBufAllocator.DEFAULT); 
  10. //或通过系统参数设置 
  11. System.setProperty("io.netty.allocator.type""pooled"); 
  12. System.setProperty("io.netty.allocator.type""unpooled"); 

我在开发项目中,我一般不进行更改。因为我觉得池化效率更高。有其他高见,欢迎留言。

13 3.5 ByteBuf的释放

ByteBuf如果采⽤的是堆缓冲区模式的话,可以由GC回收,但是如果采⽤的是直接缓冲区,就不受GC的 管理,就得⼿动释放,否则会发⽣内存泄露。

3.5.1 ByteBuf的手动释放(一般不推荐使用,了解)

1. 实现逻辑

  1. ⼿动释放,就是在使⽤完成后,调⽤ReferenceCountUtil.release(byteBuf); 进⾏释放。 
  2. 通过release⽅法减去 byteBuf的使⽤计数,Netty 会⾃动回收 byteBuf。 

2. 代码

  1. /** 
  2. * 获取客户端发来的数据 
  3. * @param ctx 
  4. * @param msg 
  5. * @throws Exception 
  6. */ 
  7. @Override 
  8. public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 
  9.     ByteBuf byteBuf = (ByteBuf) msg; 
  10.     String msgStr = byteBuf.toString(CharsetUtil.UTF_8); 
  11.     System.out.println("客户端发来数据:" + msgStr); 
  12.     //释放资源 
  13.     ReferenceCountUtil.release(byteBuf); 

注意:

⼿动释放可以达到⽬的,但是这种⽅式会⽐较繁琐,如果⼀旦忘记释放就可能会造成内存泄露。

3.5.1 ByteBuf的自动释放

⾃动释放有三种⽅式,分别是:⼊站的TailHandler、继承SimpleChannelInboundHandler、 HeadHandler的出站释放。

1. TailHandler

Netty的ChannelPipleline的流⽔线的末端是TailHandler,默认情况下如果每个⼊站处理器Handler都把消息往下传,TailHandler会释放掉ReferenceCounted类型的消息。

  1. /** 
  2. * 获取客户端发来的数据 
  3. * @param ctx 
  4. * @param msg 
  5. * @throws Exception 
  6. */ 
  7. @Override 
  8. public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 
  9.     ByteBuf byteBuf = (ByteBuf) msg; 
  10.     String msgStr = byteBuf.toString(CharsetUtil.UTF_8); 
  11.     System.out.println("客户端发来数据:" + msgStr); 
  12.     //向客户端发送数据 
  13.     ctx.writeAndFlush(Unpooled.copiedBuffer("ok", CharsetUtil.UTF_8)); 
  14.     ctx.fireChannelRead(msg); //将ByteBuf向下传递 

源码:

在DefaultChannelPipeline中的TailContext内部类会在最后执⾏

  1. @Override 
  2. public void channelRead(ChannelHandlerContext ctx, Object msg) { 
  3.  onUnhandledInboundMessage(ctx, msg); 
  4. //最后会执⾏ 
  5. protected void onUnhandledInboundMessage(Object msg) { 
  6.   try { 
  7.       logger.debug( 
  8.       "Discarded inbound message {} that reached at the tail of the 
  9.       pipeline. " + "Please check your pipeline configuration.", msg); 
  10.   } finally { 
  11.     ReferenceCountUtil.release(msg); //释放资源 
  12.   } 

2. SimpleChannelInboundHandler

当ChannelHandler继承了SimpleChannelInboundHandler后,在SimpleChannelInboundHandler的channelRead()⽅法中,将会进⾏资源的释放。

SimpleChannelInboundHandler的源码

  1. //SimpleChannelInboundHandler中的channelRead() 
  2. @Override 
  3. public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { 
  4.     boolean release = true
  5.     try { 
  6.       if (acceptInboundMessage(msg)) { 
  7.         @SuppressWarnings("unchecked"
  8.         I imsg = (I) msg; 
  9.         channelRead0(ctx, imsg); 
  10.       } else { 
  11.         release = false
  12.         ctx.fireChannelRead(msg); 
  13.       } 
  14.     } finally { 
  15.       if (autoRelease && release) { 
  16.        ReferenceCountUtil.release(msg); //在这⾥释放 
  17.       } 
  18.     } 

我们handler代码编写:

  1. package com.haopt.myrpc.client.handler; 
  2. import io.netty.buffer.ByteBuf; 
  3. import io.netty.buffer.Unpooled; 
  4. import io.netty.channel.ChannelHandlerContext; 
  5. import io.netty.channel.SimpleChannelInboundHandler; 
  6. import io.netty.util.CharsetUtil; 
  7. public class MyClientHandler extends SimpleChannelInboundHandler<ByteBuf> { 
  8.     @Override 
  9.     protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws Exception { 
  10.       System.out.println("接收到服务端的消息:" + 
  11.       msg.toString(CharsetUtil.UTF_8)); 
  12.     } 
  13.     @Override 
  14.     public void channelActive(ChannelHandlerContext ctx) throws Exception { 
  15.       // 向服务端发送数据 
  16.       String msg = "hello"
  17.       ctx.writeAndFlush(Unpooled.copiedBuffer(msg, CharsetUtil.UTF_8)); 
  18.     } 
  19.     @Override 
  20.     public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 
  21.       cause.printStackTrace(); 
  22.       ctx.close(); 
  23.     } 

3. 堆缓冲区(HeadHandler)

出站处理流程中,申请分配到的ByteBuf,通过HeadHandler完成⾃动释放。

在出站流程开始的时候,通过调⽤ctx.writeAndFlush(msg),Bytebuf缓冲区开始进⼊出站处理的pipeline流⽔线。

在每⼀个出站Handler中的处理完成后,最后消息会来到出站的最后⼀棒HeadHandler,再经过⼀轮复杂的调⽤,在flush完成后终将被release掉。

  1. package com.haopt.myrpc.client.handler; 
  2. import io.netty.buffer.ByteBuf; 
  3. import io.netty.buffer.Unpooled; 
  4. import io.netty.channel.ChannelHandlerContext; 
  5. import io.netty.channel.SimpleChannelInboundHandler; 
  6. import io.netty.util.CharsetUtil; 
  7. public class MyClientHandler extends SimpleChannelInboundHandler<ByteBuf> { 
  8. @Override 
  9. protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) throws 
  10. Exception { 
  11. System.out.println("接收到服务端的消息:" + 
  12. msg.toString(CharsetUtil.UTF_8)); 
  13. @Override 
  14. public void channelActive(ChannelHandlerContext ctx) throws Exception { 
  15. // 向服务端发送数据 
  16. String msg = "hello"
  17. ctx.writeAndFlush(Unpooled.copiedBuffer(msg, CharsetUtil.UTF_8)); 
  18. @Override 
  19. public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) 
  20. throws Exception { 
  21. cause.printStackTrace(); 
  22. ctx.close(); 

 

14 3.6 ByteBuf小结

a ⼊站流程中,如果对原消息不做处理,调ctx.fireChannelRead(msg) 把

原消息往下传,由流⽔线最后⼀棒 TailHandler 完成⾃动释放。

b 如果截断了⼊站处理流⽔线,则继承SimpleChannelInboundHandler ,完成⼊站ByteBuf ⾃动释放。

c 出站处理过程中,申请分配到的 ByteBuf,通过 HeadHandler 完成⾃动释放。

d ⼊站处理中,如果将原消息转化为新的消息ctx.fireChannelRead(newMsg)往下传,那必须把原消息release掉。

e ⼊站处理中,如果已经不再调⽤ ctx.fireChannelRead(msg) 传递任何消息,也没有继承SimpleChannelInboundHandler 完成⾃动释放,那更要把原消息release掉。

 

责任编辑:姜华 来源: 花花和Java
相关推荐

2018-09-04 16:20:46

MySQ索引数据结构

2018-11-21 08:00:05

Dubbo分布式系统

2020-11-06 16:50:43

工具GitLab CICD

2021-01-27 11:10:49

JVM性能调优

2011-11-07 09:37:42

Hpyer-V虚拟化云计算

2010-11-19 16:22:14

Oracle事务

2010-06-23 20:31:54

2020-09-21 09:53:04

FlexCSS开发

2009-08-25 16:27:10

Mscomm控件

2010-07-13 09:36:25

2022-08-26 13:48:40

EPUBLinux

2023-10-06 00:04:02

2020-07-20 06:35:55

BashLinux

2023-04-21 15:26:33

云计算云基础架构组件

2011-07-18 15:08:34

2022-06-03 10:09:32

威胁检测软件

2010-11-15 11:40:44

Oracle表空间

2010-09-27 09:31:42

JVM内存结构

2010-11-08 13:54:49

Sqlserver运行

2021-04-28 10:13:58

zookeeperZNode核心原理
点赞
收藏

51CTO技术栈公众号