netty-writeAndFlush的输出顺序
结果输出
netty
写出结果,总需要让channel
进行write
,然后配合flush
刷出。
如果一次性写好的话,writeAndFlush
就可以一步完成。
但是这里有两个方法
ctx.writeAndFlush
ctx.channel().writeAndFlush
底层自然都是通过channel
进行的writeAndFlush
。
但是中间部分的context
和作为全局的channel
,经由pipeline
的管理,两种写入方式有稍许的不同。
ctx.channel().writeAndFlush
ctx.channel().writeAndFlush(msg);
可以看到,虽然都是channel
进行的操作,但是由于都包含了channel
,context
和pipeline
也可以写。
这里选择的当然是AbstractChannel
public ChannelFuture writeAndFlush(Object msg) {return pipeline.writeAndFlush(msg);}
AbstractChannel
是基本的SocketChannel
的封装,也就是说,AbstractChannel
是封装过的channel
。
并不是channel
直接的信息写入。
在这里,经手的,实际上是pipeline
的writeAndFlush
public final ChannelFuture writeAndFlush(Object msg) {return tail.writeAndFlush(msg);}
pipeline
是context
的双向链表,tail
就是最后一个,pipeline
的写好像要全部遍历。
public ChannelFuture writeAndFlush(Object msg) {return writeAndFlush(msg, newPromise());}
public ChannelPromise newPromise() {return new DefaultChannelPromise(channel(), executor());}
操作对象就是
channel
,底层最真实的操作对象,真正的写操作的执行者。多线程中,总是
executor
来进行的异步操作,这两个都是必须的,也不用继续探讨。
public ChannelFuture writeAndFlush(Object msg, ChannelPromise promise) {if (msg == null) {throw new NullPointerException("msg");}if (isNotValidPromise(promise, true)) {ReferenceCountUtil.release(msg);return promise;}write(msg, true, promise);return promise;}
经过channel
的有效检测,然后进行真实的写操作
private void write(Object msg, boolean flush, ChannelPromise promise) {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 {AbstractWriteTask task;if (flush) {task = WriteAndFlushTask.newInstance(next, m, promise);} else {task = WriteTask.newInstance(next, m, promise);}safeExecute(executor, task, promise, m);}}
findContextOutbound
这里看到了findContextOutbound
,不得不想起findContextInbound
的循环遍历
private AbstractChannelHandlerContext findContextOutbound() {AbstractChannelHandlerContext ctx = this;do {ctx = ctx.prev;} while (!ctx.outbound);return ctx;}
只要能够找到循环的证据,那就能够证明它是outbound
的遍历执行了。
线程检测
executor.inEventLoop()
,多线程里面总是会这样检测,如果就是自身,就不用再挂任务。
至于执行器的选择,这个就不提了。
集成操作
if (flush) {next.invokeWriteAndFlush(m, promise);} else {next.invokeWrite(m, promise);}
单纯的写入口,就集成了刷出flush
的操作
invokeWriteAndFlush
private void invokeWriteAndFlush(Object msg, ChannelPromise promise) {if (invokeHandler()) {invokeWrite0(msg, promise);invokeFlush0();} else {writeAndFlush(msg, promise);}}
invokeWrite
private void invokeWrite(Object msg, ChannelPromise promise) {if (invokeHandler()) {invokeWrite0(msg, promise);} else {write(msg, promise);}}
遍历传播
private void invokeWrite0(Object msg, ChannelPromise promise) {try {((ChannelOutboundHandler) handler()).write(this, msg, promise);} catch (Throwable t) {notifyOutboundHandlerException(t, promise);}}
继续
当然是选择outbound
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {ctx.write(msg, promise);}
然后
这回轮到了context
public ChannelFuture write(final Object msg, final ChannelPromise promise) {if (msg == null) {throw new NullPointerException("msg");}try {if (isNotValidPromise(promise, true)) {ReferenceCountUtil.release(msg)return promise;}} catch (RuntimeException e) {ReferenceCountUtil.release(msg);throw e;}write(msg, false, promise);return promise;}
继续
private void write(Object msg, boolean flush, ChannelPromise promise) {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 {AbstractWriteTask task;if (flush) {task = WriteAndFlushTask.newInstance(next, m, promise);} else {task = WriteTask.newInstance(next, m, promise);}safeExecute(executor, task, promise, m);}}
这就循环了,findContextOutbound
不停遍历outboundContext
传播事件。
具体操作
最后必然就是传播到HeadContext
public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {unsafe.write(msg, promise);}
unsafe
就直接写入了
public final void write(Object msg, ChannelPromise promise) {...outboundBuffer.addMessage(msg, size, promise);}
ctx.writeAndFlush
ctx.writeAndFlush(msg);
public ChannelFuture writeAndFlush(Object msg) {return writeAndFlush(msg, newPromise());}
public ChannelFuture writeAndFlush(Object msg, ChannelPromise promise) {if (msg == null) {throw new NullPointerException("msg");}if (isNotValidPromise(promise, true)) {ReferenceCountUtil.release(msg);return promise;}write(msg, true, promise);return promise;}
private void write(Object msg, boolean flush, ChannelPromise promise) {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 {AbstractWriteTask task;if (flush) {task = WriteAndFlushTask.newInstance(next, m, promise);} else {task = WriteTask.newInstance(next, m, promise);}safeExecute(executor, task, promise, m);}}
也不用多说,最后调用的方法都是一样的,好像都会遍历context
传播,直到HeadContext
写操作。
差异分析
其实真正的差异在于
ctx.channel().writeAndFlush
public ChannelFuture writeAndFlush(Object msg) {return pipeline.writeAndFlush(msg);
}
public final ChannelFuture writeAndFlush(Object msg) {return tail.writeAndFlush(msg);}
对于channel()
的写入,其实是pipeline
的写入,指定的context
是tail
。
它会完整的遍历整个pipeline
。
但是ctx.writeAndFlush
,从当前的context
向后传播。
画图描述一下
例子验证
inbound
public class SimpleInboundHandler extends SimpleChannelInboundHandler<String> {@Overrideprotected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {System.out.println("write now");ctx.channel().writeAndFlush("ctx.channel().writeAndFlush()");ctx.writeAndFlush("ctx.writeAndFlush");}
}
outbound
public class SimpleOutboundHandler extends ChannelOutboundHandlerAdapter {private String name ;public SimpleOutboundHandler(String name){this.name = name;}@Overridepublic void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception {super.write(ctx, msg, promise);System.out.println(this.name + " : " + msg);}
}
pipeline
public class ServerInitializer extends ChannelInitializer<SocketChannel> {@Overrideprotected void initChannel(SocketChannel ch2) throws Exception {ChannelPipeline pipeline = ch2.pipeline();pipeline.addLast(new StringDecoder());pipeline.addLast(new StringEncoder());pipeline.addLast(new SimpleOutboundHandler("first-outbound"));pipeline.addLast(new SimpleInboundHandler());pipeline.addLast(new SimpleOutboundHandler("last-outbound"));}
}
运行结果
逻辑分析
全遍历的话,应该会经过全部的outbound
。
但是可以发现,ctx.writeAndFlush
并没有经过last-outbound
,只是经过了first-outbound
。
和我们的追踪相符合。
小结
所以,为了减少无谓的步骤,我们开发过程中可以更多使用ctx.writeAndFlush
。
如果觉得ctx.channel().writeAndFlush
更高大上,更好的话,那就得不偿失了。
毕竟就编程而言,好东西应该唾手可得。
但同样的,如果我们的完整流程是经由outbound
而实现的话。
ctx.writeAndFlush
就会使得流程变得不完整,甚至出错。
所以,具体使用哪种方式,得因地制宜,合理的使用方为上策。
netty-writeAndFlush的输出顺序相关推荐
- DateGridView列的输出顺序反了
问题: 敲机房里显示数据表的窗体,我用代码写的数据源绑定到datagridview控件显示的时候,顺序和数据库查询出来的表的顺序相反 过程: 后来一直在查DateGridView列的输出顺序反了怎么办 ...
- LUA表 pairs, ipairs输出顺序问题
LUA表 pairs, ipairs输出顺序问题 t = {[1] = 222,[2] = 23,[3] = 2433,[42] = 135,[5] = 1287,[7] = 7,[102] = 10 ...
- Jackson 注解 -- 指定输出顺序
默认情况下,字段的输出顺序和它们在类中的位置一致,我们也可以使用注解 @JsonPropertyOrder 自己指定顺序. package shangbo.jackson.demo12;import ...
- Netty writeAndFlush() 流程与异步
Netty writeAndFlush()方法分为两步, 先 write 再 flush @Overridepublic ChannelFuture writeAndFlush(Object msg, ...
- [转载] 【Python】set() 集合操作与运算 元素输出顺序
参考链接: Python中set的copy 集合 | SET 集合(set)是Python中一种重要的数据类型,表示一组各不相同元素的无序集合,其主要应用于重复元素消除及关系测试等 集合在Pytho ...
- JsonObject存入顺序和输出顺序不一样问题-豆果
2019独角兽企业重金招聘Python工程师标准>>> ##JsonObject的输入顺序和输出顺序不一样问题 ###问题原因在于JsonObject的默认实现的是用HashMap, ...
- 在顺序表中第五个位置插入一个元素9,实现顺序表插入的基本操作,输出顺序表中所有元素
题目 在顺序表中第五个位置插入一个元素9,实现顺序表插入的基本操作,输出顺序表中所有元素 #include<iostream>using namespace std; #define OK ...
- 面试题--promise和setTimeout的输出顺序
面试题–promise和setTimeout的输出顺序 下面一段代码是在网上看到的一段关于promise和setTimeout的输出顺序的代码,下面做一下解答,有兴趣的百度自行了解js的执行机制(包括 ...
- Netty : writeAndFlush的线程安全及并发问题
使用Netty编程时,我们经常会从用户线程,而不是Netty线程池发起write操作,因为我们不能在netty的事件回调中做大量耗时操作.那么问题来了 – 1, writeAndFlush是线程安全的 ...
最新文章
- Qt访问SQLite数据库
- boost::fusion::fused_function_object用法的测试程序
- python自动化工具开发_初识TPOT:一个基于Python的自动化机器学习开发工具
- c 调用python_c调用python
- 01:操作系统(centos,redhat):性能监控和网络命令
- java网络图片与二进制字符串相互转换
- c语言合法常量e8,c语言合法常量定义
- 例29:哥德巴赫猜想
- Celery使用数据库代替rabbitmq
- 计算机专业专科生毕业论文题目,★专科生计算机专业论文题目专科生计算机专业毕业论文题目大全专科生计算机专业论文选题参考...
- matlab list函数参数,Matlab 函数参数汇总
- 创业日志(三十)华东华南之10天7市行
- Unity技能系统架构
- 居家学习python自制闹铃小助手
- 酷派s6、Coolpad 9190l_C00 无log信息输出解决方法
- mysql基于WebStorm服装购物网站的设计与实现毕业设计源码281444
- 高性能mysql 随笔
- admin_move_table的重组机制验证(失败了)
- [离散数学] 关于p - q的理解。
- Flutter 不可错过的学习资源