https://github.com/wuyinxian124/nettybook2.git
使用com.phei.netty.frame.delimiter.EchoServer做实验
使用这个工程时要处理一下pom的包冲突,否则调试的时候回显"alternative"之类让你选择代码的操作
俩问题
1、数据如何传到worker线程
2、netty中如何实现多个handler链式调用
数据如何传到worker线程
@Override
public void read() {
...省略很多行...
ByteBuf byteBuf = null;
int messages = 0;
boolean close = false;
try {
int byteBufCapacity = allocHandle.guess();
int totalReadAmount = 0;
do {
byteBuf = allocator.ioBuffer(byteBufCapacity);//分配bytebuf
int writable = byteBuf.writableBytes();
int localReadAmount = doReadBytes(byteBuf);//将数据写入新分配的bytebuf
...省略很多行...
}
调用堆栈
read:119, AbstractNioByteChannel$NioByteUnsafe (io.netty.channel.nio)
processSelectedKey:485, NioEventLoop (io.netty.channel.nio)
processSelectedKeysOptimized:452, NioEventLoop (io.netty.channel.nio)
run:346, NioEventLoop (io.netty.channel.nio)
run:794, SingleThreadEventExecutor$5 (io.netty.util.concurrent)
run:748, Thread (java.lang)
上图中的pipeline.fireChannelRead
触发了handler的链式调用
netty中如何实现多个handler链式调用
上图中DefaultChannelHandlerContext的pipeline字段指向DefaultChannelPipeline。因为图已经很乱了,所以就不连接pipeline和DefaultChannelPipeline
创建
使用
接收远程数据
io.netty.channel.ChannelPipeline
中的方法开始传播事件。
比如服务端接收数据时调用堆栈如下
channelRead:37, EchoServerHandler (com.phei.netty.frame.delimiter)
invokeChannelReadNow:74, ChannelHandlerInvokerUtil (io.netty.channel)
invokeChannelRead:138, DefaultChannelHandlerInvoker (io.netty.channel)
fireChannelRead:320, DefaultChannelHandlerContext (io.netty.channel)
channelRead:103, MessageToMessageDecoder (io.netty.handler.codec)
invokeChannelReadNow:74, ChannelHandlerInvokerUtil (io.netty.channel)
invokeChannelRead:138, DefaultChannelHandlerInvoker (io.netty.channel)
fireChannelRead:320, DefaultChannelHandlerContext (io.netty.channel)
channelRead:154, ByteToMessageDecoder (io.netty.handler.codec)
invokeChannelReadNow:74, ChannelHandlerInvokerUtil (io.netty.channel)
invokeChannelRead:138, DefaultChannelHandlerInvoker (io.netty.channel)
fireChannelRead:320, DefaultChannelHandlerContext (io.netty.channel)
fireChannelRead:846, DefaultChannelPipeline (io.netty.channel)
read:127, AbstractNioByteChannel$NioByteUnsafe (io.netty.channel.nio)
processSelectedKey:485, NioEventLoop (io.netty.channel.nio)
processSelectedKeysOptimized:452, NioEventLoop (io.netty.channel.nio)
run:346, NioEventLoop (io.netty.channel.nio)
run:794, SingleThreadEventExecutor$5 (io.netty.util.concurrent)
run:748, Thread (java.lang)
上面的调用堆栈中有几点需要注意。
1、有些调用不断出现
invokeChannelReadNow:74, ChannelHandlerInvokerUtil (io.netty.channel)
invokeChannelRead:138, DefaultChannelHandlerInvoker (io.netty.channel)
fireChannelRead:320, DefaultChannelHandlerContext (io.netty.channel)
2、调用链的起始位置在fireChannelRead:846, DefaultChannelPipeline (io.netty.channel)
@Override
public ChannelPipeline fireChannelRead(Object msg) {
head.fireChannelRead(msg);//第846行
return this;
}
3、若要事件传递下去有俩条件
首先需要handler主动调用fireChannelRead方法
比如在ByteToMessageDecoder中
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof ByteBuf) {
....................
try {
.........
} catch (DecoderException e) {
throw e;
} catch (Throwable t) {
throw new DecoderException(t);
} finally {
.........
for (int i = 0; i < size; i ++) {
ctx.fireChannelRead(out.get(i));
}
out.recycle();
}
} else {
ctx.fireChannelRead(msg);
}
}
或是MessageToMessageDecoder
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
RecyclableArrayList out = RecyclableArrayList.newInstance();
try {
....................
} catch (DecoderException e) {
throw e;
} catch (Exception e) {
throw new DecoderException(e);
} finally {
int size = out.size();
for (int i = 0; i < size; i ++) {
ctx.fireChannelRead(out.get(i));
}
out.recycle();
}
}
比如EchoServerHandler这样不调用fireChannelRead,事件就无法传递下去
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg)
throws Exception {
String body = (String) msg;
System.out.println("This is " + ++counter + " times receive client : ["
+ body + "]");
body += "$_";
ByteBuf echo = Unpooled.copiedBuffer(body.getBytes());
ctx.writeAndFlush(echo);
}
如果在EchoServerHandler中调用fireChannelRead,事件将传递给尾部的DefaultChannelHandlerContext。虽然没实验检测过,但是我觉得直接传递给尾部的DefaultChannelHandlerContext也不会有问题。
发送数据到远程
writeAndFlush:430, DefaultChannelHandlerContext (io.netty.channel)
writeAndFlush:438, DefaultChannelHandlerContext (io.netty.channel)
channelRead:42, EchoServerHandler (com.phei.netty.frame.delimiter)
invokeChannelReadNow:74, ChannelHandlerInvokerUtil (io.netty.channel)
invokeChannelRead:138, DefaultChannelHandlerInvoker (io.netty.channel)
fireChannelRead:320, DefaultChannelHandlerContext (io.netty.channel)
channelRead:103, MessageToMessageDecoder (io.netty.handler.codec)
invokeChannelReadNow:74, ChannelHandlerInvokerUtil (io.netty.channel)
invokeChannelRead:138, DefaultChannelHandlerInvoker (io.netty.channel)
fireChannelRead:320, DefaultChannelHandlerContext (io.netty.channel)
channelRead:154, ByteToMessageDecoder (io.netty.handler.codec)
invokeChannelReadNow:74, ChannelHandlerInvokerUtil (io.netty.channel)
invokeChannelRead:138, DefaultChannelHandlerInvoker (io.netty.channel)
fireChannelRead:320, DefaultChannelHandlerContext (io.netty.channel)
fireChannelRead:846, DefaultChannelPipeline (io.netty.channel)
read:127, AbstractNioByteChannel$NioByteUnsafe (io.netty.channel.nio)
processSelectedKey:485, NioEventLoop (io.netty.channel.nio)
processSelectedKeysOptimized:452, NioEventLoop (io.netty.channel.nio)
run:346, NioEventLoop (io.netty.channel.nio)
run:794, SingleThreadEventExecutor$5 (io.netty.util.concurrent)
run:748, Thread (java.lang)
由于本例中没有与MASK_WRITE
和MASK_FLUSH
匹配的handler,所以在io.netty.channel.DefaultChannelHandlerContext#writeAndFlush(Object msg, ChannelPromise promise)
中直接调用了pipeline链表中的头context
@Override
public ChannelFuture writeAndFlush(Object msg, ChannelPromise promise) {
DefaultChannelHandlerContext next;
next = findContextOutbound(MASK_WRITE);//没有找到MASK_WRITE,直接返回了pipeline链表中的头节点
next.invoker.invokeWrite(next, msg, promise);
next = findContextOutbound(MASK_FLUSH);//没有找到MASK_FLUSH,直接返回了pipeline链表中的头节点
next.invoker.invokeFlush(next);//里面调用了io.netty.channel.socket.nio.NioSocketChannel#doWriteBytes
return promise;
}
1、如果实现了
MASK_WRITE
和MASK_FLUSH
匹配的handler,应该可以产生调用链。
DefaultChannelHandlerContext中skipFlags字段用于标识此上下文中的handler某些函数是否跳过。比如对于TailHandler,由于其write方法中标有@Skip
注释,在findContextOutbound中就会直接跳过所属上下文。而对于HeadHandler其write方法没有标注@Skip
注释,所以在findContextOutbound中可以被返回。
2、上面的代码中使用的是findContextOutbound
,如果是"接收远程数据",则是findContextInbound
。"in"和"out"可能和pipeline中链表调用顺序有关。"in"从头到尾,"out"从尾到头
3、注意上面代码使用了两次findContextOutbound
分别找了不同的handler。不过不会产生树状调用链,因为代码中已经调用了invokeWrite
和invokeFlush
,这俩函数中不会直接或间接调用invokeWriteAndFlushNow
这类东西