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

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

netty version

  • 4.1.65.Final

ChannelPipeline 是什么

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

ChannelPipeline就是用来组合所有的ChannelHandler

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

测试demo

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

NettyServer

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

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顺序如下

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的顺序如下

ch.pipeline().addLast(new InboundHandler1());  
ch.pipeline().addLast(new InboundHandler2()); 
ch.pipeline().addLast(new OutboundHandler1());  
ch.pipeline().addLast(new OutboundHandler2());

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

head->in1->in2->out1->out2->tail

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

那么输出的顺序就是

InboundHandler1 
InboundHandler2 
OutboundHandler2
OutboundHandler1

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

InboundHandler1 
InboundHandler2 

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

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

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

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

源码

  • 源码: https://github.com/weihubeats/weihubeats_demos/tree/master/java-demos/netty-demo/src/test/java/com/weihubeats/netty/demo/channelPipeline

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/126736.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

Apache RocketMQ - 概述

2022年&#xff0c;RocketMQ 5.0的正式版发布&#xff0c;相比于4.0版本而言&#xff0c;架构走向云原生化&#xff0c;并且覆盖了更多的业务场景。 如何从互联网时代演进到云时代&#xff1f; 1. 消息队列演进史 操作系统、数据库、中间件是基础软件的三驾马车&#xff0c;…

Sketch是什么软件,如何收费和获得免费版

Sketch软件为设计师构建了一个优秀的本地Mac应用程序。Sketch是整个设计过程的平台&#xff0c;通过基于Web的工具共享工作&#xff0c;获取反馈&#xff0c;测试原型&#xff0c;并将其移交给任何浏览器。Sketch软件的定价根据不同的许可类型和订阅计划而变化。本文从Sketch软…

18. 深度学习 - 从零理解神经网络

文章目录 本文目标预测趋势与关系波士顿房价预测 Hi, 你好。我是茶桁。 我们终于又开启新的篇章了&#xff0c;从今天这节课开始&#xff0c;我们会花几节课来理解一下深度学习的相关知识&#xff0c;了解神经网络&#xff0c;多层神经网络相关知识。并且&#xff0c;我们会尝…

网络安全自学手册

想自学网络安全&#xff08;黑客技术&#xff09;首先你得了解什么是网络安全&#xff01;什么是黑客&#xff01; 网络安全可以基于攻击和防御视角来分类&#xff0c;我们经常听到的 “红队”、“渗透测试” 等就是研究攻击技术&#xff0c;而“蓝队”、“安全运营”、“安全…

No module named ‘importlib.metadata‘

解决办法 参考博客 https://wenku.csdn.net/answer/45a1563cc02e9592dd1d1d28fe7b88e7 pip install importlib_metadata

使用U盘安装ubuntu22操作教程

U盘启动 将烧录好的U盘&#xff0c;插上待安装系统的电脑 服务器在开机之后长按【ESC键】进入BIOS选项中&#xff0c;选择对应的U盘启动 如下图&#xff0c;在界面中“USB”选项就是我的U盘&#xff0c;第一启动项选择U盘启动&#xff0c;其他启动项不动&#xff0c;选择后按F…

③【操作表数据】MySQL添加数据、修改数据、删除数据

个人简介&#xff1a;Java领域新星创作者&#xff1b;阿里云技术博主、星级博主、专家博主&#xff1b;正在Java学习的路上摸爬滚打&#xff0c;记录学习的过程~ 个人主页&#xff1a;.29.的博客 学习社区&#xff1a;进去逛一逛~ MySQL添加数据、修改数据、删除数据 &#x1f…

让旗下产品受到更多用户认可,GNC健安喜登陆中国国际进口博览会

11月5日-10日&#xff0c;第六届中国国际进口博览会&#xff08;以下简称“中国进博会”&#xff09;在上海国家会展中心正式起航。自2018年首次举办以来&#xff0c;中国进博会受到了无数参展企业的推崇&#xff0c;无数制造商、采购商的追捧。随着参会企业的逐年增长&#xf…

辐射骚扰整改思路及方法:对共模电流的影响?|深圳比创达电子EMC

某产品首次EMC测试时&#xff0c;辐射、静电、浪涌均失败。本篇文章就“原理探究&#xff1a;对共模电流的影响”问题进行详细讨论。 现在来研究左侧的磁场分布情况。分别对两根导线使用右手螺旋定则可以发现&#xff0c;两根导线的磁场均为顺时针方向&#xff0c;即磁场是互相…

