Netty源码分析之ChannelPipeline—出站事件的传播
上篇文章中我们梳理了ChannelPipeline中入站事件的传播,这篇文章中我们看下出站事件的传播,也就是ChannelOutboundHandler接口的实现。
1、出站事件的传播示例
我们对上篇文章中的示例代码进行改造,在ChannelPipeline中加入ChannelOutboundHandler出站实现
public class ServerApp {
public static void main(String[] args) {
EventLoopGroup boss = new NioEventLoopGroup();
EventLoopGroup work = new NioEventLoopGroup(2);
try {
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(boss, work).channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
// p.addLast(new LoggingHandler(LogLevel.INFO));
// 向ChannelPipeline中添加自定义channelHandler
p.addLast(new OutHandlerA());
p.addLast(new ServerHandlerA());
p.addLast(new ServerHandlerB());
p.addLast(new ServerHandlerC());
p.addLast(new OutHandlerB());
p.addLast(new OutHandlerC());
}
});
bootstrap.bind(8050).sync();
} catch (Exception e) {
// TODO: handle exception
}
}
}
public class OutHandlerA extends ChannelOutboundHandlerAdapter {
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
System.err.println(this.getClass().getName()+msg);
ctx.writeAndFlush((ByteBuf)msg);
}
}
public class OutHandlerB extends ChannelOutboundHandlerAdapter {
@Override
public void write(ChannelHandlerContext ctx,Object msg,ChannelPromise promise) {
System.out.println(this.getClass().getName()+msg);
ctx.write((ByteBuf)msg);
}
}
public class OutHandlerC extends ChannelOutboundHandlerAdapter {
@Override
public void write(ChannelHandlerContext ctx,Object msg,ChannelPromise promise) {
System.out.println(this.getClass().getName()+"--"+msg);
ctx.write((ByteBuf)msg);
}
}
然后我们在ServerHandlerA的channelRead方法中执行ctx的write方法,模拟消息出站事件的发生。
public class ServerHandlerA extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx, Object object) {
ByteBuf byteBuf = PooledByteBufAllocator.DEFAULT.buffer();
byteBuf.writeByte(1);
byteBuf.writeByte(2);
ctx.channel().write(byteBuf);
//ctx.write(byteBuf);
}
}
上面channelRead方法中write方法的调用有两种方式 ctx.channel().write 与 ctx.write,这两种方式有何区别呢,我们首先看下这两种方式的运行结果
ctx.channel().write
io.netty.example.echo.my.OutHandlerC--PooledUnsafeDirectByteBuf(ridx: 0, widx: 2, cap: 256)
io.netty.example.echo.my.OutHandlerB--PooledUnsafeDirectByteBuf(ridx: 0, widx: 2, cap: 256)
io.netty.example.echo.my.OutHandlerA--PooledUnsafeDirectByteBuf(ridx: 0, widx: 2, cap: 256)
ctx.write
io.netty.example.echo.my.OutHandlerA--PooledUnsafeDirectByteBuf(ridx: 0, widx: 2, cap: 256)
可以看到当调用ctx.channel().write时,消息在管道中传播的顺序是从尾部一直传递到最上层的OutboundHandler;而 ctx.write会从所在的 handler 向前找 OutboundHandler。
那么这两种方式区别是否就如结果所示呢,下面我们就开始对这两种方法的内部实现进行分析
2、出站事件传播的分析
ctx.channel().write与 ctx.write 分别用的是AbstractChannel与AbstractChannelHandlerContext的write方法
AbstractChannel 的 write方法
@Override
public ChannelFuture write(Object msg) {
return pipeline.write(msg);
}
AbstractChannelHandlerContext 的 write方法
@Override
public ChannelFuture write(Object msg) {
return write(msg, newPromise());
}
上面代码中AbstractChannel的 wirte方法最终调用的是pipeline的write方法,我们进入pipeline内部查看,可以看到pipeline的write方法默认从尾部AbstractChannelHandlerContext节点开始调用。
@Override
public final ChannelFuture write(Object msg) {
return tail.write(msg);
}
继续向下跟踪最终它们调用的都是AbstractChannelHandlerContext 的 write方法,下面我们看下方法内部的具体实现。
private void write(Object msg, boolean flush, ChannelPromise promise) {
ObjectUtil.checkNotNull(msg, "msg");
try {
if (isNotValidPromise(promise, true)) {//检查ChannelPromise是否有效
ReferenceCountUtil.release(msg);
// cancelled
return;
}
} catch (RuntimeException e) {
ReferenceCountUtil.release(msg);
throw e;
}
//寻找上一个AbstractChannelHandlerContext节点
AbstractChannelHandlerContext next = findContextOutbound();
final Object m = pipeline.touch(msg, next);
EventExecutor executor = next.executor();
if (executor.inEventLoop()) {//与当前线程是否一致
if (flush) {//确定是否要把数据冲刷到远程节点
next.invokeWriteAndFlush(m, promise);
} else {
next.invokeWrite(m, promise);
}
} else { //如果不一致的封装成writeTask任务线程
final AbstractWriteTask task;
if (flush) {
task = WriteAndFlushTask.newInstance(next, m, promise);
} else {
task = WriteTask.newInstance(next, m, promise);
}
//把该线程任务交给对应的EventExecutor执行
if (!safeExecute(executor, task, promise, m)) {
// We failed to submit the AbstractWriteTask. We need to cancel it so we decrement the pending bytes
// and put it back in the Recycler for re-use later.
//
// See https://github.com/netty/netty/issues/8343.
task.cancel();
}
}
}
主要关注下findContextOutbound(),这个方法的作用就是获取当前AbstractChannelHandlerContext节点的上一个节点prev
private AbstractChannelHandlerContext findContextOutbound() {
AbstractChannelHandlerContext ctx = this;
do {
ctx = ctx.prev;//获取当前节点的上一个节点
} while (!ctx.outbound);//判断是不是出站节点
return ctx;
}
最终通过next.invokeWrite(m, promise)回调方法,调用下一个节点中封装的ChannelOutboundHandler的write方法,从而实现write方法事件的传递
private void invokeWrite(Object msg, ChannelPromise promise) {
if (invokeHandler()) {//判断当前ChannelOutboundHandler是否已经被添加到pipeline中(handlerAdded事件触发)
invokeWrite0(msg, promise);
} else {
write(msg, promise);
}
}
private boolean invokeHandler() {
// Store in local variable to reduce volatile reads.
int handlerState = this.handlerState;
return handlerState == ADD_COMPLETE || (!ordered && handlerState == ADD_PENDING);
}
private void invokeWrite0(Object msg, ChannelPromise promise) {
try {
((ChannelOutboundHandler) handler()).write(this, msg, promise);
} catch (Throwable t) {
notifyOutboundHandlerException(t, promise);
}
}
到这里整个出站事件的传播流程已经基本清晰了,wirte方法本身就是一个寻找并回调下一个节点中wirte方法的过程。
3、write与writeAndFlush
在上面代码中可以看到这两个方法主要在于是否会在执行write方法后,是否会执行flush方法。
private void invokeWriteAndFlush(Object msg, ChannelPromise promise) {
if (invokeHandler()) { //是否调用回调方法
//调用write与flush回调方法,最终调用自定义hander的对应实现
invokeWrite0(msg, promise);
invokeFlush0();
} else {
writeAndFlush(msg, promise);
}
}
这里需要注意的是invokeFlush0()在invokeWrite0后执行,也就是必须等到消息出站事件传递完毕后,才会调用flush把数据冲刷到远程节点。简单理解就是你无论是在OutHandlerA、OutHandlerB还是OutHandlerC中调用writeAndFlush,最后都是要在write事件传递完毕才会flush数据的。
同时我们需要注意到当write与flush事件从OutHandlerA再往上传递时,OutHandlerA的的上一个节点就是Pipeline的头节点HeadContext,我们看下HeadContext的write与flush方法实现;
@Override
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) {
unsafe.write(msg, promise);
} @Override
public void flush(ChannelHandlerContext ctx) {
unsafe.flush();
}
到这里我们可以看出,消息的真正入队与发送最终是通过HeadContext的write与flush方法实现。
通过以上的分析我们可以看到Pipeline出站事件的传播流程,同时我们需要注意ctx.write与ctx.channel().write的区别以及消息的发送最终是通头部节点调用unsafe的write与flush方法实现的,其中如有不足与不正确的地方还望指出与海涵。
关注微信公众号,查看更多技术文章。

