1-ChannelHandler和Pipeline
ChannelHandler 用来处理 Channel 上的各种事件,分为入站、出站两种。所有 ChannelHandler 被连成一串,就是 Pipeline
- 入站处理器通常是 ChannelInboundHandlerAdapter 的子类,主要用来读取客户端数据,写回结果
- 出站处理器通常是 ChannelOutboundHandlerAdapter 的子类,主要对写回结果进行加工;
类比:每个 Channel 是一个产品的加工车间,Pipeline 是车间中的流水线,ChannelHandler 就是流水线上的各道工序,而后面要讲的 ByteBuf 是原材料,经过很多工序的加工:先经过一道道入站工序,再经过一道道出站工序最终变成产品
场景:模拟客户端向服务端发送hello, server;服务端向客户端回应:hello,client...
import io.netty.bootstrap.ServerBootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOutboundHandlerAdapter;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.ChannelPromise;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import lombok.extern.slf4j.Slf4j;
import java.nio.charset.Charset;
@Slf4j
public class HandlerServer {
public static void main(String[] args) {
NioEventLoopGroup bossGroup = new NioEventLoopGroup(1);
NioEventLoopGroup workGroup = new NioEventLoopGroup(2);
new ServerBootstrap()
.group(bossGroup, workGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel ch) throws Exception {
// 1. 通过 channel 拿到 pipeline
ChannelPipeline pipeline = ch.pipeline();
// 2. 添加处理器 head -> h1 -> h2 -> h3 -> h4 -> h5 -> h6 -> tail
pipeline.addLast("h1", new ChannelInboundHandlerAdapter(){
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf msg1=(ByteBuf)msg;
String msgNew = msg1.toString(Charset.defaultCharset());
log.debug("1-msgNew={}",msgNew);
super.channelRead(ctx, msg);
}
});
pipeline.addLast("h2", new ChannelInboundHandlerAdapter(){
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf msg1=(ByteBuf)msg;
String msgNew = msg1.toString(Charset.defaultCharset());
log.debug("2-msgNew={}",msgNew);
super.channelRead(ctx, msg); // 将数据传递给下个 handler,如果不调用,调用链会断开 或者调用 ctx.fireChannelRead(msg);
}
});
pipeline.addLast("h3", new ChannelInboundHandlerAdapter(){
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf msg1=(ByteBuf)msg;
String msgNew = msg1.toString(Charset.defaultCharset());
log.debug("3-msgNew={}",msgNew);
//ctx.channel().writeAndFlush(msg) 会 从尾部开始触发 后续出站处理器的执行
ctx.channel().writeAndFlush(ctx.alloc().buffer().writeBytes("hello,client...".getBytes()));
}
});
pipeline.addLast("h4", new ChannelOutboundHandlerAdapter(){
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
ByteBuf msg1=(ByteBuf)msg;、、
String msgNew = msg1.toString(Charset.defaultCharset());
log.debug("4-msgNew={}",msgNew);
super.write(ctx, msg, promise);
}
});
pipeline.addLast("h5", new ChannelOutboundHandlerAdapter(){
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
ByteBuf msg1=(ByteBuf)msg;
String msgNew = msg1.toString(Charset.defaultCharset());
log.debug("5-msgNew={}",msgNew);
super.write(ctx, msg, promise);
}
});
pipeline.addLast("h6", new ChannelOutboundHandlerAdapter(){
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {
ByteBuf msg1=(ByteBuf)msg;
String msgNew = msg1.toString(Charset.defaultCharset());
log.debug("6-msgNew={}",msgNew);
super.write(ctx, msg, promise);
}
});
}
})
.bind(8080);
}
}
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringEncoder;
import lombok.extern.slf4j.Slf4j;
import java.net.InetSocketAddress;
import java.nio.charset.Charset;
@Slf4j
public class HandlerClient {
public static void main(String[] args) throws InterruptedException {
ChannelFuture channelFuture = new Bootstrap()
.group(new NioEventLoopGroup())
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<NioSocketChannel>() {
@Override // 在连接建立后被调用
protected void initChannel(NioSocketChannel ch) throws Exception {
ch.pipeline().addLast(new StringEncoder());
ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
if(msg instanceof ByteBuf){
ByteBuf msg1=(ByteBuf)msg;
String msgNew = msg1.toString(Charset.defaultCharset());
log.debug("in-1-client,msgNew={}",msgNew);
}else{
log.debug("in-1-client,000");
}
}
});
}
})
.connect(new InetSocketAddress("localhost", 8080));
channelFuture.sync();
channelFuture.channel().writeAndFlush("hello, server");
}
}
服务器端日志输出:
14:39:01 [DEBUG] [nioEventLoopGroup-3-1] c.n.d.d.HandlerServer - 1-msgNew=hello, server
14:39:01 [DEBUG] [nioEventLoopGroup-3-1] c.n.d.d.HandlerServer - 2-msgNew=hello, server
14:39:01 [DEBUG] [nioEventLoopGroup-3-1] c.n.d.d.HandlerServer - 3-msgNew=hello, server
14:39:01 [DEBUG] [nioEventLoopGroup-3-1] c.n.d.d.HandlerServer - 6-msgNew=hello,client...
14:39:01 [DEBUG] [nioEventLoopGroup-3-1] c.n.d.d.HandlerServer - 5-msgNew=hello,client...
14:39:01 [DEBUG] [nioEventLoopGroup-3-1] c.n.d.d.HandlerServer - 4-msgNew=hello,client...
客户端日志输出:
14:39:01 [DEBUG] [nioEventLoopGroup-2-1] c.n.d.d.HandlerClient - in-1-client,msgNew=hello,client...
可以看到,ChannelInboundHandlerAdapter 是按照 addLast 的顺序执行的,而 ChannelOutboundHandlerAdapter 是按照 addLast 的逆序执行的。ChannelPipeline 的实现是一个 ChannelHandlerContext(包装了 ChannelHandler) 组成的双向链表;