Java后端开发——JDBC入门实验

JDBC&#xff08;Java Database Connectivity&#xff09;是Java编程语言中用于与数据库建立连接并进行数据库操作的API&#xff08;应用程序编程接口&#xff09;。JDBC允许开发人员连接到数据库&#xff0c;执行各种操作&#xff08;如插入、更新、删除和查询数据&#xff09…

代码随想录 Day38 完全背包问题 LeetCode T70 爬楼梯 T322 零钱兑换 T279 完全平方数

前言 在今天的题目开始之前,让我们来回顾一下之前的知识,动规五部曲 1.确定dp数组含义 2.确定dp数组的递推公式 3.初始化dp数组 4.确定遍历顺序 5.打印dp数组来排错 tips: 1.当求取物品有限的时候用0-1背包,求取物品无限的时候用完全背包 结果是排列还是组合也有说法,当结果是组…

如何选择最适合的知识付费小程序开发工具?

在选择适合的知识付费小程序开发工具时&#xff0c;需要考虑开发者的技能水平、项目需求、平台兼容性以及用户体验。下面将介绍一些常用的开发工具&#xff0c;并提供一些选择工具的考虑因素。 1. 微信小程序开发工具 微信小程序是知识付费小程序的一个常见平台&#xff0c;…

生活污水处理一体化处理设备有哪些

生活污水处理一体化处理设备有多种类型&#xff0c;包括但不限于以下几种&#xff1a; 鼓风机&#xff1a;提供曝气系统所需的气流。潜水污水提升泵&#xff1a;将污水从低处提升到高处。旋转式滚筒筛分机&#xff1a;对污水中的悬浮物进行分离和筛选。回旋式格栅&#xff1a;…

LVS NAT 模式

1.3.2. LVS DR 模式 模式&#xff08;局域网改写 &#xff08;局域网改写 mac 地址&#xff09; ①.客户端将请求发往前端的负载均衡器&#xff0c;请求报文源地址是 CIP&#xff0c;目标地址为 VIP。 ②.负载均衡器收到报文后&#xff0c;发现请求的是在规则里面存在的地址&am…

股票四倍杠杆什么意思?

股票四倍杠杆是指投资者通过借款或使用金融衍生品&#xff0c;以增加其投资股票的能力&#xff0c;达到放大投资回报的目的。具体来说&#xff0c;投资者可以通过向券商或银行等金融机构借入资金&#xff0c;或者使用融资融券等金融衍生品&#xff0c;以增加其购买股票的资本&a…

看李广的故事:发现团队管理之道

在漠北之战中&#xff0c;李广因迷失道路而延误了军期。因李广年事已高&#xff0c;无法承受幕府的责难&#xff0c;最终选择在军前自刎而死。 这一事件令人痛惜&#xff0c;不禁让人想起在工作中遇到的类似情况。有些同事因为突然离职&#xff0c;让领导感到愕然&#xff0c;…

【python小游戏】飞机大作战源码分享(附完整源码+图片资源可直接运行)

效果演示 源码 plane_main1.py import pygame from plane_sprites import * import timeclass PlaneGame(object):"""飞机大战主游戏"""def __init__(self):print("游戏初始化")# 1. 创建游戏的窗口self.screen pygame.display.set…

WPF中数据绑定验证深入讲解

WPF中数据绑定验证深入讲解 WPF在用户输入时&#xff0c;提供了验证功能&#xff0c;通常验证使用以下两种方式来实现&#xff1a; 在数据对象中引发错误。通常是在属性设置过程中抛出异常&#xff0c;或者在数据类中实现INotifyDataErrorInfo或IDataErrorInfo接口。在绑定级…

epoll实现 IO复用

1、epoll实现 IO复用 epoll的提出--》它所支持的文件描述符上限是系统可以最大打开的文件的数目&#xff1b;eg&#xff1a;1GB机器上&#xff0c;这个上限10万个左右。 每个fd上面有callback(回调函数)函数&#xff0c;只有活跃的fd才有主动调用callback&#xff0c;不需要轮询…

【Python爬虫】网页抓取实例之淘宝商品信息抓取

之前我们已经说过网页抓取的相关内容 上次我们是以亚马逊某网页的产品为例 抓取价格、品牌、型号、样式等 该网页上价格、品牌、型号、样式等 都只有一个 如果网页上的目标内容 根据不同规格有多个 又该怎么提取呢&#xff1f; ▼如下图所示 当机身颜色、套餐、存储容量…