图文加多个测试带你彻底搞懂Netty ChannelPipeline的执行顺序(附源码)

这里是weihubeats ,觉得文章不错可以关注公众号小奏技术,文章首发。拒绝营销号,拒绝标题党

netty version

  • 4.1.65.Final

ChannelPipeline 是什么

Pipeline,管道、流水线,类似于责任链模式。基本上我们使用Netty开发程序需要编写的就是ChannelPipeline中的各个ChannelHandler

ChannelPipeline就是用来组合所有的ChannelHandler

我们今天重点讨论的对象是ChannelPipeline添加多个ChannelHandler他的执行顺序是什么

测试demo

这里我们直接用测试代码来看看

NettyServer

java 复制代码
public class NettyServer {
	public static void main(String[] args) throws InterruptedException {
		// 启动服务器
		startServer();
		
	}

	private static void startServer() throws InterruptedException {
		EventLoopGroup bossGroup = new NioEventLoopGroup();
		EventLoopGroup workerGroup = new NioEventLoopGroup();
		ServerBootstrap b = new ServerBootstrap();

		b.group(bossGroup, workerGroup)
				.channel(NioServerSocketChannel.class)
				.option(ChannelOption.SO_BACKLOG, 1024)
				.childHandler(new ChannelInitializer<SocketChannel>() {
					@Override
					protected void initChannel(SocketChannel ch) {
						ChannelPipeline pipeline = ch.pipeline();
						pipeline.addLast(new CustomInboundHandler1());
						pipeline.addLast(new CustomInboundHandler2());
						pipeline.addLast(new CustomOutboundHandler1());
						pipeline.addLast(new CustomOutboundHandler2());
						pipeline.addLast(new SimpleChannelInboundHandler<ByteBuf>() {
							@Override
							protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) {
								//buf.readableBytes()获取缓冲区可读的字节数
								byte[] req = new byte[msg.readableBytes()];
								// 将缓冲区的字节数组复制到新的byte数组中
								msg.readBytes(req);
								String body = new String(req, StandardCharsets.UTF_8);

								System.out.println("Server received: " + body);
								ByteBuf firstMessage;
								byte[] req1 = "你好客户端".getBytes();
								firstMessage = Unpooled.buffer(req1.length);
								firstMessage.writeBytes(req1);
								System.out.println("开始给客户端发送消息");
								ctx.writeAndFlush(firstMessage);
							}
							@Override
							public void channelRegistered(ChannelHandlerContext ctx) {
								System.out.println("连接上来了");
								ctx.fireChannelRegistered();
							}
						});
					}
				});

		ChannelFuture future = b.bind(8888).sync();
		future.channel().closeFuture().sync();
	}
}

NettyClient

java 复制代码
public class NettyClient {

	public static void main(String[] args) throws Exception {
		// 启动客户端
		startClient();
	}

	public static void startClient() throws InterruptedException {
		Bootstrap clientBootstrap = new Bootstrap();
		NioEventLoopGroup group = new NioEventLoopGroup();

		clientBootstrap.group(group)
				.channel(NioSocketChannel.class)
				.handler(new ChannelInitializer<SocketChannel>() {
					@Override
					protected void initChannel(SocketChannel ch) {
						ChannelPipeline pipeline = ch.pipeline();
						pipeline.addLast(new CustomOutboundHandler2());
						pipeline.addLast(new CustomOutboundHandler1());
						pipeline.addLast(new CustomInboundHandler2());
						pipeline.addLast(new CustomInboundHandler1());
						pipeline.addLast(new SimpleChannelInboundHandler<ByteBuf>() {
							@Override
							public void channelActive(ChannelHandlerContext ctx) {
								ByteBuf firstMessage;
								byte[] req = "你好服务器".getBytes();
								firstMessage = Unpooled.buffer(req.length);
								firstMessage.writeBytes(req);
								System.out.println("------------ 开始发送消息 ------------");
								ctx.writeAndFlush(firstMessage);
							}

							@Override
							protected void channelRead0(ChannelHandlerContext ctx, ByteBuf msg) {
								//buf.readableBytes()获取缓冲区可读的字节数
								byte[] req = new byte[msg.readableBytes()];
								// 将缓冲区的字节数组复制到新的byte数组中
								msg.readBytes(req);
								String body = new String(req, StandardCharsets.UTF_8);

								System.out.println("Client received: " + body);
							}

						});
					}
				});

		ChannelFuture future = clientBootstrap.connect("localhost", 8888).sync();
		future.channel().closeFuture().sync();
	}
}

测试

我们这里先启动NettyServer,然后启动 NettyClient

控制台输出信息如下

  • NettyClient

  • NettyServer

首先我们可以看到我们在 Netty 添加Handler顺序如下

java 复制代码
pipeline.addLast(new CustomOutboundHandler2());
						pipeline.addLast(new CustomOutboundHandler1());
						pipeline.addLast(new CustomInboundHandler2());
						pipeline.addLast(new CustomInboundHandler1());