注意:
Handler3处的,如果调用ctx.writeAndFlush(ctx.alloc().buffer().writeBytes("hello,client...".getBytes()));
客户端会收到消息,但是服务端的4,5,6不会输出在控制台;
ctx.channel().writeAndFlush(ctx.alloc().buffer().writeBytes("hello,client...".getBytes()));
客户端会收到消息,但是服务端的4,5,6会输出在控制台;
2-ByteBuf
优点:
池化 - 可以重用池中 ByteBuf 实例,更节约内存,减少内存溢出的可能
读写指针分离,不需要像 ByteBuffer 一样切换读写模式
可以自动扩容
支持链式调用,使用更流畅
很多地方体现零拷贝,例如 slice、duplicate、CompositeByteBuf
2.1-直接内存 vs 堆内存
//创建池化基于堆的 ByteBuf
ByteBuf buffer = ByteBufAllocator.DEFAULT.heapBuffer(10);
//创建池化基于直接内存的 ByteBuf
ByteBuf buffer = ByteBufAllocator.DEFAULT.directBuffer(10);
//直接内存创建和销毁的代价昂贵,但读写性能高(少一次内存复制),适合配合池化功能一起用
//直接内存对 GC 压力小,因为这部分内存不受 JVM 垃圾回收的管理,但也要注意及时主动释放2.2-池化 vs 非池化
池化的最大意义在于可以重用 ByteBuf,优点有
没有池化,则每次都得创建新的 ByteBuf 实例,这个操作对直接内存代价昂贵,就算是堆内存,也会增加 GC 压力
有了池化,则可以重用池中 ByteBuf 实例,并且采用了与 jemalloc 类似的内存分配算法提升分配效率
高并发时,池化功能更节约内存,减少内存溢出的可能
Unpooled 是一个工具类,类如其名,提供了非池化的 ByteBuf 创建、组合、复制等操作;
2.3-堆外内存回收
由于 Netty 中有堆外内存的 ByteBuf 实现,堆外内存最好是手动来释放,而不是等 GC 垃圾回收。
UnpooledHeapByteBuf 使用的是 JVM 内存,只需等 GC 回收内存即可
UnpooledDirectByteBuf 使用的就是直接内存了,需要特殊的方法来回收内存
PooledByteBuf 和它的子类使用了池化机制,需要更复杂的规则来回收内存
基本规则是,谁是最后使用者,谁负责 release,详细分析如下:
起点,对于 NIO 实现来讲,在 io.netty.channel.nio.AbstractNioByteChannel.NioByteUnsafe#read 方法中首次创建 ByteBuf 放入 pipeline(line 163 pipeline.fireChannelRead(byteBuf))
入站 ByteBuf 处理原则
对原始 ByteBuf 不做处理,调用 ctx.fireChannelRead(msg) 向后传递,这时无须 release
将原始 ByteBuf 转换为其它类型的 Java 对象,这时 ByteBuf 就没用了,必须 release
如果不调用 ctx.fireChannelRead(msg) 向后传递,那么也必须 release
注意各种异常,如果 ByteBuf 没有成功传递到下一个 ChannelHandler,必须 release
假设消息一直向后传,那么 TailContext 会负责释放未处理消息(原始的 ByteBuf)
出站 ByteBuf 处理原则
出站消息最终都会转为 ByteBuf 输出,一直向前传,由 HeadContext flush 后 release
异常处理原则
有时候不清楚 ByteBuf 被引用了多少次,但又必须彻底释放,可以循环调用 release 直到返回 true
本文介绍了Netty框架中的ChannelHandler与ByteBuf的基本概念和使用方式。ChannelHandler包括入站和出站处理器,分别用于处理客户端数据和服务端响应。ByteBuf提供了高效的缓冲区管理和池化机制,减少了内存溢出风险并提升了性能。

3131

被折叠的 条评论
为什么被折叠?