Netty源码分析之ChannelPipeline—出站事件的传播的更多相关文章
- Netty源码分析之ChannelPipeline—入站事件的传播
之前的文章中我们说过ChannelPipeline作为Netty中的数据管道,负责传递Channel中消息的事件传播,事件的传播分为入站和出站两个方向,分别通知ChannelInboundHandle ...
- Netty源码分析之ChannelPipeline—异常事件的传播
ChannelHandler中异常的获取与处理是通过继承重写exceptionCaught方法来实现的,本篇文章我们对ChannelPipeline中exceptionCaught异常事件的传播进行梳 ...
- 【Netty源码分析】ChannelPipeline(二)
在上一篇博客[Netty源码学习]ChannelPipeline(一)中我们只是大体介绍了ChannelPipeline相关的知识,其实介绍的并不详细,接下来我们详细介绍一下ChannelPipeli ...
- Netty源码分析之ChannelPipeline(一)—ChannelPipeline的构造与初始化
Netty中ChannelPipeline实际上类似与一条数据管道,负责传递Channel中读取的消息,它本质上是基于责任链模式的设计与实现,无论是IO事件的拦截器,还是用户自定义的ChannelHa ...
- [编织消息框架][netty源码分析]6 ChannelPipeline 实现类DefaultChannelPipeline职责与实现
ChannelPipeline 负责channel数据进出处理,如数据编解码等.采用拦截思想设计,经过A handler处理后接着交给next handler ChannelPipeline 并不是直 ...
- Netty源码分析之ChannelPipeline(二)—ChannelHandler的添加与删除
上篇文章中,我们对Netty中ChannelPipeline的构造与初始化进行了分析与总结,本篇文章我们将对ChannelHandler的添加与删除操作进行具体的的代码分析: 一.ChannelHan ...
- netty源码分析系列文章
netty源码分析系列文章 nettynetty源码阅读netty源码分析 想在年终之际将对netty研究的笔记记录下来,先看netty3,然后有时间了再写netty4的,希望对大家有所帮助,这个是 ...
- Netty 源码分析——ChannelPipeline
Netty 源码分析--ChannelPipeline 通过前面的两章我们分析了客户端和服务端的流程代码,其中在初始化 Channel 的时候一定会看到一个 ChannelPipeline.所以在 N ...
- Netty 源码分析系列(二)Netty 架构设计
前言 上一篇文章,我们对 Netty做了一个基本的概述,知道什么是Netty以及Netty的简单应用. Netty 源码分析系列(一)Netty 概述 本篇文章我们就来说说Netty的架构设计,解密高 ...
随机推荐
- ORs-5-OR Subgenomes Variation among Birds, Sea Turtle and Alligator
OR Subgenomes Variation among Birds, Sea Turtle and Alligator 由 该图数据计算每种鸟的relative percentage,得到下图: ...
- Linux安装swoole拓展 (一键安装lnmp后安装可用完美)
一键安装lnmp后安装可用完美 swoole(一键安装完lnmp重启下,之前出现502一直解决不了,不清楚啥情况) 找到对应php版本,在lnmp文件夹的src 1.安装swoole cd /usr/ ...
- deeplearning.ai 卷积神经网络 Week 2 卷积神经网络经典架构
1. Case study:学习经典网络的原因是它们可以被迁移到其他任务中. 1.1)几种经典的网络: a)LeNet-5(LeCun et al., 1998. Gradient-based lea ...
- 升级mongodb数据库2.6.6到3.0.3,切换引擎,主从同步数据
只升级mongodb的版本极为简单,更新/替换 bin/目录下的可执行文件即可. debian和osx上的升级,按步骤来,没什么好说的. http://docs.mongodb.org/manual/ ...
- mongodb Map/reduce测试代码
private void AccountInfo() { ls.Clear(); DateTime dt = DateTime.Now.Date; IMongoQuery query = Query& ...
- Pip安装TensorFlow报错:MemoryError
问题描述 使用pip安装TensorFlow时,一直报错,情况如下: 解决办法 使用如下命令可正常安装: pip3 install --no-cache-dir tenstoflow --no-cac ...
- List.remove()的使用注意
不使用forEach的循环 使用forEach循环 参考 今天修改一个bug,需要取一个List和一个Set的交集,使用了双重循环.想着提高循环效率,每加入一个交集中的元素,就将List中的元素删除, ...
- Django 学习笔记1-- URLconf
今天好像巴黎有点乱,希望明天太阳还会照常升起. 简介 Django 是一个由 Python 编写.开源并采用经典的 MVC 设计模式的 Web Full Stack 应用框架. 在 Django 中, ...
- 自动化移动安全渗透测试框架:Mobile Security Framework
自动化移动安全渗透测试框架:Mobile Security Framework 译/Sphinx 测试开发社区 7月3日 Mobile Security Framework (移动安全框架) 是一 ...
- Nginx+PHP配置错误,日志:[error] 24324#0: *31 FastCGI sent in stderr: "Primary script unknown" while reading response header from upstream
一.问题现象 1.安装nginx.php.php-fpm后,浏览器访问php报错,“File not found”: 二.问题排查 1.检查nginx.php-fpm服务是否正常启动,均正常启动: 2 ...