client Handler 执行顺序

所以对于client执行的顺序是CustomOutboundHandler1CustomOutboundHandler2CustomInboundHandler2CustomInboundHandler1

通俗的讲就是我们client发送消息的时候就是如下一个顺序

只会执行OutboundHandler

client接受到server返回的消息后的handler执行顺序如下

只会执行InboundHandler

server Handler 执行顺序

服务端的顺序也是类似,由于client发送消息到server,所以对于服务端来说是接受消息

所以服务端首先执行的顺序就是接受消息

所以打印了

CustomInboundHandler1 - channelRead

CustomInboundHandler2 - channelRead

Server received: 你好服务器

server在处理完数据后会返回消息给client,这里server就是发送数据

所以就是这个顺序

CustomOutboundHandler2 - write

CustomOutboundHandler1 - write

图解handler顺序

基于上面的demo演示,我们就总结了一个handler的执行顺序

横看就是这个顺序,当然我们也可以通过上面的箭头看。

如果是发送消息 就是 从下到上

如果是接受消息就是从上到下

再谈ctx.channel().writeAndFlushctx.writeAndFlush

现在我们所有的handerl一般调用的方法都是 ctx.channel().writeAndFlush

那么ctx.channel().writeAndFlushctx.writeAndFlush方法有什么区别呢

我们知道ChannelPipeline是一个双向链表结构。 比如我们server添加handler的顺序如下

java 复制代码
ch.pipeline().addLast(new InboundHandler1());  
ch.pipeline().addLast(new InboundHandler2()); 
ch.pipeline().addLast(new OutboundHandler1());  
ch.pipeline().addLast(new OutboundHandler2());

那么在链表中的顺序就是如下

rust 复制代码
head->in1->in2->out1->out2->tail

如果我们在InboundHandler2中执行ctx.channel().writeAndFlush

那么输出的顺序就是

java 复制代码
InboundHandler1 
InboundHandler2 
OutboundHandler2
OutboundHandler1

如果在InboundHandler2中执行ctx.writeAndFlush,执行顺序是

java 复制代码
InboundHandler1 
InboundHandler2 

可以看到执行ctx.writeAndFlush他不会从tail节点向前找OutboundHandler

ctx.channel().writeAndFlush则是从tail节点开始向前找OutboundHandler

如果我们将handler顺序修改下改成如下

java 复制代码
ch.pipeline().addLast(new OutboundHandler1());  
ch.pipeline().addLast(new OutboundHandler2());  
ch.pipeline().addLast(new InboundHandler1());  
ch.pipeline().addLast(new InboundHandler2());

然后在InboundHandler2执行ctx.writeAndFlush,由于InboundHandler2在链表tail节点的前一个节点,所以输出的结果和执行 ctx.channel().writeAndFlush一样 都是

复制代码
InboundHandler1
InboundHandler2
OutboundHandler2 
OutboundHandler1

总结

从上面大量的用例我们可以看出如下规律

  1. ChannelPipeline是双向链表结构,包含ChannelInboundHandlerChannelOutboundHandler两种处理器.也有处理器既是ChannelInboundHandler又是ChannelOutboundHandler,但是也属于这两种
  2. ctx.writeAndFlush只会从当前的handler位置开始,往前找outbound执行
  3. ctx.pipeline().writeAndFlushctx.channel().writeAndFlush会从tail的位置开始,往前找outbound执行
  4. InboundHandler的执行顺序一般是从上到下Head -> Tail
  5. OutboundHandler的执行顺序一般是从下到上,Tail -> Head

源码

相关推荐
bjbxkj3 小时前
量子隧穿:PROFINET到Ethernet ip的无损耗协议转换方案转
网络·网络协议·tcp/ip
TinpeaV8 小时前
websocket入门详解
java·网络·spring boot·websocket·网络协议
爱学习的章鱼哥8 小时前
计算机网络|| 网络协议分析
网络·网络协议·计算机网络
-曾牛8 小时前
基于微信小程序的在线聊天功能实现:WebSocket通信实战
前端·后端·websocket·网络协议·微信小程序·小程序·notepad++
八月的雨季 最後的冰吻10 小时前
SIP协议栈--osip源码梳理
linux·服务器·网络·c++·网络协议
Warren9810 小时前
Java面试八股Spring篇(4500字)
java·开发语言·spring boot·后端·spring·面试
若汝棋茗10 小时前
基于TouchSocket实现WebSocket自定义OpCode扩展协议
网络·websocket·网络协议
背帆10 小时前
go的interface接口底层实现
开发语言·后端·golang
IT成长史11 小时前
deepseek梳理java高级开发工程师springboot面试题2
java·spring boot·后端
qq_2663487312 小时前
springboot AOP中,通过解析SpEL 表达式动态获取参数值
java·spring boot·后端