inBound事件的传播
何为inBound事件以及ChannelInboundHandler
ChannelRead事件的传播 ChannelRead是典型的inbound事件,以他为例了解inbound事件的传播
SimpleInBoundHandler处理器
何为inBound事件以及ChannelInboundHandler
ChannelHandler的继承关系
ChannelHandler定义了哪些功能
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
public interface {
void handlerAdded(ChannelHandlerContext ctx) throws Exception; //handler被pipeline删除时的回调 void handlerRemoved(ChannelHandlerContext ctx) throws Exception; //出现异常时的回调
void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception;
//一个注解,是否可以被多个pipeline添加 @Inherited @Documented @Target(ElementType.TYPE) @Retention(RetentionPolicy.RUNTIME) @interface Sharable { // no value } }
ChannelInboundHandler在ChannelHandler的基础上扩展了什么
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
public interface ChannelInboundHandler extends { //回调,Handler注册到nioEventLoop的selector上时 void channelRegistered(ChannelHandlerContext ctx) throws Exception; void channelUnregistered(ChannelHandlerContext ctx) throws Exception;
//channel的激活或失效时的回调 void channelActive(ChannelHandlerContext ctx) throws Exception; void channelInactive(ChannelHandlerContext ctx) throws Exception;
//channel读到数据或接收到连接时的回调 //对于服务端而言是连接,对于客户端channel则是bytebuf的数据 void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception; void channelReadComplete(ChannelHandlerContext ctx) throws Exception;
//trigger一些用户自定义的事件 void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception;
//可写状态发生了改变 void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception;
//出现异常时的回调 @Override @SuppressWarnings("deprecation") void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception; }
这次由ChannelRead为例,看一下inbound事件是如何传播的.
ChannelRead事件的传播
这里创建3个自定义的InboundHandler测试ChannelRead事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
public class InBoundHandlerA extends ChannelInboundHandlerAdapter {
@Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { System.out.println("InBoundHandlerA: " + msg); ctx.fireChannelRead(msg); } } public class InBoundHandlerB extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { System.out.println("InBoundHandlerB: " + msg);//打印 ctx.fireChannelRead(msg);//继续传播 }
@Override public void channelActive(ChannelHandlerContext ctx) { //channel被激活时取到pipeline,激活channelRead事件 ctx.channel().pipeline().fireChannelRead("hello world"); } } public class InBoundHandlerC extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { System.out.println("InBoundHandlerC: " + msg); ctx.fireChannelRead(msg); } }
服务端启动代码添加childHandler的部分为
1 2 3 4 5 6 7 8
.childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) { ch.pipeline().addLast(new InBoundHandlerA()); ch.pipeline().addLast(new InBoundHandlerB()); ch.pipeline().addLast(new InBoundHandlerC()); } });
此时启动后在控制台执行telnet 127.0.0.1 8888
,它的后台输出结果为
1 2 3
InBoundHandlerA: hello world InBoundHandlerB: hello world InBoundHandlerC: hello world
如果把添加childHandler的部分改为下面的顺序
1 2 3
ch.pipeline().addLast(new InBoundHandlerA()); ch.pipeline().addLast(new InBoundHandlerC()); ch.pipeline().addLast(new InBoundHandlerB());
那么输出是
1 2 3
InBoundHandlerA: hello world InBoundHandlerC: hello world InBoundHandlerB: hello world
可以推测出,inbound和添加顺序相关.在InboundHandlerB
的channelActive()
中打个断点.看一下它的fireChannelRead("hello world")
的逻辑.会看到它会调用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
@Override public final ChannelPipeline fireChannelRead(Object msg) { //head节点 AbstractChannelHandlerContext.invokeChannelRead(head, msg); return this; } --- static void invokeChannelRead(final AbstractChannelHandlerContext next, Object msg) { final Object m = next.pipeline.touch(ObjectUtil.checkNotNull(msg, "msg"), next); EventExecutor executor = next.executor(); if (executor.inEventLoop()) {//true //此时的next为head next.invokeChannelRead(m); } else { executor.execute(new Runnable() { @Override public void run() { next.invokeChannelRead(m); } }); } }
也就是说,当遇到channelRead事件时它会从调用head的invokeChannelRead.继续看
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
private void invokeChannelRead(Object msg) { if (invokeHandler()) { //调用head的channelRead() ((ChannelInboundHandler) handler()).channelRead(this, msg); } else { fireChannelRead(msg); } } --- @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { //也是调用head的fireChannelRead // 这里head会把fireChannelRead原封不动的进行传播 ctx.fireChannelRead(msg); }
这个fireChannelRead是io.netty.channel.AbstractChannelHandlerContext#fireChannelRead
.它会调用findContextInbound()
来获取下一个inboundHandler
1 2 3 4 5 6 7 8 9 10 11 12 13 14
@Override public ChannelHandlerContext fireChannelRead(final Object msg) { //获取下一个inboundHandler invokeChannelRead(findContextInbound(), msg); return this; } --- private AbstractChannelHandlerContext findContextInbound() { AbstractChannelHandlerContext ctx = this; do { ctx = ctx.next; } while (!ctx.inbound);//寻找下一个inbound节点 return ctx; }
获取到下一个inboundHandler,也就是InboundHandlerA后它会调用io.netty.channel.AbstractChannelHandlerContext#invokeChannelRead
,这个和之前head调用的是完全相同的方法,他回去调用当前inboundHandler,也就是InboundHandlerA的readChannel方法,它就是我们在用户代码中定义的部分.
1 2 3 4 5 6 7 8
public class InBoundHandlerA extends ChannelInboundHandlerAdapter {
@Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { System.out.println("InBoundHandlerA: " + msg); ctx.fireChannelRead(msg);//继续往下传播 } }
然后InBoundHandlerA
的channelRead()
又会调用下一个InBoundHandlerB
的channelRead()
,它又会接着调用InBoundHandlerC
的channelRead()
此时回顾一下InBoundHandlerB
中的逻辑,会发现.pipeline中inBound的传播方式为:
1 2
ctx.fireChannelRead(msg); //从当前节点继续往下传播 ctx.channel().pipeline().fireChannelRead("hello world");//从head节点开始往下传播
当传播到最后一个节点(C)时,它会传播到最后的Tail节点,调用它的channelRead()
.之前说过tial是用来做一些收尾工作,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
@Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { //事件一直传播到tail, //tail会认为没有被处理,这个方法会打印一个logger.debug.来提醒开发者 onUnhandledInboundMessage(msg); } --- protected void onUnhandledInboundMessage(Object msg) { try { logger.debug( "Discarded inbound message {} that reached at the tail of the pipeline. " + "Please check your pipeline configuration.", msg); } finally { //如果时bytebuf就进行释放 ReferenceCountUtil.release(msg); } }
SimpleInBoundHandler处理器
以下面的自定义Handler为例,看一下它的使用场景
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
public class AuthHandler extends SimpleChannelInboundHandler<ByteBuf> { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { //如果没有把这里的ByteBuf传播到tail,那么tail节点就不会帮你释放这段ByteBuf //通常这种情况下你需要手动进行释放, //而SimpleChannelInboundHandler会帮你自动释放他们 }
@Override protected void channelRead0(ChannelHandlerContext ctx, ByteBuf password) throws Exception { if (paas(password)) { ctx.pipeline().remove(this); } else { ctx.close(); } }
private boolean paas(ByteBuf password) { return false; } }
看一下SimpleChannelInboundHandler
的channelRead()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
@Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { boolean release = true; try { //如果是inbound if (acceptInboundMessage(msg)) { //转为bytebuf I imsg = (I) msg; //抽象方法 //继承SimpleChannelInboundHandler时你可以做一些你想干的,并且不用考虑释放 channelRead0(ctx, imsg); } else { release = false; ctx.fireChannelRead(msg); } } finally { if (autoRelease && release) { //自动释放 ReferenceCountUtil.release(msg); } } }
也就是说使用SimpleChannelInboundHandler
时不需要管channelRead()
而是通过channelRead0()
来做原本在channelRead()
中的逻辑.因为SimpleChannelInboundHandler的channelRead()
中定义了从执行channelRead0()
直到释放的过程,所以当channelRead0()
被执行后它会自动帮你去释放
outBound事件的传播
何为outBound事件以及ChannelOutBoundHandler
write()事件的传播 典型的outBound事件
何为outBound事件以及ChannelOutBoundHandler
ChannelOutboundHandler在ChannelHandler的基础上扩展了什么
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
public interface ChannelOutboundHandler extends { //端口绑定,服务端启动时的那个 void bind(ChannelHandlerContext ctx, SocketAddress localAddress, ChannelPromise promise) throws Exception;
void connect( ChannelHandlerContext ctx, SocketAddress remoteAddress, SocketAddress localAddress, ChannelPromise promise) throws Exception;
void disconnect(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;
void close(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;
void deregister(ChannelHandlerContext ctx, ChannelPromise promise) throws Exception;
void read(ChannelHandlerContext ctx) throws Exception;
void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) throws Exception;
void flush(ChannelHandlerContext ctx) throws Exception; }
相对于InBounder,outBound更像是用户主动发起的操作.而InBounder更类似于事件触发
write()事件的传播
这里创建3个自定义的OutboundHandler测试write事件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34
public class OutBoundHandlerA extends ChannelOutboundHandlerAdapter {
@Override public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) { System.out.println("OutBoundHandlerA: " + msg); ctx.write(msg, promise); } } --- public class OutBoundHandlerB extends ChannelOutboundHandlerAdapter { @Override public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) { System.out.println("OutBoundHandlerB: " + msg); ctx.write(msg, promise); }
@Override public void handlerAdded(final ChannelHandlerContext ctx) { //定时器,模拟服务端处理完成数据后给客户端的响应 ctx.executor().schedule(() -> { ctx.channel().write("hello world"); }, 3, TimeUnit.SECONDS); } } 大专栏 事件和异常的传播 · 农场主的黑科技. pan class="line">--- public class OutBoundHandlerC extends ChannelOutboundHandlerAdapter {
@Override public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) { System.out.println("OutBoundHandlerC: " + msg); ctx.write(msg, promise); } }
服务端创建代码
1 2 3 4 5 6 7 8
.childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) { ch.pipeline().addLast(new OutBoundHandlerA()); ch.pipeline().addLast(new OutBoundHandlerB()); ch.pipeline().addLast(new OutBoundHandlerC()); } });
输出为以下,和添加顺序正好相反 .也就是说outBound的添加顺序和pipeline中的传播顺序是相反的
1 2 3
OutBoundHandlerC: hello world OutBoundHandlerB: hello world OutBoundHandlerA: hello world
下面看看write()
的源码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
@Override public ChannelFuture write(Object msg) { return pipeline.write(msg); } --- @Override public final ChannelFuture write(Object msg) { //注意,这里会调用tail的write return tail.write(msg); } --- @Override public ChannelFuture write(Object msg) { //newPromise()创建一个空的回调 return write(msg, newPromise()); }
这个pipeline的write()实际会调用tail的write,这里的newPromise()
是一个回调
1 2 3 4 5 6 7 8 9 10
@Override public ChannelFuture write(final Object msg, final ChannelPromise promise) { write(msg, false, promise);//flash=false return promise; } --- private void write(Object msg, boolean flush, ChannelPromise promise) { AbstractChannelHandlerContext next = findContextOutbound(); //.. }
通过findContextOutbound()
找到下一个传播对象,看一下它的源码
1 2 3 4 5 6 7
private AbstractChannelHandlerContext findContextOutbound() { AbstractChannelHandlerContext ctx = this; do { ctx = ctx.prev; } while (!ctx.outbound); return ctx; }
和inbound的时候相似,通过while找前面那个outbound节点,此时会返回最后添加的C节点,继续看这个write()
的逻辑
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
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()) {//true if (flush) {//参数传的false next.invokeWriteAndFlush(m, promise); } else { //最终调用这个 next.invokeWrite(m, promise); } } else { //.. }
也就是说write()
会调用下一个outbound节点的invokeWrite()
1 2 3 4 5 6 7 8 9 10 11 12
private void invokeWrite(Object msg, ChannelPromise promise) { if (invokeHandler()) {//true invokeWrite0(msg, promise);//调用这个 } else { write(msg, promise); } } --- private void invokeWrite0(Object msg, ChannelPromise promise) { //拿到ChannelOutboundHandler(节点C)后,调用它的write()方法 ((ChannelOutboundHandler) handler()).write(this, msg, promise); }
也就是会调用用户代码自定义的write()
1 2 3 4 5 6 7 8
public class OutBoundHandlerC extends ChannelOutboundHandlerAdapter {
@Override public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise) { System.out.println("OutBoundHandlerC: " + msg); ctx.write(msg, promise); //继续往下传播 } }
这个write会从当前节点调用io.netty.channel.AbstractChannelHandlerContext#write(java.lang.Object, io.netty.channel.ChannelPromise)
,寻找下一个传播对象,并调用它的write
1 2 3 4 5 6 7 8 9 10 11
@Override public ChannelFuture write(final Object msg, final ChannelPromise promise) { write(msg, false, promise); return promise; } --- private void write(Object msg, boolean flush, ChannelPromise promise) { //这时返回的就是节点B AbstractChannelHandlerContext next = findContextOutbound(); //.. }
后面节点B又会通过相同逻辑调用A.A节点又会调用相同逻辑,但此时返回的next是head节点.也就是head节点的wtite()方法.
1 2 3 4
@Override public void write(ChannelHandlerContext ctx, Object msg, ChannelPromise promise){ unsafe.write(msg, promise); }
总结一下就是
1 2
ctx.channel().write("hello world"); //从tail开始传播 ctx.write(msg, promise); //从当前节点开始往下传播
异常的传播
异常的触发链
在服务端启动代码中添加6个Handler
1 2 3 4 5 6 7 8 9 10 11
.childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) { ch.pipeline().addLast(new InBoundHandlerA()); ch.pipeline().addLast(new InBoundHandlerB()); ch.pipeline().addLast(new InBoundHandlerC()); ch.pipeline().addLast(new OutBoundHandlerA()); ch.pipeline().addLast(new OutBoundHandlerB()); ch.pipeline().addLast(new OutBoundHandlerC()); } });
把InBoundHandlerB
改为以下
1 2 3 4 5 6 7 8 9 10 11 12
public class InBoundHandlerB extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { //显示的抛出异常,模拟读数据的过程中抛出异常 throw new BusinessException("from InBoundHandlerB"); }
@Override public void channelActive(ChannelHandlerContext ctx) { ctx.channel().pipeline().fireChannelRead("hello world"); } }
在InBoundHandlerA
,InBoundHandlerC
,OutBoundHandlerA
,OutBoundHandlerB
,OutBoundHandlerC
中加入以下逻辑
1 2 3 4 5
@Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { System.out.println("InBoundHandlerA.exceptionCaught()");//类名 ctx.fireExceptionCaught(cause); }
通过telnet写入数据,此时会抛出异常
1 2
$ telnet 127.0.0.1 8888 $ send ayt
服务端的输出为
1 2 3 4 5 6
InBoundHandlerB.exceptionCaught() InBoundHandlerC.exceptionCaught() OutBoundHandlerA.exceptionCaught() OutBoundHandlerB.exceptionCaught() OutBoundHandlerC.exceptionCaught() //..以下异常信息
可以看出inBound的输出顺序和inbound事件的传播顺序很相似,但outBound的异常传播顺序和之前的outBound事件传播顺序是相反的.
跟踪源码看一下,抛出异常的InBoundHandlerB#channelRead
的调用顺序是怎样的
1 2 3 4 5 6 7 8 9 10 11 12
private void invokeChannelRead(Object msg) { if (invokeHandler()) { try { //调用InBoundHandlerB#channelRead ((ChannelInboundHandler) handler()).channelRead(this, msg); } catch (Throwable t) { //会捕获到异常 notifyHandlerException(t); } } else { fireChannelRead(msg); } }
跟踪抛出异常时会执行的notifyHandlerException(t)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
private void notifyHandlerException(Throwable cause) { invokeExceptionCaught(cause); } --- private void invokeExceptionCaught(final Throwable cause) { if (invokeHandler()) { try { //它会调用InBoundHandlerB#exceptionCaught handler().exceptionCaught(this, cause); } catch (Throwable error) { } } else { fireExceptionCaught(cause); } }
也就是说在InBoundHandlerB#channelRead
抛出异常时它会回调InBoundHandlerB#exceptionCaught
,
它里面通过ctx.fireExceptionCaught(cause);
把异常事件继续进行传播.接着看
1 2 3 4 5 6 7
@Override public ChannelHandlerContext fireExceptionCaught(final Throwable cause) { //直接拿到当前节点的next节点进行传播,next和节点的添加顺序相关 //当前节点为InBoundHandlerB,那么它的next是C invokeExceptionCaught(next, cause); return this; }
InBoundHandlerB会调用C的异常捕获
1 2 3 4 5 6 7 8
static void invokeExceptionCaught(final AbstractChannelHandlerContext next, final Throwable cause) { EventExecutor executor = next.executor(); if (executor.inEventLoop()) { next.invokeExceptionCaught(cause);//这个 } else { //... } }
接下来就和InB调用同一个方法,执行C的exceptionCaught()
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
private void invokeExceptionCaught(final Throwable cause) { if (invokeHandler()) { try { handler().exceptionCaught(this, cause); } catch (Throwable error) { } } else { fireExceptionCaught(cause); } } --- //用户代码InBoundHandlerC @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { System.out.println("InBoundHandlerC.exceptionCaught()"); ctx.fireExceptionCaught(cause); }
而InBoundHandlerC
的下一个节点则是OutBoundHandlerA
,再下一个则是OutBoundHandlerB
那么OutBoundHandlerC
调用ctx.fireExceptionCaught(cause);
继续往下传播异常时会传播到哪?Tail节点的exceptionCaught()
Tail节点的exceptionCaught()
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
@Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { onUnhandledInboundException(cause); } --- protected void onUnhandledInboundException(Throwable cause) { try { logger.warn( "An exceptionCaught() event was fired, and it reached at the tail of the pipeline. " + "It usually means the last handler in the pipeline did not handle the exception.", cause); } finally { ReferenceCountUtil.release(cause); } }
提醒用户代码异常尚未被处理.上面的示例程序中它会打印
1 2 3 4 5 6 7 8
Jan 07, 2019 10:00:35 AM io.netty.channel.DefaultChannelPipeline onUnhandledInboundException WARNING: An exceptionCaught() event was fired, and it reached at the tail of the pipeline. It usually means the last handler in the pipeline did not handle the exception. com.imooc.netty.ch6.BusinessException: from InBoundHandlerB at com.imooc.netty.ch6.InBoundHandlerB.channelRead(InBoundHandlerB.java:12) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:373) at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:359) at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:351) //...
以上就是异常的触发链.可以发现它的异常触发和是inBound还是outBound没有关系,只会每次调用next触发下一个节点,也就是说和Handler的添加顺序有关.
那么如果没有在用户代码中重载exceptionCaught()
,它的父类会做哪些事?
1 2 3 4 5
//io.netty.channel.ChannelHandlerAdapter#exceptionCaught @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { ctx.fireExceptionCaught(cause); }
就是直接把异常往下传播
异常处理的最佳实践
项目中常用的异常处理方式:在每一条channel的最后给它添加一个终极的Exception处理器,如:
1 2 3 4 5 6 7 8
public class ExceptionCaughtHandler extends ChannelInboundHandlerAdapter { @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { if (cause instanceof BusinessException){ System.out.println("BusinessException"); } } }
在pipeline的最后添加ExceptionCaughtHandler
1 2 3 4 5 6 7 8 9 10 11 12 13
.childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) { ch.pipeline().addLast(new InBoundHandlerA()); ch.pipeline().addLast(new InBoundHandlerB()); ch.pipeline().addLast(new InBoundHandlerC()); ch.pipeline().addLast(new OutBoundHandlerA()); ch.pipeline().addLast(new OutBoundHandlerB()); ch.pipeline().addLast(new OutBoundHandlerC()); //终极的Exception处理器 ch.pipeline().addLast(new ExceptionCaughtHandler()); } });
此时的输出为以下,说明异常在最后被处理了
1 2 3 4 5 6
InBoundHandlerB.exceptionCaught() InBoundHandlerC.exceptionCaught() OutBoundHandlerA.exceptionCaught() OutBoundHandlerB.exceptionCaught() OutBoundHandlerC.exceptionCaught() BusinessException
总结
netty如何判断ChannelHandler类型的? 添加一个节点时,pipeline会通过一个instanceof来判断是inbound类型还是outbound类型,然后用布尔变量来标示
对于ChannelHandler的添加应该遵循什么样的顺序? inBound事件的传播顺序和inBoundHandler的添加顺序相同 outBound事件的传播顺序和outBoundHandler的添加顺序相反
用户手动触发事件传播,不同的触发方式有什么样的区别?
inbound事件,如
1 2
ctx.fireChannelRead(msg); //从当前节点继续往下传播 ctx.channel().pipeline().fireChannelRead("hello world");//从head节点开始往下传播
outbound事件,如
1 2
ctx.write(msg, promise); //从当前节点开始往下传播 ctx.channel().write("hello world"); //从tail开始传播
WCF技术剖析之二十四: ServiceDebugBehavior服务行为是如何实现异常的传播的?
原文:WCF技术剖析之二十四: ServiceDebugBehavior服务行为是如何实现异常的传播的? 服务端只有抛出FaultException异常才能被正常地序列化成Fault消息,并实现向客户 ...
『无为则无心』Python函数 — 39、Python中异常的传播
目录 1.异常的传播 2.如何处理异常 1.异常的传播 当在函数中出现异常时,如果在函数中对异常进行了处理,则异常不会再继续传播.如果函数中没有对异常进行处理,则异常会继续向函数调用者传播.如果函数调 ...
Java黑科技之源:JVMTI完全解读
Java生态中有一些非常规的技术,它们能达到一些特别的效果.这些技术的实现原理不去深究的话一般并不是广为人知.这种技术通常被称为黑科技.而这些黑科技中的绝大部分底层都是通过JVMTI实现的. 形象地说 ...
一文带你了解 HTTP 黑科技
这是 HTTP 系列的第三篇文章,此篇文章为 HTTP 的进阶文章. 在前面两篇文章中我们讲述了 HTTP 的入门,HTTP 所有常用标头的概述,这篇文章我们来聊一下 HTTP 的一些 黑科技. HT ...
基于Twitter的Snowflake算法实现分布式高效有序ID生产黑科技(无懈可击)
参考美团文档:https://tech.meituan.com/2017/04/21/mt-leaf.html Twitter-Snowflake算法产生的背景相当简单,为了满足Twitter每秒上万 ...
优云亮相GOPS2017全球运维大会 “黑科技”获全场最高关注
2017年4月21日,GOPS――2017全球运维大会于深圳・圣淘沙酒店拉开帷幕.GOPS全球运维大会由高效运维社区(GreatOPS)和开放运维联盟(OOPSA)联合主办,由工信部信通院数据中心联盟 ...
MTSC2019-腾讯WeTest独家揭秘移动游戏测试和质量保障 QA 黑科技
WeTest 导读 TesterHome 联合腾讯 WeTest 出品 MTSC2019 重磅游戏测试 Topic ,首次公开揭秘腾讯亿级用户游戏背后的质量保障 QA 黑科技. 2019 年,中国游戏 ...
黑科技如何制造人类V2.0?
黑科技泛指人类尚未成熟但具有巨大潜力的科学技术,智能手机.大数据.扫码支付.电子地图等等都曾属于黑科技范畴,随着时间的推移,它们慢慢成熟,且展现出巨大的能力,影响人类进程,最终黑科技转变成人类伟大的创 ...
支持十万并发的黑科技-NIO
今天是猿灯塔“365天原创计划”第3天. 今天讲: 支持十万并发的黑科技-NIO 翻译过来就是:Dubbo是阿里巴巴开源的基于 Java 的高性能 RPC(一种远程调用) 分布式服务框架(SOA),致 ...
随机推荐
mysql6数据库安装与配置
详细步骤可以参考这篇文章 https://www.cnblogs.com/duguangming/p/10623520.html 1.下载并打开mysql6数据库安装文件 2.默认点击下一步 3.点击 ...
Println(Object)小贴士
println public void println(Object x) 打印 Object,然后终止该行.此方法首先调用 String.valueOf(x) 获取打印对象的字符串值,然后的行为如同 ...
psmisc
https://packages.ubuntu.com/xenial/psmisc Package: psmisc (22.21-2.1build1) Other Packages Related t ...
Sort - Merge Sort
归并排序思想 归并排序时间复杂度分析 归并排序特点 归并排序实现 递归 #include <iostream> using namespace std; void merge(int *a ...
新服务器搭建-总结: 下载nginx,jdk8,docker-compose编排(安装mysql,redis) 附安装
三明SEO: 前言 如题, 公司新买了一条4核16G的服务器, 不得不重新搭建环境, 只能一一重来, 做个记录 1.nginx : 手动安装 2.jdk8: 手动安装 3. 安装docker 及doc ...
C实现日志等级控制
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <stdarg.h&g ...
liquibase使用教程
在项目中引入liquibase过程: 1.父项目 pom.xml 中添加依赖 <dependencies> <dependency> <groupId>mysql& ...
在VMware装了linux系统,如何在windows系统中用xshell连接
网上有好几种方法,不过我觉得这种比较简单 1.找到VMware菜单 打开 编辑>虚拟网络编辑器 如图: 点下面的更改设置 点确定就可以了,什么都不用改.然后回到linux系统中ifconfig ...
PAT甲级——1058 A+B in Hogwarts
1058 A+B in Hogwarts If you are a fan of Harry Potter, you would know the world of magic has its own ...
VS工程的相对路径写法
最近搭建一个VS工程的框架,为了让所有人都能直接使用,要使用相对路径,下面的几种常见路径的写法: 1.两个点“..\”表示在工程文件(*.vcxproj)的上一级目录. 2.一个点“.\”则表示和工程 ...