结果输出

netty写出结果,总需要让channel进行write,然后配合flush刷出。

如果一次性写好的话,writeAndFlush就可以一步完成。

但是这里有两个方法

  1. ctx.writeAndFlush
  2. ctx.channel().writeAndFlush

底层自然都是通过channel进行的writeAndFlush

但是中间部分的context和作为全局的channel,经由pipeline的管理,两种写入方式有稍许的不同。

ctx.channel().writeAndFlush

        ctx.channel().writeAndFlush(msg);


可以看到,虽然都是channel进行的操作,但是由于都包含了channelcontextpipeline也可以写。

这里选择的当然是AbstractChannel

    public ChannelFuture writeAndFlush(Object msg) {return pipeline.writeAndFlush(msg);}

AbstractChannel是基本的SocketChannel的封装,也就是说,AbstractChannel是封装过的channel

并不是channel直接的信息写入。

在这里,经手的,实际上是pipelinewriteAndFlush

    public final ChannelFuture writeAndFlush(Object msg) {return tail.writeAndFlush(msg);}

pipelinecontext的双向链表,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的写入,指定的contexttail

它会完整的遍历整个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"));}
}

运行结果

逻辑分析

pipelineoutbound

first-outbound
inbound
last-outbound
last-outbound
first-outbound

全遍历的话,应该会经过全部的outbound

但是可以发现,ctx.writeAndFlush并没有经过last-outbound,只是经过了first-outbound

和我们的追踪相符合。

小结

所以,为了减少无谓的步骤,我们开发过程中可以更多使用ctx.writeAndFlush

如果觉得ctx.channel().writeAndFlush更高大上,更好的话,那就得不偿失了。

毕竟就编程而言,好东西应该唾手可得。

但同样的,如果我们的完整流程是经由outbound而实现的话。

ctx.writeAndFlush就会使得流程变得不完整,甚至出错。

所以,具体使用哪种方式,得因地制宜,合理的使用方为上策。

netty-writeAndFlush的输出顺序相关推荐

  1. DateGridView列的输出顺序反了

    问题: 敲机房里显示数据表的窗体,我用代码写的数据源绑定到datagridview控件显示的时候,顺序和数据库查询出来的表的顺序相反 过程: 后来一直在查DateGridView列的输出顺序反了怎么办 ...

  2. LUA表 pairs, ipairs输出顺序问题

    LUA表 pairs, ipairs输出顺序问题 t = {[1] = 222,[2] = 23,[3] = 2433,[42] = 135,[5] = 1287,[7] = 7,[102] = 10 ...

  3. Jackson 注解 -- 指定输出顺序

    默认情况下,字段的输出顺序和它们在类中的位置一致,我们也可以使用注解 @JsonPropertyOrder 自己指定顺序. package shangbo.jackson.demo12;import ...

  4. Netty writeAndFlush() 流程与异步

    Netty writeAndFlush()方法分为两步, 先 write 再 flush @Overridepublic ChannelFuture writeAndFlush(Object msg, ...

  5. [转载] 【Python】set() 集合操作与运算 元素输出顺序

    参考链接: Python中set的copy 集合 | SET 集合(set)是Python中一种重要的数据类型,表示一组各不相同元素的无序集合,其主要应用于重复元素消除及关系测试等  集合在Pytho ...

  6. JsonObject存入顺序和输出顺序不一样问题-豆果

    2019独角兽企业重金招聘Python工程师标准>>> ##JsonObject的输入顺序和输出顺序不一样问题 ###问题原因在于JsonObject的默认实现的是用HashMap, ...

  7. 在顺序表中第五个位置插入一个元素9,实现顺序表插入的基本操作,输出顺序表中所有元素

    题目 在顺序表中第五个位置插入一个元素9,实现顺序表插入的基本操作,输出顺序表中所有元素 #include<iostream>using namespace std; #define OK ...

  8. 面试题--promise和setTimeout的输出顺序

    面试题–promise和setTimeout的输出顺序 下面一段代码是在网上看到的一段关于promise和setTimeout的输出顺序的代码,下面做一下解答,有兴趣的百度自行了解js的执行机制(包括 ...

  9. Netty : writeAndFlush的线程安全及并发问题

    使用Netty编程时,我们经常会从用户线程,而不是Netty线程池发起write操作,因为我们不能在netty的事件回调中做大量耗时操作.那么问题来了 – 1, writeAndFlush是线程安全的 ...

最新文章

  1. Qt访问SQLite数据库
  2. boost::fusion::fused_function_object用法的测试程序
  3. python自动化工具开发_初识TPOT:一个基于Python的自动化机器学习开发工具
  4. c 调用python_c调用python
  5. 01:操作系统(centos,redhat):性能监控和网络命令
  6. java网络图片与二进制字符串相互转换
  7. c语言合法常量e8,c语言合法常量定义
  8. 例29:哥德巴赫猜想
  9. Celery使用数据库代替rabbitmq
  10. 计算机专业专科生毕业论文题目,★专科生计算机专业论文题目专科生计算机专业毕业论文题目大全专科生计算机专业论文选题参考...
  11. matlab list函数参数,Matlab 函数参数汇总
  12. 创业日志(三十)华东华南之10天7市行
  13. Unity技能系统架构
  14. 居家学习python自制闹铃小助手
  15. 酷派s6、Coolpad 9190l_C00 无log信息输出解决方法
  16. mysql基于WebStorm服装购物网站的设计与实现毕业设计源码281444
  17. 高性能mysql 随笔
  18. admin_move_table的重组机制验证(失败了)
  19. [离散数学] 关于p - q的理解。
  20. Flutter 不可错过的学习资源

热门文章

  1. 小程序编译时, 提示: 80051,scource size 8290KB exceed max limit 2MB
  2. Clojure的recur尾递归优化探秘
  3. acm 1465 java做法
  4. OSChina 周二乱弹 —— 举杯邀明月,对狗成三人
  5. 来自中科院的一次java技术面经历
  6. 创建私有CA并进行证书申请。
  7. 深入分析JavaWeb Item46 -- Struts2数据校验与国际化
  8. 2020年数据中心IDC深度报告
  9. 希捷公布部分硬盘存在固件缺陷
  10. java 如何通过年份获取当前年有多少天,具体年份天数